Académique Documents
Professionnel Documents
Culture Documents
html
1 de 7 06/10/00 20:05
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2 file:///C|/librosVirtuales/index.html
División
Conversiones
4.1.7 - Manipulación de cadenas
4.1.8 - Operaciones lógicas a nivel de bit
4.1.9 - De control del procesador
4.1.10 - De rotación y desplazamiento
4.2 - Resumen alfabético de las instrucciones y banderines. Indice.
4.3 - Instrucciones específicas del 286, 386 y 486 en modo real
4.3.1 - Diferencias en el comportamiento global respecto al 8086
4.3.2 - Instrucciones específicas del 286
4.3.3 - Instrucciones propias del 386 y 486
4.3.4 - Detección de un sistema AT o superior
4.3.5 - Evaluación exacta del microprocesador instalado
4.3.6 - Modo plano (flat) del 386 y superiores
2 de 7 06/10/00 20:05
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2 file:///C|/librosVirtuales/index.html
3 de 7 06/10/00 20:05
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2 file:///C|/librosVirtuales/index.html
10 - PROGRAMAS RESIDENTES
10.1 - Principios básicos
10.2 - Un ejemplo sencillo
10.3 - Localización de un programa residente
10.3.1 - Método de los vectores de interrupción
10.3.2 - Método de la cadena de bloque de memoria
10.3.3 - Método de la interrupción Multiplex
10.4 - Expulsión de un programa residente de la memoria
10.5 - Gestión avanzada de la interrupción Multiplex
10.5.1 - El convenio BMB Compuscience
10.5.2 - El convenio CiriSOFT
10.5.3 - La propuesta AMIS
10.5.4 - Comparación entre métodos
10.6 - Métodos especiales para economizar memoria
10.7 - Programas autoinstalables en memoria superior
10.8 - Programas residentes en memoria extendida con DR-DOS 6.0
10.9 - Ejemplo de programa residente que utiliza la BIOS
10.10 -Uso sin límites de servicios del DOS en programas residentes
4 de 7 06/10/00 20:05
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2 file:///C|/librosVirtuales/index.html
11 - CONTROLADORES DE DISPOSITIVO
11.1 - Introducción
11.2 - Encabezamiento y palabra de atributos
11.3 - Rutinas de estrategia e interrupción
11.4 - Ordenes a soportar por el controlador de dispositivo
11.5 - La cadena de controladores de dispositivo instalados
11.6 - Ejemplo de controlador de dispositivo de caracteres
11.7 - Ejemplo de controlador de dispositivo de bloques
11.7.1 - Disco virtual TURBODSK: Características
11.7.2 - Ensamblando TURBODSK
11.7.3 - Análisis detallado del listado de TURBODSK
11.8 - Los controladores de dispositivo y el DOS
5 de 7 06/10/00 20:05
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2 file:///C|/librosVirtuales/index.html
6 de 7 06/10/00 20:05
EL UNIVERSO DIGITAL DEL IBM PC, AT Y PS/2 file:///C|/librosVirtuales/index.html
12.11 - El ratón
12.12 - El reloj de tiempo real del AT: Motorola MC146818
12.12.1 - Descripción del integrado
12.12.2 - El MC146818 dentro del ordenador
12.12.3 - Un método para averiguar la configuración del AT y PS/2
13 - EL ENSAMBLADOR Y EL LENGUAJE C
13.1 - Uso del Turbo C y Borland C a bajo nivel
13.1.1 - Acceso a los puertos de E/S
13.1.2 - Acceso a la memoria
13.1.3 - Control de interrupciones
13.1.4 - Llamada a interrupciones
13.1.5 - Cambio de vectores de interrupción
13.1.6 - Programas residentes
13.1.7 - Variables globales predefinidas interesantes
13.1.8 - Inserción de codigo en línea
13.1.9 - Las palabras clave interrupt y asm
13.2 - Interfaz C (Borland/Microsoft) - Ensamblador
13.2.1 - Modelos de memoria
13.2.2 - Integración de módulos en ensamblador
APÉNDICES
I Mapa de memoria
II Tabla de interrupciones del sistema
III Tabla de variables de la BIOS
IV Puertos de E/S
V Códigos de rastreo del teclado
VI Tamaños y tiempos de ejecución de las instrucciones
VII Señales del slot de expansión ISA
VIII Funciones del sistema, la BIOS y el DOS aludidas en este libro
IX Especificaciones XMS y EMS: Todas sus funciones
X Juego de caracteres ASCII extendido
XI Bibliografía
7 de 7 06/10/00 20:05
untitled file:///C|/librosVirtuales/UniversoDigital/00.html
Nota: Pudiendo haber discrepancias entre sucesivas ediciones de estas normas, la versión de referencia
válida e inapelable será la ubicada en todo momento en la red, en la dirección electrónica arriba indicada o cualquier
otra que pudiera sucederla.
La edición 4.0 (4ª edición) de El Universo Digital del IBM PC, AT y PS/2 es un libro
electrónico/impreso de dominio público; de libre uso, difusión, copia y distribución entre particulares, en
cualquier soporte. Quienes decidan utilizarlo deberán registrarse por vía electrónica una sola vez, por razones
de ética (http://fly.to/udigital). También es posible hacerlo enviando una carta o postal ordinaria (mejor en un
sobre) al autor, con cualquier texto, a la siguiente dirección:
Indicando claramente que el motivo es registrar el Universo Digital. Los que hayan comprado la versión
impresa en persona no necesitan registrarse, aunque lo recibiría con agrado, incluso si ha pasado bastante
tiempo (pero si lo compraron por correo no deben registrarse: conservo su pedido). Me gustaría conocer en
alguna medida la difusión de la obra, en especial a partir de este momento, lo que hasta ahora me resultaba
algo más sencillo. Por supuesto, los datos o direcciones indicadas por los usuarios nunca serán divulgados por
mí.
Se aplican exactamente las mismas condiciones que para usuarios particulares, con la excepción de que se
recomienda un único registro electrónico o una sola carta o postal en representación de todos los posibles
usuarios de la entidad.
Editando revistas (no libros) la distribución está permitida en cualquier formato digital (HTML,
PostScript, WordPerfect, texto, o cualesquiera otros) tanto en fragmentos como toda la obra completa.
Siendo el formato una revista impresa sólo se permiten fragmentos que no totalicen más del 75% de la obra
en los sucesivos números publicados. Es necesario citar la procedencia. La distribución por empresas que
cobren una cierta cantidad por el soporte es libre. Mi única sugerencia es que la empresa me envíe una copia
del soporte (CD, etc.) en que se publique, por cortesía.
Tratándose de empresas editoriales u otras cualesquiera que planeen incluirlo, entero o por fragmentos, en
el soporte impreso, electrónico u online de algún libro que vayan a publicar, deberían contactar primero
conmigo para negociar una nueva versión (que en todo caso no implicaría la desaparición de ésta en su estatus
1 de 7 12/10/00 19:02
untitled file:///C|/librosVirtuales/UniversoDigital/00.html
actual).
Modificaciones.
El Universo Digital no nació tras una decisión premeditada. Su objetivo inicial fue dotar de un manual de
apoyo al Curso de Lenguaje Ensamblador, que ofrece todos los años la asociación Grupo Universitario de
Informática de la Universidad de Valladolid, en el marco de unos Cursos de Introducción a la Informática
-para los alumnos y personal en general de la Universidad- que abarcan un espectro mucho más amplio que el
de la programación de los ordenadores.
La primera versión ocupaba 116 páginas, cuando su denominación era aún la de Curso de Ensamblador.
Sin embargo, en una época en la que era difícil encontrar información, y buena bibliografía especializada, el
autor siguió recopilando material interesante y añadiéndolo al curso. Una buena parte de dicho material y del
añadido después ha sido además de cosecha propia. La primera edición de El Universo Digital, editada no
mucho tiempo después del manual del curso, rebasó ligeramente las 300 páginas. Posteriormente se
incrementaría aún algo más, hasta las 420 de la 3ª edición que ha mantenido durante la mayor parte del
tiempo.
El DOS en la actualidad.
Actualmente, y desde hace algún tiempo, la programación en DOS ya no es importante, y mucho menos al
nivel que desarrolla este libro, y ello pese a que incluso Windows 95 corre aún en alguna parte sobre DOS,
comportamiento que irá reduciéndose hasta la eliminación en próximas versiones.
El futuro de la programación, sin embargo, no es sólo para los programadores de alto nivel. En alguna
manera, los propios usuarios pueden y podrán cada vez en mayor medida hacer sus propios programas
incluso sin darse cuenta. Sin embargo, siempre hay alguien que tiene que construir los sistemas operativos, y
sobre todo, los controladores para dar soporte a los dispositivos en los diversos sistemas operativos. Por no
mencionar las aplicaciones especializadas, desde máquinas industriales al microprocesador de las sondas
espaciales (que, evidentemente, no corre bajo Windows). Es para los programadores de sistemas, y para
aquellos que necesitan o quieren saber cómo funciona el PC por dentro, como ejemplo práctico de
arquitectura interna de un ordenador, para los que va destinado este libro. Que podrán practicar en un
entorno cómodo para este tipo de programación, como es el DOS (que deja todo el control de la máquina a
cada tarea). Aunque algunos contenidos muy relacionados con el DOS siguen presentes en esta obra, el lector
habrá de tener en cuenta si es pertinente profundizar en ellos o no, en la época que vivimos.
Mi objetivo inicial no fue publicarlo, aunque hace dos o tres años sí me lo planteé un poco en serio.
Las ventajas de una edición oficial sería su no engorrosa distribución (uno de los motivos por los que
siempre ha costado poco es porque nuestra Asociación y el propio autor ha puesto su mano de obra gratis),
2 de 7 12/10/00 19:02
untitled file:///C|/librosVirtuales/UniversoDigital/00.html
así como su mayor difusión. Puesto en contacto con cuatro prestigiosas editoriales; las que han respondido
han valorado muy positivamente la obra, sin embargo la han rechazado aduciendo otros motivos
(«sobrecarga del programa editorial», solapamiento en contenidos con «obras publicadas o en fase de
publicación», o simplemente «falta de interés comercial»). Una de ellas aún no ha respondido.
Los inconvenientes de su publicación por una editorial serían el importante aumento de precio, y mi
renuncia a los derechos de distribución (en particular, nuestra Asociación tendría que comprar en la librería los
ejemplares para nuestros cursos).
Sin embargo, la ventaja de la publicación para facilitar la difusión popular es obvia, máxime si lo hace una
editorial importante (si no, no aparecería en todas las estanterías, la publicidad la harían los lectores
lentamente, como ya se venía haciendo, y la distribución sería incluso más limitada pese al recurso a los
baratos servicios de reprografía por parte de los usuarios).
Mi decisión final ya la había acariciado con anterioridad. Algo había que hacer, pues la distribución gratuita
del libro llevaba mucho tiempo.
Uno de los motivos que han terminado empujándome a esta decisión, ha sido la considerable cantidad de
pedidos que hemos recibido desde países de hispanoamérica. Se trata de ciudadanos que conocen el índice
del libro a través del Web y lo piden, sobre todo desde México. Sin embargo, sólo en la primera ocasión lo
he enviado (a Perú); los motivos son, desgraciadamente, la práctica imposibilidad de comerciar a pequeña
escala con esos países (no existe el envío contrarreembolso, por ejemplo); las enormes demoras del envío por
superficie (el coste del envío aéreo supera el del propio libro) y las complicadas gestiones de pago e injustas
comisiones bancarias (aunque las pague el usuario final); finalmente habría que añadir incluso mi temor
inconsciente a un aumento incontrolado de la demanda, cuando ya había demasiado trabajo que hacer para
atender la de origen nacional (en mi memoria estaba lo que ocurrió cuando empezaron a aparecer mensajes y
comenzaron a recibirse pedidos por FidoNET). Pido desde aquí disculpas a todos los que lo han solicitado
desde fuera de España, mayores además si no he contestado el E-Mail por no haber tomado aún una decisión
al respecto.
El Universo Digital de dominio público en formato electrónico, podrá ser accedido desde cualquier lugar
del mundo, y en cualquier CD de los kioscos.
El inconveniente es que no todos tienen igual acceso a estas redes y medios, aunque ese inconveniente
disminuirá exponencialmente con el tiempo (con el mismo exponente con que crezca la red).
Naturalmente, una vez que he renunciado a mis derechos sobre el libro, donándolo al dominio público, ya
no estoy obligado a venderlo impreso (medida tomada únicamente para mantener el copyright). Realmente,
no tenemos tiempo ni medios para atender la demanda actual: aunque es una medida dura de imponer,
lamento renunciar a realizar más envíos de ejemplares impresos. Renuncio con ello a facilitar su difusión a los
lectores menos introducidos en las redes telemáticas, pero beneficio a otros muchos, que además podrán
seguir usando la versión manuscrita utilizando una impresora.
Por otro lado, haber facturado sólo aproximadamente el coste de impresión y distribución, me permiten
tomar esa decisión sin temer el enfado de quienes lo habían comprado. El coste de impresión de los últimos
números en la reprografía oficial de la Universidad (rechazamos opciones más baratas de menor calidad),
3 de 7 12/10/00 19:02
untitled file:///C|/librosVirtuales/UniversoDigital/00.html
encuadernación y disquete era de 1900 pts. El libro (realmente, apuntes técnicos fotocopiados) se vendía a
2100 pts más gastos de envío. Ese margen de beneficios era más bien de maniobra, ya que por ejemplo, en
los ejemplares que no llegaban a su destino, el coste del envío y la devolución lo pagábamos nosotros. Cada
envío llevaba una media de 20 minutos de tiempo total de mano de obra, contabilizando la preparación de los
libros (transporte físico, disquete, gestión del pedido...), y la mayoría eran de una sola unidad (pese a que se
penalizaba su envío con 100 pts adicionales). El precio de los más de 1200 Universos Digitales vendidos ha
tenido un crecimiento nominal cero en los cinco años de difusión impresa.
Aunque en general no se harán más envíos, la única excepción corresponderá a los pedidos realizados
desde bibliotecas (universitarias o no universitarias), que tal vez no tengan la impresora adecuada o tiempo
para reproducirlo, lo que perjudicaría a un amplio conjunto potencial de
usuarios. No se harán envíos a otras organizaciones, ni a librerías o a particulares. Subrayamos que El
Universo Digital impreso tiene el carácter legal de apuntes técnicos impresos y no de libro.
Los pedidos de ejemplares impresos serán admitidos sólo desde España. Habrán de realizarse
exclusivamente por carta impresa, que deberá estar compulsada por el sello y en su caso papel oficial de la
biblioteca que hace el pedido, además de debidamente firmada por quien corresponda. Es conveniente que
figure el teléfono de la biblioteca o en su defecto de la conserjería del centro. Además del nombre completo,
dirección y NIF. Nos reservamos el derecho de rechazar aquellos pedidos que no cumplan alguno de estos
requisitos, o los de sospechosa procedencia. La dirección es: Grupo Universitario de Informática.
Apartado 6062. 47080 Valladolid. El precio por ejemplar será el que figure en la factura que realizará el
propio servicio de reprografía (unas 2000 pts/unidad); sumando al final el coste exacto del envío y los
disquetes.
Agradecimientos.
Agradezco desde aquí al servicio de Reprografía de la Universidad, ubicado en la Casa del Estudiante, el
esmero puesto durante tanto tiempo en la reproducción y encuadernación de cada número durante la etapa
impresa. Cualquier pequeño problema de calidad se ha debido siempre a los fallos inevitables que en
ocasiones presenta toda máquina, por buena que sea.
Mis agradecimientos también a las diversas instituciones de la Universidad de Valladolid, que han recibido
en ocasión la presión de la demanda a través de incorrectas llamadas telefónicas solicitando el libro, no
siendo ellos los encargados de su distribución; también al Grupo Universitario de Informática, por su
colaboración a todos los niveles.
No puedo decir lo mismo de los funcionarios de Correos: aunque algunos son amables, en general, el
funcionamiento de esa institución es el que cabía esperar de un monopolio no sometido a la libre competencia
en envíos postales ordinarios (y que, por tanto, no tiene la obligación de tratar bien a sus clientes, porque
también volverán mañana). El trato que reciben los clientes no se diferencia mucho del de los paquetes, y
estos son muy expresivos en ocasiones al llegar al destino. Por otro lado, la cantidad de papeles que hay que
rellenar en cada envío, y algunas normas de la empresa (como el plomo adherido a los paquetes postales) no
se han simplificado desde finales del siglo XIX. Tampoco es comprensible que sólo Argentaria sea aún la
única entidad financiera con el privilegio de gestionar las denominadas Cuentas Corrientes Postales.
Además de que el servicio de correos es caro en la realidad (esto es, cuando se incluye lo que pagamos en
impuestos para cubrir las pérdidas de la compañía) se mantiene el viejo vicio de indexar las tarifas anuales
(aumento del 8% en 1997, cuando hay un 2% de inflación nacional).
4 de 7 12/10/00 19:02
untitled file:///C|/librosVirtuales/UniversoDigital/00.html
Sin embargo, he de reconocer que la fiabilidad de Correos (entendida en cuanto a paquetes que llegan a
su destino o en su defecto vuelven por motivo de dirección incorrecta) es próxima al 100%: los envíos no
suelen perderse, al menos los de los reembolsos. En puntualidad, aunque hay extremos de gran aleatoriedad
(desde paquetes que llegan en tres días a un pueblo perdido en la otra punta del país, a los que tardan quince
en ir de Valladolid a Madrid) el tiempo promedio podría aproximarse, aunque por debajo, a lo que afirma la
empresa.
PRÓLOGO
DE LA TERCERA EDICIÓN (1994)
Ha pasado un año desde la publicación de la primera edición de esta obra. Desde entonces, ha
continuado la expansión de los interfaces gráficos de usuario y los sistemas operativos avanzados para
PC. Sin embargo, pese a que la programación continúa alejándose cada vez más del bajo nivel de las
máquinas, los programadores de sistemas en el entorno del PC siguen existiendo y son muchos más
que los que trabajan para las empresas punteras en el desarrollo de los sistemas operativos. Los
ordenadores compatibles poseen numerosas aplicaciones en el campo industrial, para las que es
conveniente un conocimiento elevado del funcionamiento interno del ordenador en general y del
MS-DOS en particular. Para aquellas personas que necesitan comprender el funcionamiento de un
ordenador, las máquinas compatibles constituyen una interesante oportunidad y punto de partida. Este
libro pretende cubrir una importante laguna en la bibliografía disponible actualmente sobre la
programación a nivel de sistemas de los ordenadores compatibles.
Respecto a la primera edición, se han incrementado los contenidos en una proporción equivalente al
20% de lo que ya existía, corrigiéndose además algunos errores. Aunque el libro comience con una
introducción a la aritmética binaria que pueda indicar todo lo contrario, se presupone que el lector tiene
unos mínimos conocimientos de informática, al menos un dominio básico del sistema operativo
MS-DOS, siendo más que recomendable conocer algún lenguaje de programación. Seguidamente se
explica el lenguaje ensamblador de la serie 80x86 de Intel separando claramente las instrucciones de
los diversos procesadores, aunque dejando de lado algunas instrucciones del 286 y 386 que se salen
del entorno MS-DOS. También se describe la sintaxis del lenguaje ensamblador; sin embargo, aunque
este último aspecto está extensamente documentado, los lectores que no conozcan el lenguaje
ensamblador de ningún microprocesador habrán de trabajar considerablemente leyendo multitud de
listados hasta adquirir la soltura necesaria y, sobre todo, creando los suyos propios. Aunque sería
conveniente describir el lenguaje C, íntimo aliado del ensamblador en la programación de sistemas, ello
se deja por razones de espacio para otras publicaciones.
5 de 7 12/10/00 19:02
untitled file:///C|/librosVirtuales/UniversoDigital/00.html
Las memorias extendida XMS y expandida EMS son descritas con cierto detenimiento, dada su
presencia en todos los ordenadores modernos y su importancia.
Existen apéndices que describen todas las funciones del DOS, de la BIOS y del sistema usadas en
las rutinas y programas desarrollados, así como la totalidad de las funciones XMS y EMS. Sin
embargo, no están ni muchísimo menos todas las interrupciones necesarias, por lo que se insta al lector
a conseguir el impresionante fichero de dominio público INTERRUPT.LST, complemento ideal de este
libro (ver bibliografía).
Los programas residentes reciben un tratamiento especialmente profundo: desde los métodos más
eficientes para que detecten su propia presencia en memoria, a las técnicas más avanzadas para
economizar memoria, pasando por el uso de funciones del DOS de manera concurrente al programa
principal, así como técnicas de empleo de memoria extendida y superior para conseguir programas que
usen 0 Kb dentro de los primeros 640 Kb de la máquina y todo ello sin olvidar la convivencia con los
actuales entornos operativos, como Windows, y la posibilidad de ser activados desde pantallas
gráficas.
Este libro también trata los controladores de dispositivo o device drivers, desde los dos posibles
enfoques de su uso: bien sea la creación de controladores de dispositivo de caracteres, bien la de
nuevas unidades de disco añadidas a las del sistema; en ambos casos se incluyen ejemplos reales de
controladores completos y comprobados, en particular el ejemplo de disco virtual: un completo
ejemplo de controlador redimensionable que soporta memoria convencional, XMS y EMS.
Existe un capítulo muy próximo al hardware en el que se describen a fondo y sin omisiones todos
los chips del ordenador, para permitir al programador de sistemas un control completo del equipo.
Para asimilar este capítulo hace falta cierta formación previa en los sistemas digitales; sin embargo, los
ejemplos que siguen a la información técnica aclaran las explicaciones previas y pueden ser
aprovechados de manera inmediata incluso sin entender todo lo anterior. Los chips de apoyo al
microprocesador son descritos de manera total: primero, no relacionados con el PC sino como tales
circuitos; después integrándolos en el ordenador y documentando profusamente su uso, con ejemplos
probados. Se consideran el interfaz de periféricos 8255 (útil para averiguar la configuración de los
PC/XT), el temporizador 8253/8254 (para temporización y síntesis de sonido), el controlador de
interrupciones 8259, el controlador de DMA 8237 (para acceso a disco), el controlador de disquetes
765 (acceso directo a los sectores), la controladora de disco duro de los AT (IDE, MFM ó Bus
Local); el controlador del teclado del AT (8042); el UART 8250 (empleado en las comunicaciones
serie) y el reloj de tiempo real MC146818 (configuración de AT y programación de alarmas y
temporizaciones). Los ejemplos en este capítulo experimentan una importante potenciación respecto a
la edición anterior; en particular, en lo relacionado con el controlador de disquetes se puede considerar
que la información vertida es prácticamente casi toda la existente, existiendo pautas suficientes para que
el lector cree sus propios programas copiones, protecciones de disco, formatos de alta capacidad, etc.
6 de 7 12/10/00 19:02
untitled file:///C|/librosVirtuales/UniversoDigital/00.html
Resumiendo, el libro pretende reunir en una sola obra la mayoría de la información necesaria para el
programador de sistemas, exponiendo toda la información y no sólo lo imprescindible, sin olvidos ni
omisiones; también se pretende explicar las técnicas más avanzadas de creación de programas
residentes. Este afán de información completa es el responsable del título del libro.
Todos los listados de ejemplo se suponen de dominio público y las rutinas pueden ser incluidas por
los lectores libremente en sus propios programas, aunque en el caso de los programas completos debe
citarse la procedencia y dejar bien claro en las versiones modificadas quién las ha alterado. En todo
caso, pese a que todas las rutinas y programas han sido probados debidamente en un 8088, un 286, un
386 o un 486 -bajo varios sistemas operativos y con diferentes configuraciones del hardware- el autor
del libro no se responsabiliza de su correcto funcionamiento en todas las circunstancias.
7 de 7 12/10/00 19:02
INTRODUCCIÕN file:///C|/librosVirtuales/01.html
Capítulo I: INTRODUCCIÓN
El sistema de numeración utilizado habitualmente es la base 10; es decir, consta de 10 dígitos (0-9) que
podemos colocar en grupos, ordenados de izquierda a derecha y de mayor a menor.
Cada posición tiene un valor o peso de 10n donde n representa el lugar contado por la derecha:
1357 = 1 x 103 + 3 x 102 + 5 x 101 + 7 x 100
Explícitamente, se indica la base de numeración como 135710.
Análogamente a la base 10, cada posición tiene un valor de 2n donde n es la posición contando desde la
derecha y empezando por 0:
1012 = 1 x 22 + 0 x 21 + 1 x 20
Además, por su importancia y utilidad, es necesario conocer otros sistemas de numeración como pueden
ser el octal (base 8) y el hexadecimal (base 16). En este último tenemos, además de los números del 0 al 9,
letras -normalmente en mayúsculas- de la A a la F.
Llegar a un número en estos sistemas desde base 2 es realmente sencillo si agrupamos las cifras binarias de
3 en 3 (octal) o de 4 en 4 (hexadecimal):
Base 2 a base 8: 101 0112 = 538
Base 2 a base 16: 0010 10112 = 2B16
A la inversa, basta convertir cada dígito octal o hexadecimal en binario:
Base 8 a base 2: 248 = 010 1002
Base 16 a base 2: 2416 = 0010 01002
De ahora en adelante, se utilizarán una serie de sufijos para determinar el sistema de numeración
empleado:
Sufijo Base Ejemplos
b 2 01101010b
o,q 8 175o
d 10 789d
h 16 6A5h
En caso de que no aparezca el sufijo, el número se considera decimal; es decir, en base 10.
Pese a que las conversiones entre base 2 y base 8 y 16 son prácticamente directas, existe un sistema
general para realizar el cambio de una base a otra. El paso de cualquier base a base 10 lo vimos antes:
6A5h = 6 x 162 + 10 x 161 + 5 x 160
Inversamente, si queremos pasar de base 10 a cualquier otra habrá que realizar sucesivas divisiones por la
base y tomar los restos:
1 de 5 12/10/00 19:03
INTRODUCCIÕN file:///C|/librosVirtuales/01.html
donde 4 es el último cociente (menor que la base) y los restantes dígitos son los restos en orden inverso.
1.3.1. - BIT.
Toda la memoria del ordenador se compone de dispositivos electrónicos que pueden adoptar únicamente
dos estados, que representamos matemáticamente por 0 y 1. Cualquiera de estas unidades de información se
denomina BIT, contracción de «binary digit» en inglés.
1.3.2. - BYTE.
Cada grupo de 8 bits se conoce como byte u octeto. Es la unidad de almacenamiento en memoria, la cual
está constituida por un elevado número de posiciones que almacenan bytes. La cantidad de memoria de que
dispone un sistema se mide en Kilobytes (1 Kb = 1024 bytes), en Megabytes (1 Mb = 1024 Kb), Gigabytes
(1 Gb = 1024 Mb), Terabytes (1 Tb = 1024 Gb) o Petabytes (1 Pb = 1024 Tb).
Los bits en un byte se numeran de derecha a izquierda y de 0 a 7, correspondiendo con los exponentes de
las potencias de 2 que reflejan el valor de cada posición. Un byte nos permite, por tanto, representar 256
estados (de 0 a 255) según la combinación de bits que tomemos.
1.3.3. - NIBBLE.
Cada grupo de cuatro bits de un byte constituye un nibble, de forma que los dos nibbles de un byte se
llaman nibble superior (el compuesto por los bits 4 a 7) e inferior (el compuesto por los bits 0 a 3). El nibble
tiene gran utilidad debido a que cada uno almacena un dígito hexadecimal:
Para sumar números, tanto en base 2 como hexadecimal, se sigue el mismo proceso que en base 10:
2 de 5 12/10/00 19:03
INTRODUCCIÕN file:///C|/librosVirtuales/01.html
En general, se define como valor negativo de un número el que necesitamos sumarlo para obtener 00h, por
ejemplo:
Por esta razón, el número 80h, cuyo complemento a dos es él mismo, se considera negativo (-128) y el
número 00h, positivo. En general, para hallar el complemento a dos de un número cualquiera basta con
calcular primero su complemento a uno, que consiste en cambiar los unos por ceros y los ceros por unos en
su notación binaria; a continuación se le suma una unidad para calcular el complemento a dos. Con una
calculadora, la operación es más sencilla: el complemento a dos de un número A de n bits es 2n -A.
Otro factor a considerar es cuando se pasa de operar con un número de cierto tamaño (ej., 8 bits) a otro
mayor (pongamos de 16 bits). Si el número es positivo, la parte que se añade por la izquierda son bits a 0.
Sin embargo, si era negativo (bit más significativo activo) la parte que se añade por la izquierda son bits a 1.
Este fenómeno, en cuya demostración matemática no entraremos, se puede resumir en que el bit más
significativo se copia en todos los añadidos: es lo que se denomina la extensión del signo: los dos siguientes
números son realmente el mismo número (el -310): 11012 (4 bits) y 111111012 (8 bits).
3 de 5 12/10/00 19:03
INTRODUCCIÕN file:///C|/librosVirtuales/01.html
Los números binarios de más de un byte se almacenan en la memoria en los procesadores de Intel en
orden inverso: 01234567h se almacenaría: 67h, 45h, 23h, 01h.
Consiste en emplear cuatro bits para codificar los dígitos del 0 al 9 (desperdiciando las seis combinaciones
que van de la 1010 a la 1111). La ventaja es la simplicidad de conversión a/de base 10, que resulta
inmediata. Los números BCD pueden almacenarse desempaquetados, en cuyo caso cada byte contiene un
dígito BCD (Binary-Coded Decimal); o empaquetados, almacenando dos dígitos por byte (para construir los
números que van del 00 al 99). La notación BCD ocupa cuatro bits -un nibble- por cifra, de forma que en el
formato desempaquetado el nibble superior siempre es 0.
Son grupos de bytes en los que una parte se emplea para guardar las cifras del número (mantisa) y otra
para indicar la posición del punto flotante (exponente), de modo equivalente a la notación científica. Esto
permite trabajar con números de muy elevado tamaño -según el exponente- y con una mayor o menor
precisión en función de los bits empleados para codificar la mantisa.
El código A.S.C.I.I. (American Standard Code for Information Interchange) es un convenio adoptado
para asignar a cada carácter un valor numérico; su origen está en los comienzos de la Informática tomando
como muestra algunos códigos de la transmisión de información de radioteletipo. Se trata de un código de 7
bits con capacidad para 128 símbolos que incluyen todos los caracteres alfanuméricos del inglés, con
símbolos de puntuación y algunos caracteres de control de la transmisión.
Con posterioridad, con la aparición de los microordenadores y la gran expansión entre ellos de los
IBM-PC y compatibles, la ampliación del código ASCII realizada por esta marca a 8 bits, con capacidad
para 128 símbolos adicionales, experimenta un considerable auge, siendo en la actualidad muy utilizada y
recibiendo la denominación oficial de página de códigos 437 (EEUU). Se puede consultar al final de este
libro. Es habitualmente la única página soportada por las BIOS de los PC. Para ciertas nacionalidades se han
diseñado otras páginas específicas que requieren de un software externo. En las lenguas del estado español y
en las de la mayoría de los demás países de la UE, esta tabla cubre todas las necesidades del idioma.
4 de 5 12/10/00 19:03
INTRODUCCIÕN file:///C|/librosVirtuales/01.html
x NOT (x)
0 1 x y x AND y x OR y x XOR y
1 0 00 0 0 0
01 0 1 1
10 0 1 1
11 1 1 0
5 de 5 12/10/00 19:03
ARQUITECTURA E HISTORIA DE LOS MICROORDENADORES file:///C|/librosVirtuales/UniversoDigital/02.html
Es sobradamente conocido que los actuales sistemas operativos son programados en su mayor parte en
lenguajes de alto nivel, especialmente C, pero siempre hay una parte en la que el ensamblador se hace casi
insustituible bajo DOS y es la programación de los drivers para los controladores de dispositivos,
relacionados con las tareas de más bajo nivel de una máquina, fundamentalmente las operaciones de
entrada/salida en las que es preciso actuar directamente sobre los demás chips que acompañan al
microprocesador. Por ello y porque las instrucciones del lenguaje ensamblador están íntimamente ligadas a la
máquina, vamos a realizar primero un somero repaso a la arquitectura interna de un microordenador.
Centrándonos en los ordenadores sobre los que vamos a trabajar desarrollaré a grandes rasgos la
arquitectura Von Newman que, si bien no es la primera en aparecer, sí que lo hizo prácticamente desde el
comienzo de los ordenadores y se sigue desarrollando actualmente. Claro es que está siendo desplazada por
otra que permiten una mayor velocidad de proceso, la RISC.
En los primeros tiempos de los ordenadores, con sistemas de numeración decimal, una electrónica
sumamente complicada muy susceptible a fallos y un sistema de programación cableado o mediante fichas,
Von Newman propuso dos conceptos básicos que revolucionarían la incipiente informática:
a) La utilización del sistema de numeración binario. Simplificaba enormemente los problemas que la
implementación electrónica de las operaciones y funciones lógicas planteaban, a la vez proporcionaba una
mayor inmunidad a los fallos (electrónica digital).
Tomando como modelo las máquinas que aparecieron incorporando las anteriores características, el
ordenador se puede considerar compuesto por las siguientes partes:
- La Unidad Central de Proceso, U.C.P., más conocida por sus siglas en inglés (CPU).
- La Memoria Interna, MI.
- Unidad de Entrada y Salida, E/S.
- Memoria masiva Externa, ME.
Realicemos a continuación una descripción de lo que se entiende por cada una de estas partes y cómo
están relacionadas entre si:
- La Unidad Central de Proceso (CPU) viene a ser el cerebro del ordenador y tiene por misión efectuar
las operaciones aritmético-lógicas y controlar las transferencias de información a realizar.
1 de 6 12/10/00 19:05
ARQUITECTURA E HISTORIA DE LOS MICROORDENADORES file:///C|/librosVirtuales/UniversoDigital/02.html
- La Memoria Interna (MI) contiene el conjunto de instrucciones que ejecuta la CPU en el transcurso de
un programa. Es también donde se almacenan temporalmente las variables del mismo, todos los datos que se
precisan y todos los resultados que devuelve.
- Unidades de entrada y salida (E/S) o Input/Output (I/O): son las encargadas de la comunicación de la
máquina con el exterior, proporcionando al operador una forma de introducir al ordenador tanto los
programas como los datos y obtener los resultados.
Como es de suponer, estas tres partes principales de que consta el ordenador deben estar íntimamente
conectadas; aparece en este momento el concepto de bus: el bus es un conjunto de líneas que enlazan los
distintos componentes del ordenador, por ellas se realiza la transferencia de datos entre todos sus elementos.
- De control: forman parte de él las líneas que seleccionan desde dónde y hacia dónde va dirigida la
información, también las que marcan la secuencia de los pasos a seguir para dicha transferencia.
- De datos: por él, de forma bidireccional, fluyen los datos entre las distintas partes del ordenador.
- De direcciones: como vimos, la memoria está dividida en pequeñas unidades de almacenamiento que
contienen las instrucciones del programa y los datos. El bus de direcciones consta de un conjunto de líneas
que permite seleccionar de qué posición de la memoria se quiere leer su contenido. También direcciona los
puertos de E/S.
La forma de operar del ordenador en su conjunto es direccionar una posición de la memoria en busca de
una instrucción mediante el bus de direcciones, llevar la instrucción a la unidad central de proceso -CPU- por
medio del bus de datos, marcando la secuencia de la transferencia el bus de control. En la CPU la instrucción
se decodifica, interpretando qué operandos necesita: si son de memoria, es necesario llevarles a la CPU; una
vez que la operación es realizada, si es preciso se devuelve el resultado a la memoria.
2.2. - EL MICROPROCESADOR.
- Unidad aritmético-lógica: Es donde se efectúan las operaciones aritméticas (suma, resta, y a veces
producto y división) y lógicas (and, or, not, etc.).
- Decodificador de instrucciones: Allí se interpretan las instrucciones que van llegando y que componen el
programa.
- Bloque de registros: Los registros son celdas de memoria en donde queda almacenado un dato
temporalmente. Existe un registro especial llamado de indicadores, estado o flags, que refleja el estado
operativo del microprocesador.
- Bloque de control de buses internos y externos: supervisa todo el proceso de transferencias de
información dentro del microprocesador y fuera de él.
La trepidante evolución del mundo informático podría provocar que algún recién llegado a este libro no
sepa exactamente qué diferencia a un ordenador "AT" del viejo "XT" inicial de IBM. Algunos términos
2 de 6 12/10/00 19:05
ARQUITECTURA E HISTORIA DE LOS MICROORDENADORES file:///C|/librosVirtuales/UniversoDigital/02.html
manejados en este libro podrían ser desconocidos para los lectores más jóvenes. Por ello, haremos una
pequeña introducción sobre la evolución de los ordenadores personales, abarcando toda la historia (ya que no
es muy larga).
La premonición.
En 1973, el centro de investigación de Xerox en Palo Alto desarrolló un equipo informático con el aspecto
externo de un PC personal actual. Además de pantalla y teclado, disponía de un artefacto similar al ratón; en
general, este aparato (denominado Alto) introdujo, mucho antes de que otros los reinventaran, algunos de los
conceptos universalmente aceptados hoy en día. Sin embargo, la tecnología del momento no permitió alcanzar
todas las intenciones. Alguna innovación, como la pantalla vertical, de formato similar a una hoja de papel (que
desearían algunos actuales internautas para los navegadores) aún no ha sido adoptada: nuestros PC's siguen
pareciendo televisores con teclas, y los procesadores de textos no muestran legiblemente una hoja en vertical
completa incluso en monitores de 20 pulgadas.
El microprocesador.
El desarrollo del primer microprocesador por Intel en 1971, el 4004 (de 4 bits), supuso el primer paso
hacia el logro de un PC personal, al reducir drásticamente la circuitería adicional necesaria. Sucesores de este
procesador fueron el 8008 y el 8080, de 8 bits. Ed Roberts construyó en 1975 el Altair 8800 basándose en
el 8080; aunque esta máquina no tenía teclado ni pantalla (sólo interruptores y luces), era una arquitectura
abierta (conocida por todo el mundo) y cuyas tarjetas se conectaban a la placa principal a través de 100
terminales, que más tarde terminarían convirtiéndose en el bus estándar S-100 de la industria.
El Apple-I apareció en 1976, basado en el microprocesador de 8 bits 6502, en aquel entonces un recién
aparecido aunque casi 10 veces más barato que el 8080 de Intel. Fue sucedido en 1977 por el Apple-II. No
olvidemos los rudimentos de la época: el Apple-II tenía un límite máximo de 48 Kbytes de memoria. En el
mismo año, Commodore sacó su PET con 8 Kbytes. Se utilizaban cintas de casete como almacenamiento,
aunque comenzaron a aparecer las unidades de disquete de 5¼. Durante finales de los 70 aparecieron
muchos otros ordenadores, fruto de la explosión inicial del microprocesador.
En 1980, Sir Clive Sinclair lanzó el ZX-80, seguido muy poco después del ZX-81. Estaban basados en un
microprocesador sucesor del 8085 de Intel: el Z80 (desarrollado por la empresa Zilog, creada por un
ex-ingeniero de Intel). Commodore irrumpió con sus VIC-20 y, posteriormente, el Commodore 64, basados
aún en el 6502 y, este último, con mejores posibilidades gráficas y unos 64 Kb de memoria. Su competidor
fue el ZX-Spectrum de Sinclair, también basado en el Z80, con un chip propio para gestión de gráficos y
otras tareas, la ULA, que permitió rebajar su coste y multiplicó su difusión por europa, y en particular por
España. Sin embargo, todos los ordenadores domésticos de la época, como se dieron en llamar, estaban
basados en procesadores de 8 bits y tenían el límite de 64 Kb de memoria. Los intentos de rebasar este límite
manteniendo aún esos chips por parte de la plataforma MSX (supuesto estándar mundial con la misma suerte
que ha corrido el Esperanto) o los CPC de Amstrad, de poco sirvieron.
El IBM PC.
Y es que IBM también fabricó su propio ordenador personal con vocación profesional: el 12 de agosto de
1981 presentó el IBM PC. Estaba basado en el microprocesador 8088, de 16 bits, cuyas instrucciones
serán las que usemos en este libro, ya que todos los procesadores posteriores son básicamente (en
MS-DOS) versiones mucho más rápidas del mismo. El equipamiento de serie consistía en 16 Kbytes de
memoria ampliables a 64 en la placa base (y a 256 añadiendo tarjetas); el almacenamiento externo se hacía en
cintas de casete, aunque pronto aparecieron las unidades de disco de 5¼ pulgadas y simple cara
3 de 6 12/10/00 19:05
ARQUITECTURA E HISTORIA DE LOS MICROORDENADORES file:///C|/librosVirtuales/UniversoDigital/02.html
(160/180 Kb por disco) o doble cara (320/360 Kb). En 1983 apareció el IBM PC-XT, que traía como
novedad un disco duro de 10 Mbytes. Un año más tarde aparecería el IBM PC-AT, introduciendo el
microprocesador 286, así como ranuras de expansión de 16 bits (el bus ISA de 16 bits) en contraposición
con las de 8 bits del PC y el XT (bus ISA de 8 bits), además incorporaba un disco duro de 20 Mbytes y
disquetes de 5¼ pero con 1.2 Mbytes.
En general, todos los equipos con procesador 286 o superior pueden catalogarse dentro de la categoría
AT; el término XT hace referencia al 8088/8086 y similares. Finalmente, por PC (a secas) se entiende
cualquiera de ambos; aunque si se hace distinción entre un PC y un AT en la misma frase, por PC se
sobreentiende un XT, menos potente. El término PC ya digo, no obstante, es hoy en día mucho más general,
referenciando habitualmente a cualquier ordenador personal.
Alrededor del PC se estaba construyendo un imperio de software más importante que el propio hardware:
estamos hablando del sistema operativo PC-DOS. Cuando aparecieron máquinas compatibles con el PC de
IBM, tenían que respetar la compatibilidad con ese sistema, lo que fue sencillo (ya que Microsoft, le gustara o
no a IBM, desarrolló el MS-DOS, compatible con el PC-DOS pero que no requería la BIOS del ordenador
original, cuyo copyright era de IBM). Incluso, el desarrollo de los microprocesadores posteriores ha estado
totalmente condicionado por el MS-DOS. [Por cierto, la jugada del PC-DOS/MS-DOS se repetiría en
alguna manera pocos años después con el OS/2-Windows].
A partir de 1986, IBM fue paulatinamente dejando de tener la batuta del mercado del PC. La razón es
que la propia IBM tenía que respetar la compatibilidad con lo anterior, y en ese terreno no tenía más
facilidades para innovar que la competencia. El primer problema vino con la aparición de los procesadores
386: los demás fabricantes se adelantaron a IBM y lanzaron máquinas con ranuras de expansión aún de 16
bits, que no permitían obtener todo el rendimiento. IBM desarrolló demasiado tarde, en 1987, la arquitectura
Microchannel, con bus de 32 bits pero cerrada e incompatible con tarjetas anteriores (aunque se
desarrollaron nuevas tarjetas, eran caras) y la incluyó en su gama de ordenadores PS/2 (alguno de cuyos
modelos era aún realmente ISA). La insolente respuesta de la competencia fue la arquitectura EISA, también
de 32 bits pero compatible con la ISA anterior.
Otro ejemplo: si IBM gobernó los estándares gráficos hasta la VGA, a partir de ahí sucedió un fenómeno
similar y los demás fabricantes se adelantaron a finales de los 80 con mejores tarjetas y más baratas; sin
embargo, se perdió la ventaja de la normalización (no hay dos tarjetas superiores a la VGA que funcionen
igual).
EISA también era caro, así que los fabricantes orientales, cruzada ya la barrera de los años 90,
desarrollaron con la norma VESA las placas con bus local (VESA Local Bus); básicamente es una
prolongación de las patillas de la CPU a las ranuras de expansión, lo que permite tarjetas rápidas de 32 bits
pero muy conflictivas entre sí. Esta arquitectura de bus se popularizó mucho con los procesadores 486. Sin
embargo, al final el estándar que se ha impuesto ha sido el propuesto por el propio fabricante de las CPU:
Intel, con su bus PCI, que con el Pentium se ha convertido finalmente en el único estándar de bus de 32 bits.
Estas máquinas aún admiten no obstante las viejas tarjetas ISA, suficientes para algunas aplicaciones de baja
velocidad (modems,... etc).
Una manera sencilla de comprender la evolución de los PC es observar la evolución de las sucesivas
versiones del DOS y los sistemas que le han sucedido.
En 1979, Seatle Computer necesitaba apoyar de alguna manera a sus incipientes placas basadas en el
8086. Como Digital Research estaba tardando demasiado en convertir el CP/M-80 a CP/M-86, desarrolló
su propio sistema: el QDOS 0.1, que fue presentado en 1980. Antes de finales de año apareció QDOS 0.3.
4 de 6 12/10/00 19:05
ARQUITECTURA E HISTORIA DE LOS MICROORDENADORES file:///C|/librosVirtuales/UniversoDigital/02.html
Bill Gates, dueño de Microsoft, de momento sólo poseía una versión de lenguaje BASIC para 8086 no
orientada a ningún sistema operativo particular, que le gustó a algún directivo de IBM. Bill Gates ya había
hecho la primera demostración mundial de BASIC corriendo en un 8086 en las placas de Seatle Computer
(en julio de 1979) y había firmado un contrato de distribución no exclusiva para el QDOS 0.3 a finales de
1980. En abril de 1981 aparecieron las primeras versiones de CP/M-86 de Digital, a la vez que QDOS se
renombraba a 86-DOS 1.0 aunque en principio parecía tener menos futuro que el CP/M. En Julio, sin
embargo, Microsoft adquiría todos los derechos del 86-DOS.
Digital Research no ocupa actualmente el lugar de Microsoft porque en 1981 era una compañía
demasiado importante como para cerrar un acuerdo con IBM sin imponer sus condiciones para cederle los
derechos del sistema operativo CP/M. Así que IBM optó por Bill Gates, que acababa de adquirir un sistema
operativo, el 86-DOS, que pasó a denominarse PC-DOS 1.0. Las versiones de PC-DOS no dependientes
de la ROM BIOS de IBM se denominarían MS-DOS, término que ha terminado siendo más popular.
A continuación se expone la evolución hasta la versión 5.0; las versiones siguientes no añaden ninguna
característica interna nueva destacable (aunque a nivel de interfaz con el usuario y utilidades incluidas haya
más cambios). El MS-DOS 7.0 sobre el que corre Windows 95 sí tiene bastantes retoques internos, pero no
es frecuente su uso aislado o independiente de Windows 95. Aunque PC-DOS y MS-DOS siembre han
caminado paralelos, hay una única excepción: la versión 7.0 (no confundir MS-DOS 7.0 con PC-DOS 7.0:
este último es, realmente, el equivalente al MS-DOS 5.0 ó 6.2).
Marzo de 1982. MS-DOS 1.25, añadiendo soporte para disquetes de doble cara. Las funciones
del DOS (en INT 21h) sólo llegaban hasta la 1Fh (¡la 30h no estaba implementada!).
Marzo de 1983. MS-DOS 2.0 introducido con el XT: reescritura del núcleo en C; mejoras en el
sistema de ficheros (FAT, subdirectorios,...); separación de los controladores de dispositivo del
sistema.
Agosto de 1984. MS-DOS 3.0: Añade soporte para disquetes de 1.2M y discos duros de 20 Mb.
No sería necesaria una nueva versión del DOS para cada nuevo formato de disco si el controlador
integrado para A:, B: y C: lo hubieran hecho flexible algún día.
Abril de 1987. MS-DOS 3.3: Soporte para disquetes de 1.44M (3½-HD). Permite particiones
secundarias en los discos duros. Soporte internacional: páginas de códigos.
Julio de 1988. MS-DOS 4.0: Soporte para discos duros de más de 32 Mb (cambio radical
interno que forzó la reescritura de muchos programas de utilidad) hasta 2 Gb. Controlador de memoria
EMM386. Precipitada salida al mercado.
5 de 6 12/10/00 19:05
ARQUITECTURA E HISTORIA DE LOS MICROORDENADORES file:///C|/librosVirtuales/UniversoDigital/02.html
Junio de 1991. MS-DOS 5.0: Soporte para memoria superior. La competencia de Digital
Research, que irrumpe en el mundo del DOS una década más tarde (con DR-DOS), obliga a
Microsoft a incluir ayuda online y a ocuparse un poco más de los usuarios.
Digital Research trabajó arduamente para lograr una compatibilidad total con MS-DOS, y finalmente
consiguió lanzar al mercado su sistema DR-DOS. Las versiones 5.0 y 6.0 de este sistema, así como el
Novell DOS 7.0 (cuando cedió los derechos a Novell) se pueden considerar prácticamente 100%
compatibles. El efecto del DR-DOS fue positivo, al forzar a Microsoft a mejorar la interacción del sistema
operativo con los usuarios (documentación en línea, programas de utilidad, ciertos detalles...); por poner un
ejemplo, hasta el MS-DOS 6.2 ha sido necesario intercambiar tres veces el disquete origen y el destino
durante la copia de un disquete normal de 1.44M. En cierto modo, la prepotencia de Microsoft con el
MS-DOS a principios de los noventa era similar a la de Digital Research a principios de los 80 con el CP/M.
El futuro.
El resto de la historia de los sistemas operativos de PC ya la conoce el lector, a menos que no esté
informado de la actualidad. Caminamos hacia la integración de los diversos Windows en uno sólo, que
esperemos que algún día sea suficientemente abierto para que le surjan competidores. Si en el futuro hubiera
un sólo sistema operativo soportado por Microsoft, no vamos por buen camino.
En ese caso, sería de agradecer que algún juez les obligara a publicar una especificación completa de las
funciones y protocolos del sistema, con objeto de que algún organismo de normalización internacional las
recogiera sin ambigüedades para permitir la libre competencia de otros fabricantes. El DOS y el Windows
actuales no son ningún invento maravilloso de Microsoft. Por poner un ejemplo, el MS-DOS 1.0 carecía de
función para identificar la versión del sistema. Exactamente lo mismo le ha sucedido a las primeras versiones
de Windows (hay varios chequeos distintos para detectarlas, según el modo de funcionamiento y la versión):
el MS-DOS no lo escribió inicialmente Microsoft, pero Windows sí, y salta a la vista que sus programadores,
para cometer semejante despiste, se sentaron delante del teclado antes de hacer un análisis de la aplicación a
desarrollar, igual que lo hubiera hecho alguien que hubiera aprendido a programar con unos fascículos
comprados en el kiosco. Con tanto analista en el paro...
No olvidemos que el DOS y Windows son el fruto de toda la sociedad utilizando el mismo tipo de
ordenadores y necesitando la compatibilidad con lo anterior a cualquier precio. La prueba evidente son los
procesadores de Intel, construidos desde hace tiempo para dar servicio al sistema operativo del PC. Somos
prisioneros, usuarios obligados de Microsoft. Naturalmente, no tengo nada contra Microsoft, pero opino que
el poder adquirido durante una década, gracias a la exclusiva de los derechos sobre un sistema operativo sin
ayuda en la línea de comandos, o de un Windows cerrado íntimamente ligado al DOS (de quien sólo
Microsoft tiene el código fuente) no legitima a ninguna empresa a tener tanto poder. No lo olvidemos: el
MS-DOS ha dado un vuelco hacia la amigabilidad con el usuario cuando Digital Research ha aparecido con
el DR-DOS. Del mismo modo que Windows seguirá lento o colgándose mientras Unix no tenga más
aplicaciones comerciales.
Si hay alguien que puede competir con Windows es Unix. Y en Unix no dependemos de ningún fabricante
concreto, ni de hardware ni de software. Probablemente, la insuficiente normalización actual la corregiría
pronto el propio mercado. ¿Tiene usted Linux instalado en casa y lo utiliza al menos para conectarse a
Internet por Infovía, o quizá le gustaría hacerlo algún día?. ¿O por el contrario es de los que piensan que Bill
Gates es un genio?. Si se queda con la segunda opción, es que ve mucho la tele, aunque evidentemente tiene
razón: y cuantos más como usted, más genio que será... ;-)
6 de 6 12/10/00 19:05
MICROPROCESADORES 8086/88, 286, 386, 486 y Pentium file:///C|/librosVirtuales/UniversoDigital/03.html
Los microprocesadores Intel 8086 y 8088 se desarrollan a partir de un procesador anterior, el 8080, que,
en sus diversas encarnaciones -incluyendo el Zilog Z-80- ha sido la CPU de 8 bits de mayor éxito.
Poseen una arquitectura interna de 16 bits y pueden trabajar con operandos de 8 y 16 bits; una capacidad
de direccionamiento de 20 bits (hasta 1 Mb) y comparten el mismo juego de instrucciones.
Disponen de 92 tipos de instrucciones, que pueden ejecutar con hasta 7 modos de direccionamiento.
Tienen una capacidad de direccionamiento en puertos de entrada y salida de hasta 64K (65536 puertos), por
lo que las máquinas construidas entorno a estos microprocesadores no suelen emplear la entrada/salida por
mapa de memoria, como veremos.
Entre esas instrucciones, las más rápidas se ejecutan en 2 ciclos teóricos de reloj y unos 9 reales (se trata
del movimiento de datos entre registros internos) y las más lentas en 206 (división entera con signo del
acumulador por una palabra extraída de la memoria). Las frecuencias internas de reloj típicas son 4.77 MHz
en la versión 8086; 8 MHz en la versión 8086-2 y 10 MHz en la 8086-1. Recuérdese que un MHz son un
millón de ciclos de reloj, por lo que un PC estándar a 4,77 MHz puede ejecutar de 20.000 a unos 0,5
millones de instrucciones por segundo, según la complejidad de las mismas (un 486 a 50 MHz, incluso sin
memoria caché externa es capaz de ejecutar entre 1,8 y 30 millones de estas instrucciones por segundo).
El microprocesador Intel 80286 se caracteriza por poseer dos modos de funcionamiento completamente
diferenciados: el modo real en el que se encuentra nada más ser conectado a la corriente y el modo
protegido en el que adquiere capacidad de proceso multitarea y almacenamiento en memoria virtual. El
proceso multitarea consiste en realizar varios procesos de manera aparentemente simultánea, con la ayuda del
sistema operativo para conmutar automáticamente de uno a otro optimizando el uso de la CPU, ya que
mientras un proceso está esperando a que un periférico complete una operación, se puede atender otro
proceso diferente. La memoria virtual permite al ordenador usar más memoria de la que realmente tiene,
almacenando parte de ella en disco: de esta manera, los programas creen tener a su disposición más memoria
de la que realmente existe; cuando acceden a una parte de la memoria lógica que no existe físicamente, se
produce una interrupción y el sistema operativo se encarga de acceder al disco y traerla.
Cuando la CPU está en modo protegido, los programas de usuario tienen un acceso limitado al juego de
instrucciones; sólo el proceso supervisor -normalmente el sistema operativo- está capacitado para realizar
ciertas tareas. Esto es así para evitar que los programas de usuario puedan campar a sus anchas y entrar en
conflictos unos con otros, en materia de recursos como memoria o periféricos. Además, de esta manera,
aunque un error software provoque el cuelgue de un proceso, los demás pueden seguir funcionando
normalmente, y el sistema operativo podría abortar el proceso colgado. Por desgracia, con el DOS el 286 no
está en modo protegido y el cuelgue de un solo proceso -bien el programa principal o una rutina operada por
interrupciones- significa la caída inmediata de todo el sistema.
1 de 10 12/10/00 19:06
MICROPROCESADORES 8086/88, 286, 386, 486 y Pentium file:///C|/librosVirtuales/UniversoDigital/03.html
El 8086 no posee ningún mecanismo para apoyar la multitarea ni la memoria virtual desde el procesador,
por lo que es difícil diseñar un sistema multitarea para el mismo y casi imposible conseguir que sea realmente
operativo. Obviamente, el 286 en modo protegido pierde absolutamente toda la compatibilidad con los
procesadores anteriores. Por ello, en este libro sólo trataremos el modo real, único disponible bajo DOS,
aunque veremos alguna instrucción extra que también se puede emplear en modo real.
Las características generales del 286 son: tiene un bus de datos de 16 bits, un bus de direcciones de 24
bits (16 Mb); posee 25 instrucciones más que el 8086 y admite 8 modos de direccionamiento. En modo
virtual permite direccionar hasta 1 Gigabyte. Las frecuencias de trabajo típicas son de 12 y 16 MHz, aunque
existen versiones a 20 y 25 MHz. Aquí, la instrucción más lenta es la misma que en el caso del 8086, solo que
emplea 29 ciclos de reloj en lugar de 206. Un 286 de categoría media (16 MHz) podría ejecutar más de
medio millón de instrucciones de estas en un segundo, casi 15 veces más que un 8086 medio a 8 MHz. Sin
embargo, transfiriendo datos entre registros la diferencia de un procesador a otro se reduce notablemente,
aunque el 286 es más rápido y no sólo gracias a los MHz adicionales.
Versiones mejoradas de los Intel 8086 y 8088 se encuentran también en los procesadores NEC-V30 y
NEC-V20 respectivamente. Ambos son compatibles Hardware y Software, con la ventaja de que el
procesado de instrucciones está optimizado, llegando a superar casi en tres veces la velocidad de los
originales en algunas instrucciones aritméticas. También poseen una cola de prebúsqueda mayor (cuando el
microprocesador está ejecutando una instrucción, si no hace uso de los buses externos, carga en una cola
FIFO de unos pocos bytes las posiciones posteriores a la que está procesando, de esta forma una vez que
concluye la instrucción en curso ya tiene internamente la que le sigue). Además, los NEC V20 y V30
disponen de las mismas instrucciones adicionales del 286 en modo real, al igual que el 80186 y el 80188.
Por su parte, el 386 dispone de una arquitectura de registros de 32 bits, con un bus de direcciones
también de 32 bits (direcciona hasta 4 Gigabytes = 4096 Mb) y más modos posibles de funcionamiento: el
modo real (compatible 8086), el modo protegido (relativamente compatible con el del 286), un modo
protegido propio que permite -¡por fin!- romper la barrera de los tradicionales segmentos y el modo «virtual
86», en el que puede emular el funcionamiento simultáneo de varios 8086. Una vez más, todos los modos son
incompatibles entre sí y requieren de un sistema operativo específico: si se puede perdonar al fabricante la
pérdida de compatibilidad del modo avanzados del 286 frente al 8086, debido a la lógica evolución
tecnológica, no se puede decir lo mismo del 386 respecto al 286: no hubiera sido necesario añadir un nuevo
modo protegido si hubiera sido mejor construido el del 286 apenas un par de años atrás. Normalmente, los
386 suelen operar en modo real (debido al DOS) por lo que no se aprovechan las posibilidades multitarea ni
de gestión de memoria. Por otra parte, aunque se pueden emplear los registros de 32 bits en modo real, ello
no suele hacerse -para mantener la compatibilidad con procesadores anteriores- con lo que de entrada se está
tirando a la basura un 50% de la capacidad de proceso del chip, aunque por fortuna estos procesadores
suelen trabajar a frecuencias de 16/20 MHz (obsoletas) y normalmente de 33 y hasta 40 MHz.
El 386sx es una variante del 386 a nivel de hardware, aunque es compatible en software. Básicamente, es
un 386 con un bus de datos de sólo 16 bits -más lento, al tener que dar dos pasadas para un dato de 32
bits-. De hecho, podría haber sido diseñado perfectamente para mantener una compatibilidad hardware con
el 286, aunque el fabricante lo evitó probablemente por razones comerciales.
El 486 se diferencia del 386 en la integración en un solo chip del coprocesador 387. También se ha
mejorado la velocidad de operación: la versión de 25 MHz dobla en términos reales a un 386 a 25 MHz
equipado con el mismo tamaño de memoria caché. La versión 486sx no se diferencia en el tamaño del bus,
también de 32 bits, sino en la ausencia del 387 (que puede ser añadido externamente). También existen
versiones de 486 con buses de 16 bits, el primer fabricante de estos chips, denominados 486SLC, ha sido
Cyrix. Una tendencia iniciada por el 486 fue la de duplicar la velocidad del reloj interno (pongamos por caso
de 33 a 66 MHz) aunque en las comunicaciones con los buses exteriores se respeten los 33 MHz. Ello agiliza
la ejecución de las instrucciones más largas: bajo DOS, el rendimiento general del sistema se puede considerar
2 de 10 12/10/00 19:06
MICROPROCESADORES 8086/88, 286, 386, 486 y Pentium file:///C|/librosVirtuales/UniversoDigital/03.html
prácticamente el doble. Son los chips DX2 (también hay una variante a 50 MHz: 25 x 2). La culminación de
esta tecnología viene de la mano de los DX4 a 75/100 MHz (25/33 x 3).
El Pentium, último procesador de Intel en el momento de escribirse estas líneas, se diferencia respecto al
486 en el bus de datos (ahora de 64 bits, lo que agiliza los accesos a memoria) y en un elevadísimo nivel de
optimización y segmentación que le permite, empleando compiladores optimizados, simultanear en muchos
casos la ejecución de dos instrucciones consecutivas. Posee dos cachés internas, tiene capacidad para
predecir el destino de los saltos y la unidad de coma flotante experimenta elevadas mejoras. Sin embargo,
bajo DOS, un Pentium básico sólo es unas 2 veces más rápido que un 486 a la misma frecuencia de reloj.
Comenzó en 60/90 MHz hasta los 166/200/233 MHz de las últimas versiones (Pentium Pro y MMX), que
junto a diversos clones de otros fabricantes, mejoran aún más el rendimiento. Todos los equipos Pentium
emplean las técnicas DX, ya que las placas base típicas corren a 60 MHz. Para hacerse una idea, por unas
200000 pts de 1997 un equipo Pentium MMX a 233 MHz es cerca de 2000 veces más rápido en aritmética
entera que el IBM PC original de inicios de la década de los 80; en coma flotante la diferencia aumenta
incluso algunos órdenes más de magnitud. Y a una fracción del coste (un millón de pts de aquel entonces que
equivale a unos 2,5 millones de hoy en día). Aunque no hay que olvidar la revolución del resto de los
componentes: 100 veces más memoria (central y de vídeo), 200 veces más grande el disco duro... y que un
disco duro moderno transfiere datos 10 veces más deprisa que la memoria de aquel IBM PC original. Por
desgracia, el software no ha mejorado el rendimiento, ni remotamente, en esa proporción: es la factura pasada
por las técnicas de programación cada vez a un nivel más alto (aunque nadie discute sus ventajas).
Una característica de los microprocesadores a partir del 386 es la disponibilidad de memorias caché de
alta velocidad de acceso -muy pocos nanosegundos- que almacenan una pequeña porción de la memoria
principal. Cuando la CPU accede a una posición de memoria, cierta circuitería de control se encarga de ir
depositando el contenido de esa posición y el de las posiciones inmediatamente consecutivas en la memoria
caché. Cuando sea necesario acceder a la instrucción siguiente del programa, ésta ya se encuentra en la caché
y el acceso es muy rápido. Lo ideal sería que toda la memoria del equipo fuera caché, pero esto no es todavía
posible actualmente. Una caché de tamaño razonable puede doblar la velocidad efectiva de proceso de la
CPU. El 8088 carecía de memoria caché, pero sí estaba equipado con una unidad de lectura adelantada de
instrucciones con una cola de prebúsqueda de 4 bytes: de esta manera, se agilizaba ya un tanto la velocidad
de proceso al poder ejecutar una instrucción al mismo tiempo que iba leyendo la siguiente.
Estos procesadores disponen de 14 registros de 16 bits (el 286 alguno más, pero no se suele emplear bajo
DOS). La misión de estos registros es almacenar las posiciones de memoria que van a experimentar repetidas
manipulaciones, ya que los accesos a memoria son mucho más lentos que los accesos a los registros.
Además, hay ciertas operaciones que sólo se pueden realizar sobre los registros. No todos los registros sirven
para almacenar datos, algunos están especializados en apuntar a las direcciones de memoria. La mecánica
básica de funcionamiento de un programa consiste en cargar los registros con datos de la memoria o de un
puerto de E/S, procesar los datos y devolver el resultado a la memoria o a otro puerto de E/S. Obviamente,
si un dato sólo va a experimentar un cambio, es preferible realizar la operación directamente sobre la
memoria, si ello es posible. A continuación se describen los registros del 8086.
AX SP CS IP
BX BP DS flags
CX SI SS
DX DI ES
Registros Registro puntero
Registros Registros de
punteros de de instrucciones
de datos segmento
pila e índices y flags
3 de 10 12/10/00 19:06
MICROPROCESADORES 8086/88, 286, 386, 486 y Pentium file:///C|/librosVirtuales/UniversoDigital/03.html
- Registros de datos:
AX, BX, CX, DX: pueden utilizarse bien como registros de 16 bits o como dos registros separados de
8 bits (byte superior e inferior) cambiando la X por H o L según queramos referirnos a la parte alta o baja
respectivamente. Por ejemplo, AX se descompone en AH (parte alta) y AL (parte baja). Evidentemente,
¡cualquier cambio sobre AH o AL altera AX!: valga como ejemplo que al incrementar AH se le están
añadiendo 256 unidades a AX.
AX = Acumulador.
BX = Base.
Se usa como registro base para referenciar direcciones de memoria con direccionamiento indirecto,
manteniendo la dirección de la base o comienzo de tablas o matrices. De esta manera, no es preciso indicar
una posición de memoria fija, sino la número BX (así, haciendo avanzar de unidad en unidad a BX, por
ejemplo, se puede ir accediendo a un gran bloque de memoria en un bucle).
CX = Contador.
Se utiliza comúnmente como contador en bucles y operaciones repetitivas de manejo de cadenas. En
las instrucciones de desplazamiento y rotación se utiliza como contador de 8 bits.
DX = Datos.
Usado en conjunción con AX en las operaciones de multiplicación y división que involucran o generan
datos de 32 bits. En las de entrada y salida se emplea para especificar la dirección del puerto E/S.
- Registros de segmento:
Definen áreas de 64 Kb dentro del espacio de direcciones de 1 Mb del 8086. Estas áreas pueden
solaparse total o parcialmente. No es posible acceder a una posición de memoria no definida por algún
segmento: si es preciso, habrá de moverse alguno.
4 de 10 12/10/00 19:06
MICROPROCESADORES 8086/88, 286, 386, 486 y Pentium file:///C|/librosVirtuales/UniversoDigital/03.html
- Registros índices:
Es un registro de 16 bits de los cuales 9 son utilizados para indicar diversas situaciones durante la
ejecución de un programa. Los bits 0, 2, 4, 6, 7 y 11 son indicadores de condición, que reflejan los resultados
de operaciones del programa; los bits del 8 al 10 son indicadores de control y el resto no se utilizan. Estos
indicadores pueden ser comprobados por las instrucciones de salto condicional, lo que permite variar el flujo
secuencial del programa según el resultado de las operaciones.
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
OF DF IF TF SF ZF AF PF CF
CF (Carry Flag): Indicador de acarreo. Su valor más habitual es lo que nos llevamos en una suma o
resta.
OF (Overflow Flag): Indicador de desbordamiento. Indica que el resultado de una operación no cabe
en el tamaño del operando destino.
ZF (Zero Flag): Indicador de resultado 0 o comparación igual.
SF (Sign Flag): Indicador de resultado o comparación negativa.
PF (Parity Flag): Indicador de paridad. Se activa tras algunas operaciones aritmético-lógicas para
indicar que el número de bits a uno resultante es par.
AF (Auxiliary Flag): Para ajuste en operaciones BCD.
DF (Direction Flag): Indicador de dirección. Manipulando bloques de memoria, indica el sentido de
avance (ascendente/descendente).
5 de 10 12/10/00 19:06
MICROPROCESADORES 8086/88, 286, 386, 486 y Pentium file:///C|/librosVirtuales/UniversoDigital/03.html
Los 386 y superiores disponen de muchos más registros de los que vamos a ver ahora. Sin embargo, bajo
el sistema operativo DOS sólo se suelen emplear los que veremos, que constituyen básicamente una extensión
a 32 bits de los registros originales del 8086.
Se amplía el tamaño de los registros de datos (que pueden ser accedidos en fragmentos de 8, 16 ó 32
bits) y se añaden dos nuevos registros de segmento multipropósito (FS y GS). Algunos de los registros aquí
mostrados son realmente de 32 bits (como EIP en vez de IP), pero bajo sistema operativo DOS no pueden
ser empleados de manera directa, por lo que no les consideraremos.
Son los distintos modos de acceder a los datos en memoria por parte del procesador. Antes de ver los
modos de direccionamiento, echaremos un vistazo a la sintaxis general de las instrucciones, ya que pondremos
alguna en los ejemplos:
Donde destino indica dónde se deja el resultado de la operación en la que pueden participar (según casos)
FUENTE e incluso el propio DESTINO. Hay instrucciones, sin embargo, que sólo tienen un operando, como
la siguiente, e incluso ninguno:
INSTRUCCIÓN DESTINO
Como ejemplos, aunque no hemos visto aún las instrucciones utilizaremos un par de ellas: la de copia o
movimiento de datos (MOV) y la de suma (ADD).
Como ya sabemos, los microprocesadores 8086 y compatibles poseen registros de un tamaño máximo de
16 bits que direccionarían hasta 64K; en cambio, la dirección se compone de 20 bits con capacidad para
1Mb, hay por tanto que recurrir a algún artificio para direccionar toda la memoria. Dicho artificio consiste en
la segmentación: se trata de dividir la memoria en grupos de 64K. Cada grupo se asocia con un registro de
segmento; el desplazamiento (offset) dentro de ese segmento lo proporciona otro registro de 16 bits. La
dirección absoluta se calcula multiplicando por 16 el valor del registro de segmento y sumando el offset,
obteniéndose una dirección efectiva de 20 bits. Esto equivale a concebir el mecanismo de generación de la
dirección absoluta, como si se tratase de que los registros de segmento tuvieran 4 bits a 0 (imaginarios) a la
derecha antes de sumarles el desplazamiento:
6 de 10 12/10/00 19:06
MICROPROCESADORES 8086/88, 286, 386, 486 y Pentium file:///C|/librosVirtuales/UniversoDigital/03.html
En la práctica, una dirección se indica con la notación SEGMENTO:OFFSET; además, una misma
dirección puede expresarse de más de una manera: por ejemplo, 3D00h:0300h es equivalente a 3D30:0000h.
Es importante resaltar que no se puede acceder a más de 64 Kb en un segmento de datos. Por ello, en los
procesadores 386 y superiores no se deben emplear registros de 32 bit para generar direcciones (bajo DOS),
aunque para los cálculos pueden ser interesantes (no obstante, sí sería posible configurar estos procesadores
para poder direccionar más memoria bajo DOS con los registros de 32 bits, aunque no resulta por lo general
práctico).
- Direccionamiento inmediato: El operando es una constante situada detrás del código de la instrucción.
Sin embargo, como registro destino no se puede indicar uno de segmento (habrá que utilizar uno de datos
como paso intermedio).
ADD AX,0fffh
Porque hay que tener en cuenta que cuando traduzcamos a números el símbolo podría quedar:
17F3:0A11 DW FFF
MOV AX,0A11
- Direccionamiento de registro: Los operandos, necesariamente de igual tamaño, están contenidos en los
registros indicados en la instrucción:
MOV DX,AX
MOV AH,AL
MOV AX,[57D1h]
MOV AX,ES:[429Ch]
Esta sintaxis (quitando la 'h' de hexadecimal) sería la que admite el programa DEBUG (realmente habría
que poner, en el segundo caso, ES: en una línea y el MOV en otra). Al trabajar con ensambladores, las
variables en memoria se pueden referenciar con etiquetas simbólicas:
MOV AX,dato
MOV AX,ES:dato
7 de 10 12/10/00 19:06
MICROPROCESADORES 8086/88, 286, 386, 486 y Pentium file:///C|/librosVirtuales/UniversoDigital/03.html
- Indirecto con índice o indexado: El operando se encuentra en una dirección determinada por la suma de
un registro de segmento*16, un registro de índice, SI o DI y un desplazamiento de 8 ó 16 bits. Ejemplos:
- Indirecto con base e índice o indexado a base: El operando se encuentra en una dirección especificada
por la suma de un registro de segmento*16, uno de base, uno de índice y opcionalmente un desplazamiento
de 8 ó 16 bits:
Como se ve en los modos de direccionamiento, hay casos en los que se indica explícitamente el registro de
segmento a usar para acceder a los datos. Existen unos segmentos asociados por defecto a los registros de
desplazamiento (IP, SP, BP, BX, DI, SI); sólo es necesario declarar el segmento cuando no coincide con el
asignado por defecto. En ese caso, el ensamblador genera un byte adicional (a modo de prefijo) para indicar
cuál es el segmento referenciado. La siguiente tabla relaciona las posibles combinaciones de los registros de
segmento y los de desplazamiento:
CS SS DS ES
IP Sí No No No
SP No Sí No No
BP con prefijo por defecto con prefijo con prefijo
BX con prefijo con prefijo por defecto con prefijo
SI con prefijo con prefijo por defecto con prefijo
DI con prefijo con prefijo por defecto con prefijo(1)
(1) También por defecto en el manejo de cadenas.
Los 386 y superiores admiten otros modos de direccionamiento más sofisticados, que se verán en el
próximo capítulo, después de conocer todas las instrucciones del 8086. Por ahora, con todos estos modos se
puede considerar que hay más que suficiente. De hecho, algunos se utilizan en muy contadas ocasiones.
3.5. - LA PILA.
La pila es un bloque de memoria de estructura LIFO (Last Input First Output: último en entrar, primero en
8 de 10 12/10/00 19:06
MICROPROCESADORES 8086/88, 286, 386, 486 y Pentium file:///C|/librosVirtuales/UniversoDigital/03.html
salir) que se direcciona mediante desplazamientos desde el registro SS (segmento de pila). Las posiciones
individuales dentro de la pila se calculan sumando al contenido del segmento de pila SS un desplazamiento
contenido en el registro puntero de pila SP. Todos los datos que se almacenan en la pila son de longitud
palabra, y cada vez que se introduce algo en ella por medio de las instrucciones de manejo de pila (PUSH y
POP), el puntero se decrementa en dos; es decir, la pila avanza hacia direcciones decrecientes. El registro BP
suele utilizarse normalmente para apuntar a una cierta posición de la pila y acceder indexadamente a sus
elementos -generalmente en el caso de variables- sin necesidad de desapilarlos para consultarlos.
La pila es utilizada frecuentemente al principio de una subrutina para preservar los registros que no se
desean modificar; al final de la subrutina basta con recuperarlos en orden inverso al que fueron depositados.
En estas operaciones conviene tener cuidado, ya que la pila en los 8086 es común al procesador y al usuario,
por lo que se almacenan en ella también las direcciones de retorno de las subrutinas. Esta última es, de hecho,
la más importante de sus funciones. La estructura de pila permite que unas subrutinas llamen a otras que a su
vez pueden llamar a otras y así sucesivamente: en la pila se almacenan las direcciones de retorno, que serán
las de la siguiente instrucción que provocó la llamada a la subrutina. Así, al retornar de la subrutina se extrae
de la pila la dirección a donde volver. Los compiladores de los lenguajes de alto nivel la emplean también para
pasar los parámetros de los procedimientos y para generar en ella las variables automáticas -variables
locales que existen durante la ejecución del subprograma y se destruyen inmediatamente después-. Por ello,
una norma básica es que se debe desapilar siempre todo lo apilado para evitar una pérdida de control
inmediata del ordenador.
Aunque las instrucciones del procesador no serán vistas hasta el próximo capítulo, con objeto de ayudar a
la imaginación del lector elaboraremos un primer programa de ejemplo en lenguaje ensamblador. La utilidad
de este programa es dejar patente que lo único que entiende el 8086 son números, aunque nosotros nos
referiremos a ellos con unos símbolos que faciliten entenderlos. También es interesante este ejemplo para
afianzar el concepto de registro de segmento.
En este programa sólo vamos a emplear las instrucciones MOV, ya conocida, y alguna otra más como la
instrucción INC (incrementar), DEC (disminuir una unidad) y JNZ (saltar si el resultado no es cero).
Suponemos que el programa está ubicado a partir de la dirección de memoria 14D3:7A10 (arbitrariamente
elegida) y que lo que pretendemos hacer con él es limpiar la pantalla. Como el ordenador es un PC con
monitor en color, la pantalla de texto comienza en B800:0000 (no es más que una zona de memoria). Por
cada carácter que hay en dicha pantalla, comenzando arriba a la izquierda, a partir de la dirección B800:0000
tenemos dos bytes: el primero, con el código ASCII del carácter y el segundo con el color. Lo que vamos a
hacer es rellenar los 2000 caracteres (80 columnas x 25 líneas) con espacios en blanco (código ASCII 32, ó
20h en hexadecimal), sin modificar el color que hubiera antes. Esto es, se trata de poner el valor 32 en la
dirección B800:0000, la B800:0002, la B800:0004... y así sucesivamente.
El programa quedaría en memoria de esta manera: La primera columna indica la dirección de memoria
donde está el programa que se ejecuta (CS=14D3h e IP=7A10h al principio). La segunda columna constituye
el código máquina que interpreta el 8086. Algunas instrucciones ocupan un byte de memoria, otras dos ó tres
9 de 10 12/10/00 19:06
MICROPROCESADORES 8086/88, 286, 386, 486 y Pentium file:///C|/librosVirtuales/UniversoDigital/03.html
(las hay de más). La tercera columna contiene el nombre de las instrucciones, algo mucho más legible para los
humanos que los números:
Como se puede ver, la segunda instrucción (bytes de código máquina 0B8h, 0 y 0B8h colocados en
posiciones consecutivas) está colocada a partir del desplazamiento 7A13h, ya que la anterior que ocupaba 3
bytes comenzaba en 7A10h. En el ejemplo cargamos el valor 0B800h en DS apoyándonos en AX como
intermediario. El motivo es que los registros de segmento no admiten el direccionamiento inmediato. A medida
que se van haciendo programas, el ensamblador da mensajes de error cuando se encuentra con estos fallos y
permite ir aprendiendo con facilidad las normas, que tampoco son demasiadas. La instrucción MOV BYTE
PTR [BX],32 equivale a decir: «poner en la dirección de memoria apuntada por BX (DS:[BX] para ser
más exactos) el byte de valor 32». El valor 0F8h del código máquina de la última instrucción es el
complemento a dos (número negativo) del valor 8.
Normalmente, casi nunca habrá que ensamblar a mano consultando unas tablas, como hemos hecho en
este ejemplo. Sin embargo, la mejor manera de aprender ensamblador es no olvidando la estrecha relación de
cada línea de programa con la CPU y la memoria.
10 de 10 12/10/00 19:06
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Nota: en el efecto de las instrucciones sobre el registro de estado se utilizará la siguiente notación:
- bit no modificado
? desconocido o indefinido
x modificado según el resultado de la operación
1 puesto siempre a 1
0 puesto siempre a 0
MOV (transferencia)
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Transfiere datos de longitud byte o palabra del operando origen al operando destino. Pueden
ser operando origen y operando destino cualquier registro o posición de memoria direccionada de las
formas ya vistas, con la única condición de que origen y destino tengan la misma dimensión. Existen
ciertas limitaciones, como que los registros de segmento no admiten el direccionamiento inmediato: es
incorrecto MOV DS,4000h; pero no lo es por ejemplo MOV DS,AX o MOV DS,VARIABLE. No
es posible, así mismo, utilizar CS como destino (es incorrecto hacer MOV CS,AX aunque pueda
admitirlo algún ensamblador). Al hacer MOV hacia un registro de segmento, las interrupciones quedan
inhibidas hasta después de ejecutarse la siguiente instrucción (8086/88 de 1983 y procesadores
posteriores).
XCHG (intercambiar)
Indicadores: OF DF IF TF SF ZF AF PF CF
1 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
- - - - - - - - -
XLAT (traducción)
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Se utiliza para traducir un byte del registro AL a un byte tomado de la tabla de traducción.
Los datos se toman desde una dirección de la tabla correspondiente a BX + AL, donde bx es un
puntero a el comienzo de la tabla y AL es un índice. Indicar tabla al lado de xlat es sólo una
redundancia opcional.
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
lea dx,datos[si]
2 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Sintaxis: LAHF
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Carga los bits 7, 6, 4, 2 y 0 del registro AH con el contenido de los indicadores SF, ZF, AF,
PF Y CF respectivamente. El contenido de los demás bits queda sin definir.
Sintaxis: SAHF
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - x x x x x
3 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Sintaxis: CLC
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - 0
Sintaxis: CLD
Indicadores: OF DF IF TF SF ZF AF PF CF
- 0 - - - - - - -
Pone a 0 el indicador de dirección DF, por lo que los registros SI y/o DI se autoincrementan
en las operaciones de cadenas, sin afectar al resto de los indicadores. Es NECESARIO colocarlo
antes de las instrucciones de manejo de cadenas si no se conoce con seguridad el valor de DF. Véase
STD.
Sintaxis: CLI
Indicadores: OF DF IF TF SF ZF AF PF CF
- - 0 - - - - - -
Sintaxis: CMC
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - x
Sintaxis: STC
4 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - 1
Sintaxis: STD
Indicadores: OF DF IF TF SF ZF AF PF CF
- 1 - - - - - - -
Sintaxis: STI
Indicadores: OF DF IF TF SF ZF AF PF CF
- - 1 - - - - - -
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Transfiere el elemento palabra que se encuentra en lo alto de la pila (apuntado por SP) al
operando destino que a de ser tipo palabra, e incrementa en dos el registro SP. La instrucción POP
CS, poco útil, no funciona correctamente en los 286 y superiores.
Ejemplos: pop ax
pop pepe
5 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Ejemplo: push cs
Sintaxis: POPF
Indicadores: OF DF IF TF SF ZF AF PF CF
x x x x x x x x x
Sintaxis: PUSHF
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Incondicional
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
6 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
o lejana, se almacena en la pila una dirección de retorno de 16 bits o dos palabras de 16 bits indicando
en este último caso tanto el offset (IP) como el segmento (CS) a donde volver.
dir dd 0f000e987h
call dword ptr dir
En el segundo ejemplo, la variable dir almacena la dirección a donde saltar. De esta última
manera -conociendo su dirección- puede llamarse también a un vector de interrupción, guardando
previamente los flags en la pila (PUSHF), porque la rutina de interrupción retornará (con IRET en vez
de con RETF) sacándolos.
JMP (salto)
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
7 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Condicional
Gestión de bucle
LOOP (bucle)
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
mov cx,10
bucle: .......
.......
loop bucle
8 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Interrupciones
INT (interrupción)
Indicadores: OF DF IF TF SF ZF AF PF CF
- - 0 0 - - - - -
Sintaxis: INTO
Indicadores: OF DF IF TF SF ZF AF PF CF
- - 0 0 - - - - -
Sintaxis: IRET
Indicadores: OF DF IF TF SF ZF AF PF CF
x x x x x x x x x
Devuelve el control a la dirección de retorno salvada en la pila por una interrupción previa y
restaura los indicadores que también se introdujeron en la pila. En total, se sacan las 3 palabras que
fueron colocadas en la pila cuando se produjo la interrupción. Véase también INT.
9 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
IN (entrada)
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Ejemplo: in ax,0fh
in al,dx
OUT (salida)
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Sintaxis: AAA
Indicadores: OF DF IF TF SF ZF AF PF CF
? - - - ? ? x ? x
10 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x x
Suma los operandos origen, destino y el valor del indicador de acarreo (0 ó 1) y el resultado
lo almacena en el operando destino. Se utiliza normalmente para sumar números grandes, de más de 16
bits, en varios pasos, considerando lo que nos llevamos (el acarreo) de la suma anterior.
ADD (suma)
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x x
Sintaxis: DAA
Indicadores: OF DF IF TF SF ZF AF PF CF
? - - - x x x x x
Convierte el contenido del registro AL en un par de valores BCD: si los cuatro bits menos
significativos de AL son un número mayor que 9, el indicador AF se pone a 1 y se suma 6 a AL. De
igual forma, si los cuatro bits más significativos de AL tras la operación anterior son un número mayor
que 9, el indicador CF se pone a 1 y se suma 60h a AL.
INC (incrementar)
11 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x -
Ejemplos: inc al
inc es:[di]
inc ss:[bp+4]
inc word ptr cs:[bx+di+7]
***RESTA***
Sintaxis: AAS
Indicadores: OF DF IF TF SF ZF AF PF CF
? - - - ? ? x ? x
CMP (comparación)
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x x
Resta origen de destino sin retornar ningún resultado. Los operandos quedan inalterados,
paro los indicadores pueden ser consultados mediante instrucciones de bifurcación condicional. Los
operandos pueden ser de tipo byte o palabra pero ambos de la misma dimensión.
Sintaxis: DAS
Indicadores: OF DF IF TF SF ZF AF PF CF
12 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
- - - - x x x x x
DEC (decrementar)
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x -
Resta una unidad del operando destino. El operando puede ser byte o palabra. Obsérvese
que esta instrucción no modifica el bit de acarreo (CF) y no es posible detectar un desbordamiento por
este procedimiento (utilícese ZF).
Ejemplo: dec ax
dec mem_byte
NEG (negación)
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x x
Ejemplo: neg al
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x x
13 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
varios pasos, considerando lo que nos llevamos (el acarreo) de la resta anterior.
SUB (resta)
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x x
Sintaxis: AAM
Indicadores: OF DF IF TF SF ZF AF PF CF
? - - - x x ? x ?
Ejemplo: mul bl
aam
Sintaxis: IMUL origen (origen no puede ser operando inmediato en 8086, sí en 286)
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - ? ? ? ? x
14 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Ejemplo: imul bx
imul ch
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - ? ? ? ? x
Sintaxis: AAD
Indicadores: OF DF IF TF SF ZF AF PF CF
? - - - x x ? x ?
Ejemplo: aad
div bl
En el ejemplo, tras convertir los dos números BCD no empaquetados (en AX) en un
dividendo válido, la instrucción de dividir genera un resultado correcto.
Indicadores: OF DF IF TF SF ZF AF PF CF
? - - - ? ? ? ? ?
15 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
respectivamente. DX o AH deben ser cero antes de la operación. Cuando el cociente es mayor que el
resultado máximo que puede almacenar, cociente y resto quedan indefinidos produciéndose una
interrupción 0. En caso de que las partes más significativas del cociente tengan un valor distinto de cero
se activan los indicadores CF y OF.
Ejemplo: div bl
div mem_pal
Indicadores: OF DF IF TF SF ZF AF PF CF
? - - - ? ? ? ? ?
Ejemplo: idiv bl
idiv bx
*** CONVERSIONES***
Sintaxis: CBW
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Copia el bit 7 del registro AL en todos los bits del registro AH, es decir, expande el signo de
AL a AX como paso previo a una operación de 16 bits.
Sintaxis: CWD
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Expande el signo del registro AX sobre el registro DX, copiando el bit más significativo de
AH en todo DX.
16 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x x
Compara dos cadenas restando al origen el destino. Ninguno de los operandos se alteran,
pero los indicadores resultan afectados. La cadena origen se direcciona con registro SI sobre el
segmento de datos DS y la cadena destino se direcciona con el registro DI sobre el segmento extra ES.
Los registros DI y SI se autoincrementan o autodecrementan según el valor del indicador DF (véanse
CLD y STD) en una o dos unidades, dependiendo de si se trabaja con bytes o con palabras. Cadena
origen y cadena destino son dos operandos redundantes que sólo indican el tipo del dato (byte o
palabra) a comparar, es más cómodo colocar CMPSB o CMPSW para indicar bytes/palabras. Si se
indica un registro de segmento, éste sustituirá en la cadena origen al DS ordinario. Ejemplo:
lea si,origen
lea di,destino
cmpsb
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Ejemplo: cld
lea si,origen
lodsb
Indicadores: OF DF IF TF SF ZF AF PF CF
17 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
- - - - - - - - -
Transfiere un byte o una palabra de la cadena origen direccionada por DS:SI a la cadena
destino direccionada por ES:DI, incrementando o decrementando a continuación los registros SI y DI
según el valor de DF (véanse CLD y STD) en una o dos unidades, dependiendo de si se trabaja con
bytes o con palabras. Cadena origen y cadena destino son dos operandos redundantes que sólo
indican el tipo del dato (byte o palabra) a comparar, es más cómodo colocar MOVSB o MOVSW
para indicar bytes/palabras. Si se indica un registro de segmento, éste sustituirá en la cadena origen al
DS ordinario.
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x x x x
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
18 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
stosw
REP/REPE/REPZ/REPNE/REPNZ (repetir)
Ejemplos:
1) Buscar el byte 69 entre las 200 primeras posiciones de tabla (se supone tabla en el
segmento ES):
LEA DI,tabla
MOV CX,200
MOV AL,69
CLD
REPNE SCASB
JE encontrado
2) Rellenar de ceros 5000 bytes de una tabla colocada en datos (se supone datos en el
segmento ES):
LEA DI,datos
MOV AX,0
MOV CX,2500
CLD
REP STOSW
19 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
AND (y lógico)
Indicadores: OF DF IF TF SF ZF AF PF CF
0 - - - x x ? x 0
Realiza una operación de Y lógico entre el operando origen y destino quedando el resultado
en el destino. Son válidos operandos byte o palabra, pero ambos del mismo tipo.
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Realiza el complemento a uno del operando destino, invirtiendo cada uno de sus bits. Los
indicadores no resultan afectados.
Ejemplo: not ax
OR (O lógico)
Indicadores: OF DF IF TF SF ZF AF PF CF
0 - - - x x ? x 0
Realiza una operación O lógico a nivel de bits entre los dos operandos, almacenándose
después el resultado en el operando destino.
Ejemplo: or ax,bx
Indicadores: OF DF IF TF SF ZF AF PF CF
0 - - - x x ? x 0
Realiza una operación Y lógica entre los dos operandos pero sin almacenar el resultado. Los
indicadores son afectados con la operación.
20 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
XOR (O exclusivo)
Indicadores: OF DF IF TF SF ZF AF PF CF
0 - - - x x ? x 0
Operación OR exclusivo a nivel de bits entre los operandos origen y destino almacenándose
el resultado en este último.
Sintaxis: NOP
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Sintaxis: HLT
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
21 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
El procesador se detiene hasta que se restaura el sistema o se recibe una interrupción. Como
en los PC se producen normalmente 18,2 interrupciones de tipo 8 por segundo (del temporizador)
algunos programadores utilizan HLT para hacer pausas y bucles de retardo. Sin embargo, el método no
es preciso y puede fallar con ciertos controladores de memoria.
Sintaxis: LOCK
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Es una instrucción que se utiliza en aplicaciones de recursos compartidos para asegurar que
no accede simultáneamente a la memoria más de un procesador. Cuando una instrucción va precedida
por LOCK, el procesador bloquea inmediatamente el bus, introduciendo una señal por la patilla
LOCK.
WAIT (espera)
Sintaxis: WAIT
Indicadores: OF DF IF TF SF ZF AF PF CF
- - - - - - - - -
Provoca la espera del procesador hasta que se detecta una señal en la patilla TEST. Ocurre,
por ejemplo, cuando el copro ha terminado una operación e indica su finalización. Suele preceder a
ESC para sincronizar las acciones del procesador y coprocesador.
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - - - - - x
Rotar a la izquierda los bits del operando destino junto con el indicador de acarreo CF el
número de bits especificado en el segundo operando. Si el número de bits a desplazar es 1, se puede
especificar directamente, en caso contrario el valor debe cargarse en CL y especificar CL como
segundo operando. No es conveniente que CL sea mayor de 7, en bytes; ó 15, en palabras.
22 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - - - - - x
Rotar a la derecha los bits del operando destino junto con el indicador de acarreo CF el
número de bits especificado en el segundo operando. Si el número de bits es 1 se puede especificar
directamente; en caso contrario su valor debe cargarse en CL y especificar CL como segundo
operando:
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - - - - - x
Rota a la izquierda los bits del operando destino el número de bits especificado en el segundo
operando, que puede ser 1 ó CL previamente cargado con el valor del número de veces.
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - - - - - x
Rota a la derecha los bits del operando destino el número de bits especificado en el segundo
operando. Si el número de bits es 1 se puede poner directamente, en caso contrario debe ponerse a
través de CL.
23 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
ror ax,cl
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x ? x x
Desplaza a la izquierda los bits del operando el número de bits especificado en el segundo
operando que debe ser CL si es mayor que 1 los bits desplazados.
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x ? x x
Desplaza a la derecha los bits del operando destino el número de bits especificado en el
segundo operando. Los bits de la izquierda se rellenan con el bit de signo del primer operando. Si el
número de bits a desplazar es 1 se puede especificar directamente, si es mayor se especifica a través
de CL.
Indicadores: OF DF IF TF SF ZF AF PF CF
x - - - x x ? x x
Desplaza a la derecha los bits del operando destino el número de los bits especificados en el
segundo operando. Los bits de la izquierda se llena con cero. Si el número de bits a desplazar es 1 se
puede especificar directamente en el caso en que no ocurra se pone el valor en CL:
24 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
25 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
OR dst,fnt OR dst,fnt 0 - - - x x ? x 0
OUT port,acum OUT port,acum - - - - - - - - -
POP dst POP dst - - - - - - - - -
POPF POPF x x x x x x x x x
PUSH dst PUSH dst - - - - - - - - -
PUSHF PUSHF - - - - - - - - -
RCL dst,cnt RCL dst,cnt x - - - - - - - x
RCR dst,cnt RCR dst,cnt x - - - - - - - x
REP/REPE/REPZ/
REPNE/REPNZ REP - - - - - - - - -
RET [val] RET [val] - - - - - - - - -
RETF [val] RETF [val] - - - - - - - - -
ROL dst,cnt ROL dst,cnt x - - - - - - - x
ROR dst,cnt ROR dst,cnt x - - - - - - - x
SAHF SAHF - - - - x x x x x
SAL/SHL dst,cnt SAL dst,cnt x - - - x x ? x x
SAR dst,cnt SAR dst,cnt x - - - x x ? x x
SBB dst,fnt SBB dst,fnt x - - - x x x x x
SCAS/SCASB/
SCASW cdst SCAS cdst x - - - x x x x x
SHR dst,cnt SHR dst,cnt x - - - x x ? x x
STC STC - - - - - - - - 1
STD STD - 1 - - - - - - -
STI STI - - 1 - - - - - -
STOS/STOSB/
STOSW cdst STOS cdst - - - - - - - - -
SUB dst,fnt SUB dst,fnt x - - - x x x x x
TEST dst,fnt TEST dst,fnt 0 - - - x x ? x 0
WAIT WAIT - - - - - - - - -
XCHG dst,fnt XCHG dst,fnt - - - - - - - - -
XLAT tfnt XLAT tfnt - - - - - - - - -
XOR dst,fnt XOR dst,fnt 0 - - - x x ? x 0
- Excepciones de división:
Las excepciones INT 0, debidas a una división por cero o a un cociente excesivamente grande,
provocan que en la pila se almacene el valor de CS:IP para la siguiente instrucción en el 8086. En el
286 y superiores se almacena el CS:IP de la propia instrucción que causa la excepción.
- Desplazamientos y rotaciones.
El valor de desplazamiento en las operaciones de manipulación de bits del 8086 es una
constante de 8 bits (indicada en CL); en el 286 y superiores se toma módulo 32 (sólo se consideran los
5 bits menos significativos).
- Prefijos redundantes.
26 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Las instrucciones tienen una longitud ilimitada en el 8086; en el 286 y superiores no pueden
exceder de 15 bytes. Por tanto, los prefijos redundantes pueden producir excepciones de código de
operación no válido.
- LOCK.
Esta instrucción no está limitada de ninguna manera en el 8086 y en el 286. En el 386 y
superiores su uso está restringido a determinadas instrucciones.
- Registro de FLAGS.
Difiere algo en los bits 12 al 15 en todos los procesadores; el 386 dispone además de un
registro de flags de 32 bits.
- Interrupción NMI.
Desde el 286 y superiores, una NMI no puede interrumpir una rutina de tratamiento NMI.
A continuación se describen las instrucciones adicionales que incorporan los 286 en modo real, que
también pueden ser consideradas cuando trabajamos con los microprocesadores compatibles V20 y
V30, así como con los procesadores superiores al 286. Las instrucciones del modo protegido se
dirigen especialmente a la multiprogramación y el tiempo compartido, siendo específicas de la
conmutación de procesos y tratamiento de la memoria virtual y no pueden emplearse directamente bajo
DOS.
BOUND r16, mem16: Comprueba si el registro de 16 bits indicado como primer operando está
27 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
dentro de los límites de una matriz. Los límites de la matriz los definen dos palabras consecutivas en la
memoria apuntadas por mem16. Si está fuera de los límites, se produce una interrupción 5 en la que el
IP apilado queda apuntando a la instrucción BOUND (¡no se incrementa!).
Las instrucciones PUSH permiten meter valores inmediatos a la pila: es válido hacer PUSH 40h.
IMUL puede multiplicar cualquier registro de 16 bits por una constante inmediata, devolviendo un
resultado palabra (CF=1 si no cabe en 16 bits); por ejemplo, es válido IMUL CX,25. También se
admiten tres operandos: IMUL r1, r2, imm. En este caso, se multiplica r2 por el valor inmediato
(8/16 bits) y el resultado se almacena en r1. Tanto r1 como r2 han de ser de 16 bits.
LEAVE abandona los procedimientos de alto nivel (equivale a MOV SP,BP / POP BP).
PUSHA/POPA: Introduce en la pila y en este orden los registros AX, CX, DX, BX, SP, BP, SI y DI
-o los saca en orden inverso-. Ideal en el manejo de interrupciones y muy usada en las BIOS de 286 y
386.
OUTS (salida de cadenas) e INS (entrada de cadenas) repetitivas (equivalente a MOVS y LODS).
Además de todas las posibilidades adicionales del 286, el 386 y el 486 permiten utilizar cualquier
registro de 32 bits de propósito general en todos los modos de funcionamiento, incluido el modo real,
tales como EAX, EBX, ECX, EDX, ESI, EDI, EBP. Sin embargo no deben intentarse
direccionamientos por encima de los 64K. En otras palabras, se pueden utilizar para acelerar las
operaciones pero no para acceder a más memoria. Por ejemplo, si EBX > 0FFFFh, la instrucción
MOV AX,[EBX] tendría un resultado impredecible. Además, estos procesadores cuentan con dos
segmentos más: además de DS, ES, CS y SS se pueden emplear también FS y GS. Aviso: parece ser
que en algunos 386 fallan ocasionalmente las instrucciones de multiplicar de 32 bits.
Nota: No es del todo cierto que el 386 y el 486 no permitan acceder a más de 64 Kb
en modo real: en la sección 4.3.6 hay un ejemplo de ello.
Los modos de direccionamiento aumentan notablemente su flexibilidad en el 386 y superiores. Con los
registros de 16 bits sólo están disponibles los modos tradicionales. En cambio, con los de 32 se puede
utilizar en el direccionamiento indirecto cualquier registro: es válida, por ejemplo, una instrucción del
tipo MOV AX,[ECX] o MOV EDX,[EAX]. Los desplazamientos en el direccionamiento indexado
con registros de 32 bits pueden ser de 8 y también de 32 bits. Cuando dos registros deben sumarse
para calcular la dirección efectiva, el segundo puede estar multiplicado por 2, 4 u 8; por ejemplo, es
válida la instrucción MOV AL,[EDX+EAX*8]. Por supuesto, bajo DOS hay que asegurarse siempre
que el resultado de todas las operaciones que determinan la dirección efectiva no excede de 0FFFFh
(0FFFEh si se accede a palabras y 0FFFCh en accesos a dobles palabras en memoria).
28 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Donde reg puede ser de 16 ó 32 bits. Se comienza a explorar por el bit 0 (BSF) o por el más
significativo (BSR) del segundo operando: si no aparece ningún bit activo (a 1) el indicador ZF se
activa; en caso contrario se almacena en el primer operando la posición relativa de ese bit:
MOV AX,8
BSF BX,AX
JZ ax_es_0 ; no se saltará, además BX = 3
MOV AX,16
BTC AX,4 ; resultado: CF = 1 y AX = 0
CMPSD: Similar a CMPSW pero empleando ESI, EDI, ECX y comparando datos de 32 bits. Se
puede emplear bajo DOS siempre que ESI y EDI (utilizando REP también ECX) no excedan de
0FFFFh.
INSD: Similar a INSW pero empleando ESI, EDI, ECX y leyendo datos de 32 bits. Se puede
emplear bajo DOS siempre que ESI y EDI (utilizando REP también ECX) no excedan de 0FFFFh.
Jcc: Los saltos condicionales ahora pueden ser de ¡32 bits!. Mucho cuidado con la directiva .386 en
los programas en que se desee mantener la compatibilidad con procesadores anteriores. JECXZ se
utiliza en vez de JCXZ (mismo código de operación).
LODSD: Similar a LODSW pero empleando ESI, EDI y ECX y cargando datos de 32 bits en EAX.
Se puede emplear bajo DOS siempre que ESI y EDI (utilizando REP también ECX) no excedan de
0FFFFh.
LSS, LFS, LGS: similar a LDS o LES pero con esos registros de segmento.
MOV CRx,reg / MOV DRx,reg y los recíprocos: acceso a registros de control y depuración.
MOVSD: Similar a MOVSW pero empleando ESI, EDI, ECX y moviendo datos de 32 bits. Se
29 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
puede emplear bajo DOS para acelerar las transferencias siempre que ESI y EDI (utilizando REP
también ECX) no excedan de 0FFFFh. Operando sobre la memoria de vídeo sólo se obtiene ventaja
si la tarjeta es realmente de 32 bits.
MOVSX / MOVZX: carga con extensión de signo o cero. Toma el segundo operando, le extiende
adecuadamente el signo (o le pone a cero la parte alta) hasta que sea tan grande como el primer
operando y luego lo carga en el primer operando. Si el primer operando es de 16 bits, el segundo sólo
puede ser de 8; si el primero es de 32 bits el segundo puede ser de 8 ó 16. El primer operando debe
ser un registro, el segundo puede ser un registro u operando en memoria (nunca inmediato):
MOV EAX,0FFFFFFFFh
MOV AX,7FFFh ; resultado: EAX = 0FFFF7FFFh
MOVSX EAX,AX ; resultado: EAX = 000007FFFh
OUTSD: Similar a OUTSW pero empleando ESI, EDI, ECX y enviando datos de 32 bits. Se puede
emplear bajo DOS siempre que ESI y EDI (usando REP también ECX) no rebasen 0FFFFh.
PUSHAD / POPAD: Similares a PUSHA y POPA pero con los registro de 32 bits. La instrucción
POPAD falla en la mayoría de los 386, incluidos los de AMD. Para solventar el fallo (que consiste en
que EAX no se restaura correctamente) basta colocar un NOP inmediatamente detrás de POPAD.
SCASD: Similar a SCASW pero empleando ESI, EDI, ECX y buscando datos de 32 bits. Se puede
emplear bajo DOS siempre que ESI y EDI (usando REP también ECX) no rebasen 0FFFFh.
SETcc reg8 ó mem8: Si se cumple la condición cc, se pone a 1 el byte de memoria o registro de 8 bits
indicado (si no, a 0). Por ejemplo, con el acarreo activo, SETC AL pone a 1 el registro AL.
MOV AX,1234h
MOV BX,5678h
SHLD AX,BX,4 ; resultado: AX=2345h, BX=5678h
STOSD: Similar a STOSW pero empleando ESI, EDI, ECX y almacenando EAX. Se puede emplear
bajo DOS siempre que ESI y EDI (utilizando REP también ECX) no excedan de 0FFFFh.
30 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
Hay casos en los que es necesario determinar si una máquina es AT o superior: no ya de cara a
emplear instrucciones propias del 286 en modo real (también disponibles en los V20/V30 y
80188/80186) sino debido a la necesidad de acceder a ciertos chips (por ejemplo, el segundo
controlador de interrupciones) que de antemano se sabe que sólo equipan máquinas AT o superiores.
Es importante por tanto determinar la presencia de un AT, de cara a evitar ciertas instrucciones que
podrían bloquear un PC o XT. No se debe en estos casos comprobar los bytes de la ROM que
identifican el equipo: a veces no son correctos y, además, la evolución futura que tengan es
impredecible. Lo ideal es verificar directamente si está instalado un 286 o superior.
PUSHF
POP AX ; AX = flags
AND AH,0Fh ; borrar nibble más significativo
PUSH AX
POPF ; intentar poner a 0 los 4 bits más significativos de los flags
PUSHF
POP AX
AND AH,0F0h ; seguirán valiendo 1 excepto en un 80286 o superior
CMP AH,0F0h
JE no_es_AT
JMP si_es_AT ; es 286 o superior
Sobra decir que las instrucciones avanzadas deben ser utilizadas con la previa comprobación del
tipo de procesador, aunque sólo sea para decir al usuario que se compre una máquina más potente
antes de abortar la ejecución del programa. Para averiguar el procesador de un ordenador puede
emplearse el siguiente programa de utilidad, basado en el procedimiento procesador? que devuelve en
AX un código numérico entro 0 y 8 distinguiendo entre los 9 procesadores más difíciles de identificar
de los ordenadores compatibles. Nota: el 486 no tiene que tener coprocesador necesariamente (el
486sx carece de él).
Algunas versiones de procesador 486 y todos los procesadores posteriores soportan la instrucción
CPUID que permite identificar la CPU. Basta comprobar un bit del registro de estado para saber si
está soportada y, en ese caso, poder emplear dicha instrucción. De este modo, resulta trivial detectar
el Pentium o cualquier procesador posterior que aparezca. Esta instrucción está documentada, por
ejemplo en alguno de los ficheros que acompañan al Interrupt List. Para los propósitos de este libro no
es preciso en general detectar más allá del 386.
Es normal que el lector recién iniciado en el ensamblador no entienda absolutamente nada de este
programa, ya que hasta los siguientes capítulos no será explicada la sintaxis del lenguaje. En ese caso,
puede saltarse este ejemplo y continuar en el capítulo siguiente, máxime si no tiene previsto trabajar
con otras instrucciones que no sean las del 8086. Por último, recordar que las instrucciones específicas
del 286 en modo real también están disponibles en los V20/V30 de NEC y la serie 80188/80186 de
Intel.
; ********************************************************************
; * *
; * CPU v2.2 (c) Septiembre 1992 CiriSOFT *
; * (c) Grupo Universitario de Informática - Valladolid *
; * *
; * Este programa determina el tipo de microprocesador del equipo *
; * y devuelve un código ERRORLEVEL indicándolo: *
31 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
; * *
; * 0-8088, 1-8086, 2-NEC V20, 3-NEC V30, *
; * 4-80188, 5-80186, 6-286, 7-386, 8-486 *
; * *
; * Aviso: Utilizar TASM 2.0 o compatible exclusivamente. *
; * *
; ********************************************************************
cpu SEGMENT
ASSUME CS:cpu, DS:cpu
.386
ORG 100h
inicio:
LEA DX,texto_ini ; texto de saludo
MOV AH,9
INT 21h ; imprimirlo
CALL procesador? ; tipo de procesador en AX
PUSH AX ; guardarlo para el final
LEA BX,cpus_indice-2 ; tabla de nombres-2
MOV CX,0FFFFh ; número de iteración-1
otro_proc: INC CX
ADD BX,2
MOV DX,[BX] ; nombre del primer procesador
CALL print
CMP CX,AX ; ¿procesador del equipo?
JNE no_es_este
LEA DX,apuntador_txt ; sí lo es: indicarlo
CALL print
no_es_este: LEA DX,separador_txt
CALL print
CMP CX,7 ; número de CPUs tratadas-1
JBE otro_proc
LEA DX,texto_fin ; últimos caracteres
CALL print
MOV AH,4Ch ; retornar código errorlevel AL
INT 21h ; fin de programa
32 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
JE ni286ni_super
PUSHF ; es 286 o superior
POP AX
OR AX,7000h ; intentar activar bit 12, 13 ó 14
PUSH AX
POPF
PUSHF
POP AX
AND AX,7000h ; 286 pone bits 12, 13 y 14 a cero
JZ cpu_hallada ; es un 286 (DL=6)
INC DL ; es un 386 (DL=7) ... de momento
PUSH DX
CLI ; momento crítico
MOV EDX,ESP ; preservar ESP en EDX
AND ESP,0FFFFh ; borrar parte alta de ESP
AND ESP,0FFFCh ; forzar ESP a múltiplo de 4
PUSHFD ; guardar flags en pila (32 bits)
POP EAX ; recuperar flags en EAX
MOV ECX,EAX
XOR EAX,40000h ; conmutar bit 18
PUSH EAX
POPFD ; intentar cambiar este bit
PUSHFD
POP EAX ; ECX conserva el bit inicial
XOR EAX,ECX ; bit 18 de EAX a 1 si cambió
SHR EAX,12h ; mover bit 18 a bit 0
AND EAX,1 ; dejar sólo ese bit
PUSH ECX
POPFD ; restaurar bit 18 de los flags
MOV ESP,EDX ; restaurar ESP
STI ; permitir interrupciones de nuevo
POP DX ; recuperar tipo de CPU en DL
CMP AX,0
JE cpu_hallada ; es 386: DL=7 (bit 18 no cambió)
INC DL ; es 486: DL=8 (bit 18 cambió)
JMP cpu_hallada
ni286ni_super: MOV DL,4 ; supuesto un 80188 ...
MOV AX,0FFFFh
MOV CL,33
SHL AX,CL ; (80188/80186 toman CL mod 32)
JNZ tipo_bus_proc ; ... lo es, calcular bus (188/186)
MOV DL,2 ; no lo es, supuesto un V20 ...
MOV CX,0FFFFh
STI
DB 0F3h,26h,0ACh ; opcode de REPZ LODSB ES:
JCXZ tipo_bus_proc ; ... lo es, calcular bus (V20/V30)
XOR DL,DL ; ya sólo puede ser un 8088/8086
tipo_bus_proc: STD ; transferencias hacia arriba
LEA DI,tipo_bus_dest
MOV AL,BYTE PTR DS:tipo_bus_byte ; opcode de STI
MOV CX,3
CLI
REP STOSB ; transferir tres bytes
CLD
NOP ; el INC CX (1 byte) será machacado
NOP ; con STOSB pero aún se ejecutará
NOP ; en un 8086/80186/V30 (y no en un
INC CX ; 8088/80188/V20) porque está en la
tipo_bus_byte: STI ; cola de lectura adelantada.
tipo_bus_dest: STI
JCXZ cpu_hallada ; el bus ya era supuesto de 8 bits
INC DL ; resulta que es de 16
33 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
print PROC
PUSH AX
PUSH BX
PUSH CX
MOV AH,9
INT 21h
POP CX
POP BX
POP AX
RET
print ENDP
cpus_indice DW i88,i86,v20,v30,i188,i186,i286,i386,i486
i88 DB "Intel 8088 $"
i86 DB "Intel 8086 $"
v20 DB " NEC V20 $"
v30 DB " NEC V30 $"
i188 DB "Intel 80188$"
i186 DB "Intel 80186$"
i286 DB "Intel 80286$"
i386 DB "Intel 80386$"
i486 DB "Intel 80486$"
cpu ENDS
END inicio
El problema es que pasar a modo protegido no es sencillo cuando la máquina ya está en modo
34 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
protegido emulando al modo real (el conocido como modo virtual 86). Por tanto, el siguiente programa
de ejemplo no funciona si está cargado un controlador de memoria expandida (EMM386, QEMM) o
dentro de Windows 3.x. Arrancando sin controlador de memoria (excepto HIMEM) no habrá
problema alguno. El programa de ejemplo se limita a llenar la pantalla de texto (empleando ahora la
dirección absoluta 0B8000h a través de EBX) de letras 'A'.
Otra restricción de este programa de ejemplo es que no activa la línea A20 de direcciones; dicho
de otro modo, el bit 21º (de los 32 bits de la dirección de memoria) suele estar forzado a 0 por defecto
al arrancar. Para acceder a la memoria de vídeo esto no es problema, pero por encima del primer
megabyte podría haber problemas según a qué dirección se pretenda acceder. De todos modos, sería
relativamente sencillo habilitar la línea A20 directamente o a través de una función del controlador
XMS.
Naturalmente, se sale de los objetivos de este libro describir el modo protegido o explicar los pasos
que realiza esta rutina de demostración. Consúltese al efecto la bibliografía recomendada del apéndice.
; +------------------------------------------------------------------+
; | Rutina para activar el modo flat del 386 y superiores (acceso |
; | a 4 Gb en modo real). |
; | |
; | TASM flat386 /m5 |
; | TLINK flat386 /t /32 |
; +------------------------------------------------------------------+
ORG 100h
prueba:
CALL flat386 ; activar modo flat
XOR AX,AX
MOV DS,AX
MOV EBX,0B8000h ; dirección de vídeo absoluta
MOV CX,2000
llena_pant: MOV BYTE PTR [EBX],'A'
INC EBX
MOV BYTE PTR [EBX],15
INC EBX
LOOP llena_pant
INT 20h ; fin de programa
flat386 PROC
PUSH DS
PUSH ES
PUSH EAX
PUSH BX
PUSH CX
MOV CX,SS
XOR EAX,EAX
MOV AX,CS
SHL EAX,4 ; dirección lineal de segmento CS
35 de 36 12/10/00 19:07
JUEGO DE INSTRUCCIONES 80x86 file:///C|/librosVirtuales/UniversoDigital/04.html
flat386 ENDP
segmento ENDS
END prueba
36 de 36 12/10/00 19:07
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
Hasta ahora hemos visto los mnemónicos de las instrucciones que pasadas a su correspondiente código
binario ya puede entender el microprocesador. Si bien se realiza un gran avance al introducir los mnemónicos
respecto a programar directamente en lenguaje maquina -es decir, con números en binario o hexadecimal- aún
resultaría tedioso tener que realizar los cálculos de los desplazamientos en los saltos a otras partes del
programa en las transferencias de control, reservar espacio de memoria dentro de un programa para
almacenar datos, etc... Para facilitar estas operaciones se utilizan las directivas que indican al ensamblador
qué debe hacer con las instrucciones y los datos.
Los programas de ejemplo de este libro y la sintaxis de ensamblador tratada son las del MASM de
Microsoft y el ensamblador de IBM. No obstante, todos los programas han sido desarrollados con el Turbo
Assembler 2.0 de Borland (TASM), compatible con el clásico MASM 5.0 de Microsoft pero más potente y
al mismo tiempo mucho más rápido y flexible. TASM genera además un código más reducido y optimizado.
Por otra parte, MASM 5.0 no permite cambiar (aunque sí la 6.0) dentro de un segmento el modo del
procesador: esto conlleva el riesgo de ejecutar indeseadamente instrucciones de 32 bits al no poder acotar
exactamente las líneas donde se desea emplearlas, algo vital para mantener la compatibilidad con
procesadores anteriores. También es propenso a generar errores de fase y otros similares al tratar con
listados un poco grandes. Respecto a MASM 6.0, el autor de este libro encontró que en ocasiones calcula
incorrectamente el valor de algunos símbolos y etiquetas, aunque es probable que la versión 6.1 (aparecida
sospechosa e inusualmente muy poco tiempo después) haya corregido dichos fallos, intolerables en un
ensamblador. Por otro lado, las posibilidades adicionales de TASM no han sido empleadas por lo general.
Muchos programas han sido ensamblados una vez con MASM, para asegurar que éste puede ensamblarlos.
Conviene decir aquí que este capítulo es especialmente arduo para aquellos que no conocen el lenguaje
ensamblador de ninguna máquina. La razón es que la información está organizada a modo de referencia, por
lo que con frecuencia se utilizan unos elementos -para explicar otros- que aún no han sido definidos. Ello por
otra parte resulta inevitable también en algunos libros más básicos, debido a la complejidad de la sintaxis del
lenguaje ensamblador ideada por el fabricante (que no la del microprocesador). Por ello, es un buen consejo
actuar a dos pasadas, al igual que el propio ensamblador en ocasiones: leer todo una vez primero -aunque no
se entienda del todo- y volverlo a leer después más despacio.
Un programa fuente en ensamblador contiene dos tipos de sentencias: las instrucciones y las directivas.
Las instrucciones se aplican en tiempo de ejecución, pero las directivas sólo son utilizadas durante el
ensamblaje. El formato de una sentencia de instrucción es el siguiente:
Los corchetes, como es normal al explicar instrucciones en informática, indican que lo especificado entre
ellos es opcional, dependiendo de la situación que se trate.
- Si se utiliza el punto «.» éste debe colocarse como primer carácter de la etiqueta.
- El primer carácter no puede ser un dígito.
- No se pueden utilizar los nombres de instrucciones o registros como nombres de etiquetas.
1 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
las etiquetas son de tipo NEAR cuando el campo de etiqueta finaliza con dos puntos (:); esto es, se
considera cercana: quiere esto decir que cuando realizamos una llamada sobre dicha etiqueta el ensamblador
considera que está dentro del mismo segmento de código (llamadas intrasegmento) y el procesador sólo carga
el puntero de instrucciones IP. Téngase en cuenta que hablamos de instrucciones; las etiquetas empleadas
antes de las directivas, como las directivas de definición de datos por ejemplo, no llevan los dos puntos y sin
embargo son cercanas.
Las etiquetas son de tipo FAR si el campo de etiqueta no termina con los dos puntos: en estas etiquetas
la instrucción a la que apunta no se encuentra en el mismo segmento de código sino en otro. Cuando es
referenciada en una transferencia de control se carga el puntero de instrucciones IP y el segmento de código
CS (llamadas intersegmento).
Campo de nombre. Contiene el mnemónico de las instrucciones vistas en el capítulo anterior, o bien
una directiva de las que veremos más adelante.
Campo de operandos. Indica cuales son los datos implicados en la operación. Puede haber 0, 1 ó 2;
en el caso de que sean dos al 1º se le llama destino y al 2º -separado por una coma- fuente.
Campo de comentarios. Cuando en una línea hay un punto y coma (;) todo lo que sigue en la línea es
un comentario que realiza aclaraciones sobre lo que se está haciendo en ese programa, resulta de gran utilidad
de cara a realizar futuras modificaciones al mismo.
Las sentencias fuente -tanto instrucciones como directivas- pueden contener constantes y operadores.
5.2.1. - CONSTANTES.
Pueden ser binarias (ej. 10010b), decimales (ej. 34d), hexadecimales (ej. 0E0h) u octales (ej. 21o ó 21q);
también las hay de cadena (ej. 'pepe', "juan") e incluso con comillas dentro de comillas de distinto tipo (como
'hola,"amigo"'). En las hexadecimales, si el primer dígito no es numérico hay que poner un 0. Sólo se puede
poner el signo (-) en las decimales (en las demás, calcúlese el complemento a dos). Por defecto, las numéricas
están en base 10 si no se indica lo contrario con una directiva (poco recomendable como se verá).
Pueden emplearse libremente (+), (-), (*) y (/) -en este último caso la división es siempre entera-. Es
válida, por ejemplo, la siguiente línea en ensamblador (que se apoya en la directiva DW, que se verá más
adelante, para reservar memoria para una palabra de 16 bits):
dato DW 12*(numero+65)/7
2 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
Pueden ser el AND, OR, XOR y NOT. Realizan las operaciones lógicas en las expresiones. Ej.:
Devuelven condiciones de cierto (0FFFFh ó 0FFh) o falso (0) evaluando una expresión. Pueden ser: EQ
(igual), NE (no igual), LT (menor que), GT (mayor que), LE (menor o igual que), GE (mayor o igual que).
Ejemplo:
* Operador SEG: devuelve el valor del segmento de la variable o etiqueta, sólo se puede emplear en
programas de tipo EXE:
Si se desea obtener el offset de una variable respecto al grupo (directiva GROUP) de segmentos en
que está definida y no respecto al segmento concreto en que está definida:
también es válido:
* Operador .TYPE: devuelve el modo de la expresión indicada en un byte. El bit 0 indica modo «relativo
al código» y el 1 modo «relativo a datos», si ambos bits están inactivos significa modo absoluto. El bit 5 indica
si la expresión es local (0 si está definida externamente o indefinida); el bit 7 indica si la expresión contiene una
referencia externa. El TASM utiliza también el bit 3 para indicar algo que desconozco. Este operador es útil
sobre todo en las macros para determinar el tipo de los parámetros:
* Operador TYPE: devuelve el tamaño (bytes) de la variable indicada. No válido en variables DUP:
kilos DW 76
MOV AX,TYPE kilos ; AX = 2
Tratándose de etiquetas -en lugar de variables- indica si es lejana o FAR (0FFFEh) o cercana o
NEAR (0FFFFh).
3 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
* Operadores MASK y WIDTH: informan de los campos de un registro de bits (véase RECORD).
* Operador PTR: redefine el atributo de tipo (BYTE, WORD, DWORD, QWORD, TBYTE) o el
de distancia (NEAR o FAR) de un operando de memoria. Por ejemplo, si se tiene una tabla definida de la
siguiente manera:
Para colocar en AL el primer byte de la misma, la instrucción MOV AL,tabla es incorrecta, ya que
tabla (una cadena 10 palabras) no cabe en el registro AL. Lo que desea el programador debe indicárselo en
este caso explícitamente al ensamblador de la siguiente manera:
Trabajando con varios segmentos, PTR puede redefinir una etiqueta NEAR de uno de ellos para
convertirla en FAR desde el otro, con objeto de poder llamarla.
* Operadores CS:, DS:, ES: y SS: el ensamblador genera un prefijo de un byte que indica al
microprocesador el segmento que debe emplear para acceder a los datos en memoria. Por defecto, se supone
DS para los registros BX, DI o SI (o sin registros de base o índice) y SS para SP y BP. Si al acceder a un
dato éste no se encuentra en el segmento por defecto, el ensamblador añadirá el byte adicional de manera
automática. Sin embargo, el programador puede forzar también esta circunstancia:
MOV AL,ES:variable
En el ejemplo, variable se supone ubicada en el segmento extra. Cuando se referencia una dirección
fija hay que indicar el segmento, ya que el ensamblador no conoce en qué segmento está la variable, es uno de
los pocos casos en que debe indicarse. Por ejemplo, la siguiente línea dará un error al ensamblar:
MOV AL,[0]
Para solucionarlo hay que indicar en qué segmento está el dato (incluso aunque éste sea DS):
MOV AL,DS:[0]
En este último ejemplo el ensamblador no generará el byte adicional ya que las instrucciones MOV
operan por defecto sobre DS (como casi todas), pero ha sido necesario indicar DS para que el ensamblador
nos entienda. Sin embargo, en el siguiente ejemplo no es necesario, ya que midato está declarado en el
segmento de datos y el ensamblador lo sabe:
MOV AL,midato
Por lo general no es muy frecuente la necesidad de indicar explícitamente el segmento: al acceder a una
variable el ensamblador mira en qué segmento está declarada (véase la directiva SEGMENT) y según como
estén asignados los ASSUME, pondrá o no el prefijo adecuado según sea conveniente. Es responsabilidad
exclusiva del programador inicializar los registros de segmento al principio de los procedimientos para que el
ASSUME no se quede en tinta mojada... sí se emplean con bastante frecuencia, sin embargo, los prefijos CS
en las rutinas que gestionan interrupciones (ya que CS es el único registro de segmento que apunta en
principio a las mismas, hasta que se cargue DS u otro).
4 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
* Operador SHORT: indica que la etiqueta referenciada, de tipo NEAR, puede alcanzarse con un salto
corto (-128 a +127 posiciones) desde la actual situación del contador de programa. El ensamblador TASM,
si se solicitan dos pasadas, coloca automáticamente instrucciones SHORT allí donde es posible, para
economizar memoria (el MASM no).
* Operador '$': indica la posición del contador de posiciones («Location Counter») utilizado por el
ensamblador dentro del segmento para llevar la cuenta de por dónde se llega ensamblando. Muy útil:
frase DB "simpático"
longitud EQU $-OFFSET frase
* Operadores HIGH y LOW: devuelven la parte alta o baja, respectivamente (8 bits) de la expresión:
Sólo es obligatorio el campo «nombre_directiva»; los campos han de estar separados por al menos un
espacio en blanco. La sintaxis de «nombre» es análoga a la de la «etiqueta» de las líneas de instrucciones,
aunque nunca se pone el sufijo «:». El campo de comentario cumple también las mismas normas. A
continuación se explican las directivas empleadas en los programas ejemplo de este libro y alguna más, aunque
falta alguna que otra y las explicadas no lo están en todos los casos con profundidad.
* DB (definir byte), DW (definir palabra), DD (definir doble palabra), DQ (definir cuádruple palabra), DT
(definir 10 bytes): sirven para declarar las variables, asignándolas un valor inicial:
anno DW 1991
mes DB 12
numerazo DD 12345678h
texto DB "Hola",13,10
Se pueden definir números reales de simple precisión (4 bytes) con DD, de doble precisión (8 bytes)
con DQ y «reales temporales» (10 bytes) con DT; todos ellos con el formato empleado por el coprocesador.
Para que el ensamblador interprete el número como real ha de llevar el punto decimal:
temperatura DD 29.72
espanoles91 DQ 38.9E6
Con el operando DUP pueden definirse estructuras repetitivas. Por ejemplo, para asignar 100 bytes a
cero y 25 palabras de contenido indefinido (no importa lo que el ensamblador asigne):
5 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
Se admiten también los anidamientos. El siguiente ejemplo crea una tabla de bytes donde se repite 50
veces la secuencia 1,2,3,7,7:
Donde olimpiadas ya no podrá cambiar de valor en todo el programa. Se trata de un operador muy
flexible. Es válido hacer:
* = (signo '='): asigna el valor de la expresión a un nombre simbólico variable: Análogo al anterior pero
con posibilidad de cambiar en el futuro. Muy usada en macros (sobre todo con REPT).
num = 19
num = pepe + 1
dato = [BX+3]
dato = ES:[BP+1]
* ORG (ORiGin): pone el contador de posiciones del ensamblador, que indica el offset donde se deposita
la instrucción o dato, donde se indique. En los programas COM (que se cargan en memoria con un OFFSET
100h) es necesario colocar al principio un ORG 100h, y un ORG 0 en los controladores de dispositivo
(aunque si se omite se asume de hecho un ORG 0).
* END [expresión]: indica el final del fichero fuente. Si se incluye, expresión indica el punto donde arranca
el programa. Puede omitirse en los programas EXE si éstos constan de un sólo módulo. En los COM es
preciso indicarla y, además, la expresión -realmente una etiqueta- debe estar inmediatamente después del
ORG 100h.
* .286, .386 Y .8087 obligan al ensamblador a reconocer instrucciones específicas del 286, el 386 y del
8087. También debe ponerse el «.» inicial. Con .8086 se fuerza a que de nuevo sólo se reconozcan
instrucciones del 8086 (modo por defecto). La directiva .386 puede ser colocada dentro de un segmento
(entre las directivas SEGMENT/ENDS) con el ensamblador TASM, lo que permite emplear instrucciones de
386 con segmentos de 16 bits; alternativamente se puede ubicar fuera de los segmentos (obligatorio en
MASM) y definir éstos explícitamente como de 16 bits con USE16.
* EVEN: fuerza el contador de posiciones a una posición par, intercalando un byte con la instrucción NOP
si es preciso. En buses de 16 ó más bits (8086 y superiores, no en 8088) es dos veces más rápido el acceso
a palabras en posición par:
EVEN
dato_rapido DW 0
* .RADIX n: cambia la base de numeración por defecto. Bastante desaconsejable dada la notación elegida
para indicar las bases por parte de IBM/Microsoft (si se cambia la base por defecto a 16, ¡los números no
6 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
pueden acabar en 'd' ya que se confundirían con el sufijo de decimal!: lo ideal sería emplear un prefijo y no un
sufijo, que a menudo obliga además a iniciar los números por 0 para distinguirlos de las etiquetas).
* SEGMENT-ENDS: SEGMENT indica el comienzo de un segmento (código, datos, pila, etc.) y ENDS
su final. El programa más simple, de tipo COM, necesita la declaración de un segmento (común para datos,
código y pila). Junto a SEGMENT puede aparecer, opcionalmente, el tipo de alineamiento, la
combinación, el uso y la clase:
Se pueden definir unos segmentos dentro de otros (el ensamblador los ubicará unos tras otros). El
alineamiento puede ser BYTE (ninguno), WORD (el segmento comienza en posición par), DWORD
(comienza en posición múltiplo de 4), PARA (comienza en una dirección múltiplo de 16, opción por defecto)
y PAGE (comienza en dirección múltiplo de 256). La combinación puede ser:
- (No indicada): los segmentos se colocan unos tras otros físicamente, pero son lógicamente
independientes: cada uno tiene su propia base y sus propios offsets relativos.
- PUBLIC: usado especialmente cuando se trabaja con segmentos definidos en varios ficheros que
se ensamblan por separado o se compilan con otros lenguajes, por ello debe declararse un nombre entre
comillas simples -'clase'- para ayudar al linkador. Todos los segmentos PUBLIC de igual nombre y clase
tienen una base común y son colocados adyacentemente unos tras otros, siendo el offset relativo al primer
segmento cargado.
- COMMON: similar, aunque ahora los segmentos de igual nombre y clase se solapan. Por ello, las
variables declaradas han de serlo en el mismo orden y tamaño.
- AT: asocia un segmento a una posición de memoria fija, no para ensamblar sino para declarar
variables (inicializadas siempre con '?') de cara a acceder con comodidad a zonas de ROM, vectores de
interrupción, etc. Ejemplo:
De esta manera, la dirección del primer puerto serie puede obtenerse de esta manera (por
ejemplo):
- STACK: segmento de pila, debe existir uno en los programas de tipo EXE; además el Linkador
de Borland (TLINK 4.0) exige obligatoriamente que la clase de éste sea también 'STACK', con el LINK de
Microsoft no siempre es necesario indicar la clase del segmento de pila. Similar, por lo demás, a PUBLIC.
- MEMORY: segmento que el linkador ubicará al final de todos los demás, lo que permitiría saber
dónde acaba el programa. Si se definen varios segmentos de este tipo el ensamblador acepta el primero y
trata a los demás como COMMON. Téngase en cuenta que el linkador no soporta esta característica, por lo
que emplear MEMORY es equivalente a todos los efectos a utilizar COMMON. Olvídate de MEMORY.
El uso indica si el segmento es de 16 bits o de 32; al emplear la directiva .386 se asumen por defecto
segmentos de 32 bits por lo que es necesario declarar USE16 para conseguir que los segmentos sean
7 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
interpretados como de 16 bits por el linkador, lo que permite emplear algunas instrucciones del 386 en el
modo real del microprocesador y bajo el sistema operativo DOS.
Por último, 'clase' es un nombre opcional que empleará el linkador para encadenar los módulos, siendo
conveniente nombrar la clase del segmento de pila con 'STACK'.
* ASSUME (Suponer): Indica al ensamblador el registro de segmento que se va a utilizar para direccionar
cada segmento dentro del módulo. Esta instrucción va normalmente inmediatamente después del SEGMENT.
El programa más sencillo necesita que se «suponga» CS como mínimo para el segmento de código, de lo
contrario el ensamblador empezará a protestar un montón al no saber que registro de segmento asociar al
código generado. También conviene hacer un assume del registro de segmento DS hacia el segmento de
datos, incluso en el caso de que éste sea el mismo que el de código: si no, el ensamblador colocará un byte de
prefijo adicional en todos los accesos a memoria para forzar que éstos sean sobre CS. Se puede indicar
ASSUME NOTHING para cancelar un ASSUME anterior. También se puede indicar el nombre de un grupo
o emplear «SEG variable» o «SEG etiqueta» en vez de nombre_segmento:
ASSUME reg_segmento:nombre_segmento[,...]
* PROC-ENDP permite dar nombre a una subrutina, marcando con claridad su inicio y su fin.
Aunque es redundante, es muy recomendable para estructurar los programas.
cls PROC
...
cls ENDP
El atributo FAR que aparece en ocasiones junto a PROC indica que es un procedimiento lejano y las
instrucciones RET en su interior se ensamblan como RETF (los CALL hacia él serán, además, de 32 bits).
Observar que la etiqueta nunca termina con dos puntos.
* PUBLIC: permite hacer visibles al exterior (otros ficheros objeto resultantes de otros listados en
ensamblador u otro lenguaje) los símbolos -variables y procedimientos- indicados. Necesario para
programación modular e interfaces con lenguajes de alto nivel. Por ejemplo:
Declara la variable var_x y el procedimiento proc1 como accesibles desde el exterior por medio de la
directiva EXTRN.
* EXTRN: Permite acceder a símbolos definidos en otro fichero objeto (resultante de otro ensamblaje o
de una compilación de un lenguaje de alto nivel); es necesario también indicar el tipo del dato o procedimiento
(BYTE, WORD o DWORD; NEAR o FAR; se emplea además ABS para las constantes numéricas):
En el ejemplo se accede a los símbolos externos proc1 y var_x (ver ejemplos de PUBLIC) y a
continuación sería posible hacer un CALL proc1 o un MOV CX,var_x. Si la directiva EXTRN se coloca
8 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
dentro de un segmento, se supone el símbolo dentro del mismo. Si el símbolo está en otro segmento, debe
colocarse EXTRN fuera de todos los segmentos indicando explícitamente el prefijo del registro de segmento
(o bien hacer el ASSUME apropiado) al referenciarlo. Evidentemente, al final, al linkar habrá que enlazar este
módulo con el que define los elementos externos.
* GROUP segmento1, segmento2,... permite agrupar dos o más segmentos lógicos en uno sólo de no más
de 64 Kb totales (ojo: el ensamblador no comprueba este extremo, aunque sí el enlazador). Ejemplo:
codigo SEGMENT
...
codigo ENDS
datos SEGMENT
dato DW 1234
datos ENDS
La ventaja de agrupar segmentos es poder crear programas COM y SYS que contengan varios
segmentos. En todo caso, téngase en cuenta aún en ese caso que no pueden emplearse todas las
características de la programación con segmentos (por ejemplo, no se puede utilizar la directiva SEG ni debe
existir segmento de pila).
* LABEL: Permite referenciar un símbolo con otro nombre, siendo factible redefinir el tipo. La sintaxis es:
nombre LABEL tipo (tipo = BYTE, WORD, DWORD, NEAR o FAR). Ejemplo:
En el ejemplo, con MOV AX,palabra se accederá a ambos bytes a la vez (el empleo de MOV
AX,byte_bajo daría error: no se puede cargar un sólo byte en un registro de 16 bits y el ensamblador no
supone que realmente pretendíamos tomar dos bytes consecutivos de la memoria).
9 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
* STRUC - ENDS: permite definir registros al estilo de los lenguajes de alto nivel, para acceder de una
manera más elegante a los campos de una información con cierta estructura. Estos campos pueden
componerse de cualquiera de los tipos de datos simples (DB, DW, DD, DQ, DT) y pueden ser modificables
o no en función de si son simples o múltiples, respectivamente:
alumno STRUC
mote DB '0123456789' ; modificable
edadaltura DB 20,175 ; no modificable
peso DB 0 ; modificable
otros DB 10 DUP(0) ; no modificable
telefono DD ? ; modificable
alumno ENDS
En el ejemplo se definen los campos modificables (los únicos definibles) dejando sin definir (comas
consecutivas) los no modificables, creándose la estructura 'felipe' que ocupa 27 bytes. Las cadenas de
caracteres son rellenadas con espacios en blanco al final si no alcanzan el tamaño máximo de la declaración.
El TASM es más flexible y permite definir también el primer elemento de los campos múltiples sin dar error.
Tras crear la estructura, es posible acceder a sus elementos utilizando un (.) para separar el nombre del
campo:
* RECORD: similar a STRUC pero operando con campos de bits. Permite definir una estructura
determinada de byte o palabra para operar con comodidad. Sintaxis:
La estructura registro totaliza 7 bits, por lo que ocupa un byte. Está dividida en tres campos que
ocupan los 7 bits menos significativos del byte: el campo A ocupa los bits 6 y 5, el B los bits del byte: el
campo A ocupa los bi1 al 4 y el C el bit 0:
65 4321 0
11 0101 ?
Quedando reg1 con el valor binario 1001011 (el campo B permanece inalterado y el A y C toman los
valores indicados). Ejemplos de operaciones soportadas:
10 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
Se emplean para que el ensamblador evalúe unas condiciones y, según ellas, ensamble o no ciertas
zonas de código. Es frecuente, por ejemplo, de cara a generar código para varios ordenadores: pueden existir
ciertos símbolos definidos que indiquen en un momento dado si hay que ensamblar ciertas zonas del listado o
no de manera condicional, según la máquina. En los fragmentos en ensamblador del código que generan los
compiladores también aparecen con frecuencia (para actuar de manera diferente, por ejemplo, según el
modelo de memoria). Es interesante también la posibilidad de definir un símbolo que indique que el programa
está en fase de pruebas y ensamblar código adicional en ese caso con objeto de depurarlo. Sintaxis:
* PAGE num_lineas, num_columnas: Formatea el listado de salida; por defecto son 66 líneas por página
(modificable entre 10 y 255) y 80 columnas (seleccionable de 60 a 132). PAGE salta de página e incrementa
su número. «PAGE +» indica capítulo nuevo (y se incrementa el número).
* TITLE título: indica el título que aparece en la 1ª línea de cada página (máximo 60 caracteres).
* .XCREF: Suprimir listado de referencias cruzadas (listado alfabético de símbolos junto al nº de línea en
que son definidos y referenciados, de cara a facilitar la depuración).
11 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
* COMMENT delimitador comentario delimitador: Define un comentario que puede incluso ocupar varias
líneas, el delimitador (primer carácter no blanco ni tabulador que sigue al COMMENT) indica el inicio e
indicará más tarde el final del comentario. ¡No olvidar cerrar el comentario!.
* %OUT mensaje: escribe en la consola el mensaje indicado durante la fase de ensamblaje y al llegar a ese
punto del listado, excepto cuando el listado es por pantalla y no en fichero.
* .LFCOND: Listar los bloques de código asociados a una condición falsa (IF).
* .TFCOND: Invertir el modo vigente de listado de los bloques asociados a una condición falsa.
5.4. - MACROS.
No conviene confundir las macros con subrutinas: es estas últimas, el conjunto de instrucciones aparece
una sola vez en todo el programa y luego se invoca con CALL. Sin embargo, cada vez que se referencia a una
macro, el código que ésta representa se expande en el programa definitivo, duplicándose tantas veces como
se use la macro. Por ello, aquellas tareas que puedan ser realizadas con subrutinas siempre será más
conveniente realizarlas con las mismas, con objeto de economizar memoria. Es cierto que las macros son algo
más rápidas que las subrutinas (se ahorra un CALL y un RET) pero la diferencia es tan mínima que en la
práctica es despreciable en el 99,99% de los casos. Por ello, es absurdo e irracional realizar ciertas tareas
con macros que pueden ser desarrolladas mucho más eficientemente con subrutinas: es una pena que en
muchos manuales de ensamblador aún se hable de macros para realizar operaciones sobre cadenas de
caracteres, que generarían programas gigantescos con menos de un 1% de velocidad adicional.
La macro se define por medio de la directiva MACRO. Es necesario definir la macro antes de utilizarla.
Una macro puede llamar a otra. Con frecuencia, las macros se colocan juntas en un fichero independiente y
luego se mezclan en el programa principal con la directiva INCLUDE:
IF1
INCLUDE fichero.ext
ENDIF
La sentencia IF1 asegura que el ensamblador lea el fichero fuente de las macros sólo en la primera pasada,
para acelerar el ensamblaje y evitar que aparezcan en el listado (generado en la segunda fase). Conviene
hacer hincapié en que la definición de la macro no consume memoria, por lo que en la práctica es indiferente
declarar cientos que ninguna macro:
El nombre simbólico es el que permitirá en adelante hacer referencia a la macro, y se construye casi con
las mismas reglas que los nombres de las variables y demás símbolos. La macro puede contener parámetros
12 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
de manera opcional. A continuación vienen las instrucciones que engloba y, finalmente, la directiva ENDM
señala el final de la macro. No se debe repetir el nombre simbólico junto a la directiva ENDM, ello provocaría
un error un tanto curioso y extraño por parte del ensamblador (algo así como «Fin del fichero fuente
inesperado, falta directiva END»), al menos con MASM 5.0 y TASM 2.0.
En realidad, y a diferencia de lo que sucede con los demás símbolos, el nombre de una macro puede
coincidir con el de una instrucción máquina o una directiva del ensamblador: a partir de ese momento, la
instrucción o directiva machacada pierde su significado original. El ensamblador dará además un aviso de
advertencia si se emplea una instrucción o directiva como nombre de macro, aunque tolerará la operación.
Normalmente se las asignará nombres normales, como a las variables. Sin embargo, si alguna vez se
redefiniera una instrucción máquina o directiva, para restaurar el significado original del símbolo, la macro
puede ser borrada -o simplemente porque ya no va a ser usada a partir de cierto punto del listado, y así ya no
consumirá espacio en las tablas de macros que mantiene en memoria el ensamblador al ensamblar-. No es
necesario borrar las macros antes de redefinirlas. Para borrarlas, la sintaxis es la siguiente:
PURGE nombre_simbólico[,nombre_simbólico,...]
Desde el 286 existe una instrucción muy cómoda que introduce en la pila 8 registros, y otra que los saca
(PUSHA y POPA). Quien esté acostumbrado a emplearlas, puede crear unas macros que simulen estas
instrucciones en los 8086:
SUPERPUSH MACRO
PUSH AX
PUSH CX
PUSH DX
PUSH BX
PUSH SP
PUSH BP
PUSH SI
PUSH DI
ENDM
La creación de SUPERPOP es análoga, sacando los registros en orden inverso. El orden elegido no es
por capricho y se corresponde con el de la instrucción PUSHA original, para compatibilizar. A partir de la
definición de esta macro, tenemos a nuestra disposición una nueva instrucción máquina (SUPERPUSH)
que puede ser usada con libertad dentro de los programas.
Para quien no haya tenido relación previa con algún lenguaje estructurado de alto nivel, haré un breve
comentario acerca de lo que son los parámetros formales y actuales en una macro, similar aquí a los
procedimientos de los lenguajes de alto nivel.
Cuando se llama a una macro se le pueden pasar opcionalmente un cierto número de parámetros de cierto
tipo. Estos parámetros se denominan parámetros actuales. En la definición de la macro, dichos parámetros
aparecen asociados a ciertos nombres arbitrarios, cuya única misión es permitir distinguir unos parámetros de
otros e indicar en qué orden son entregados: son los parámetros formales. Cuando el ensamblador expanda
la macro al ensamblar, los parámetros formales serán sustituidos por sus correspondientes parámetros
actuales. Considerar el siguiente ejemplo:
13 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
MOV AX,a
ADD AX,b
MOV total,AX
POP AX
ENDM
....
SUMAR positivos, negativos, total
En el ejemplo, «a», «b» y «total» son los parámetros formales y «positivos», «negativos» y «total» son los
parámetros actuales. Tanto «a» como «b» pueden ser variables, etiquetas, etc. en otro punto del programa;
sin embargo, dentro de la macro, se comportan de manera independiente. El parámetro formal «total» ha
coincidido en el ejemplo y por casualidad con su correspondiente actual. El código que genera el ensamblador
al expandir la macro será el siguiente:
PUSH AX
MOV AX,positivos
ADD AX,negativos
MOV total,AX
POP AX
Las instrucciones PUSH y POP sirven para no alterar el valor de AX y conseguir que la macro se
comporte como una caja negra; no es necesario que esto sea así pero es una buena costumbre de
programación para evitar que los programas hagan cosas raras. En general, las macros de este tipo no
deberían alterar los registros y, si los cambian, hay que tener muy claro cuáles.
Si se indican más parámetros de los que una macro necesita, se ignorarán los restantes. En cambio, si
faltan, el MASM asumirá que son nulos (0) y dará un mensaje de advertencia, el TASM es algo más rígido y
podría dar un error. En general, se trata de situaciones atípicas que deben ser evitadas.
También puede darse el caso de que no sea posible expandir la macro. En el ejemplo, no hubiera sido
posible ejecutar SUMAR AX,BX,DL porque DL es de 8 bits y la instrucción MOV DL,AX sería ilegal.
Son necesarias normalmente para los saltos condicionales que contengan las macros más complejas. Si se
pone una etiqueta a donde saltar, la macro sólo podría ser empleada una vez en todo el programa para evitar
que dicha etiqueta aparezca duplicada. La solución está en emplear la directiva LOCAL que ha de ir
colocada justo después de la directiva MACRO:
En el ejemplo, al invocar la macro dos veces el ensamblador no generará la etiqueta «ya_esta» sino las
etiquetas ??0000, ??0001, ... y así sucesivamente. La directiva LOCAL no sólo es útil para los saltos
condicionales en las macros, también permite declarar variables internas a los mismos. Se puede indicar un
número casi indefinido de etiquetas con la directiva LOCAL, separándolas por comas.
* Operador ;;
14 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
Indica que lo que viene a continuación es un comentario que no debe aparecer al expansionar la macro.
Cuando al ensamblar se genera un listado del programa, las macros suelen aparecer expandidas en los puntos
en que se invocan; sin embargo sólo aparecerán los comentarios normales que comiencen por (;). Los
comentarios relacionados con el funcionamiento interno de la macro deberían ir con (;;), los relativos al uso y
sintaxis de la misma con (;). Esto es además conveniente porque durante el ensamblaje son mantenidos en
memoria los comentarios de macros (no los del resto del programa) que comienzan por (;), y no conviene
desperdiciar memoria...
* Operador &
Utilizado para concatenar texto o símbolos. Es necesario para lograr que el ensamblador sustituya un
parámetro dentro de una cadena de caracteres o como parte de un símbolo:
SALUDO MACRO c
MOV AL,"&c"
etiqueta&c: CALL imprimir
ENDM
MOV AL,"A"
etiquetaA: CALL imprimir
Cuando se utilizan estructuras repetitivas REPT, IRP o IRPC (que se verán más adelante) existe un
problema adicional al intentar crear etiquetas, ya que el ensamblador se come un & al hacer la primera
sustitución, generando la misma etiqueta a menos que se duplique el operador &:
MEMORIA MACRO x
IRP i, <1, 2>
x&i DB i
ENDM
ENDM
Si se invoca MEMORIA ET se produce el error de "etiqueta ETi repetida", que se puede salvar
añadiendo tantos '&' como niveles de anidamiento halla en las estructuras repetitivas empleadas, como se
ejemplifica a continuación:
MEMORIA MACRO x
IRP i, <1, 2>
x&&i DB i
ENDM
ENDM
ET1 DB 1
ET2 DB 2
* Operador ! o <>
Empleado para indicar que el carácter que viene a continuación debe ser interpretado literalmente y no
como un símbolo. Por ello, !; es equivalente a <;>.
* Operador %
15 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
Convierte la expresión que le sigue -generalmente un símbolo- a un número; la expresión debe ser una
constante (no relocalizable). Sólo se emplea en los argumentos de macros. Dada la macro siguiente:
(Evidentemente, el % que precede a OUT forma parte de la directiva y no se trata del % operador que
estamos tratando)
Estas directivas pueden ser empleadas también sin las macros, aumentando la comodidad de la
programación, aunque abundan especialmente dentro de las macros.
Permite repetir cierto número de veces una secuencia de instrucciones. El bloque de instrucciones se
delimita con ENDM (no confundirlo con el final de una macro). Por ejemplo:
REPT 2
OUT DX,AL
ENDM
OUT DX,AL
OUT DX,AL
Empleando símbolos definidos con (=) y apoyándose además en las macros se puede llegar a crear
pseudo-instrucciones muy potentes:
SUCESION MACRO n
num = 0
REPT n
16 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
DB num
num = num + 1
ENDM ; fin de REPT
ENDM ; fin de macro
DB 0
DB 1
DB 2
* IRP simbolo_control, <arg1, arg2, ..., arg_n> ... ENDM (Indefinite repeat)
Es relativamente similar a la instrucción FOR de los lenguajes de alto nivel. Los ángulos (<) y (>) son
obligatorios. El símbolo de control va tomando sucesivamente los valores (no necesariamente numéricos)
arg1, arg2, ... y recorre en cada pasada todo el bloque de instrucciones hasta alcanzar el ENDM (no
confundirlo con fin de macro) sustituyendo simbolo_control por esos valores en todos los lugares en que
aparece:
IRP i, <1,2,3>
DB 0, i, i*i
ENDM
DB 0, 1, 1
DB 0, 2, 4
DB 0, 3, 9
Nota: Todo lo encerrado entre los ángulos se considera un único parámetro. Un (;) dentro de los
ángulos no se interpreta como el inicio de un comentario sino como un elemento más. Por otra parte, al
emplear macros anidadas, deben indicarse tantos símbolos angulares '<' y '>' consecutivos como niveles de
anidamiento existan.
Lógicamente, dentro de una macro también resulta bastante útil la estructura IRP:
PUSH AX
PUSH DX
MOV AL, 17
MOV DX, 318h
OUT DX, AL
MOV DX, 1C9h
OUT DX, AL
MOV DX, 2D1h
17 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
OUT DX, AL
MOV DX, 1A4h
OUT DX,AL
POP DX
POP AX
Cuando se pasan listas como parámetros hay que encerrarlas entre '<' y '>' al llamar, para no
confundirlas con elementos independientes. Por ejemplo, supuesta la macro INCD:
INC AX
DEC BX ; CX y DX se ignoran (4 parámetros)
INC AX
INC BX
INC CX
DEC DX ; (2 parámetros)
* IRPC simbolo_control, <c1c2 ... cn> ... ENDM (Indefinite repeat character)
Esta directiva es similar a la anterior, con una salvedad: los elementos situados entre los ángulos (<) y
(>) -ahora opcionales, por cierto- son caracteres ASCII y no van separados por comas:
IRPC i, <813>
DB i
ENDM
DB 8
DB 1
DB 3
Ejemplo de utilización dentro de una macro (en combinación con el operador &):
INICIALIZA MACRO a, b, c, d
IRPC iter, <&a&b&c&d>
DB iter
ENDM ; fin de IRPC
ENDM ; fin de macro
DB 7
DB 1
DB 4
18 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
DB 0
* EXITM
Sirve para abortar la ejecución de un bloque MACRO, REPT, IRP ó IRPC. Normalmente se utiliza
apoyándose en una directiva condicional (IF...ELSE...ENDIF). Al salir del bloque, se pasa al nivel
inmediatamente superior (que puede ser otro bloque de estos). Como ejemplo, la siguiente macro reserva n
bytes de memoria a cero hasta un máximo de 100, colocando un byte 255 al final del bloque reservado:
MALLOC MACRO n
maximo=100
REPT n
IF maximo EQ 0 ; ¿ya van 100?
EXITM ; abandonar REPT
ENDIF
maximo = maximo - 1
DB 0 ; reservar byte
ENDM
DB 255 ; byte de fin de bloque
ENDM
Como se vio al estudiar la directiva IF, existe la posibilidad de chequear condicionalmente la presencia de
un parámetro por medio de IFNB, o su ausencia con IFB. Uniendo esto a la potencia de IRP es posible crear
macros extraordinariamente versátiles. Como ejemplo, valga la siguiente macro, destinada a introducir en la
pila un número variable de parámetros (hasta 10): es especialmente útil en los programas que gestionan
interrupciones:
XPUSH AX,BX,DS,ES,VAR1
Se expandirá en:
PUSH AX
PUSH AX
PUSH DS
PUSH ES
PUSH VAR1
19 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
La ventaja es el número indefinido de parámetros soportados (no sólo 10). Un ejemplo de uso puede ser
el siguiente:
PUSH AX
PUSH BX
PUSH CX
POP CX
POP BX
POP AX
La programación modular consiste en dividir los problemas más complejos en módulos separados con
unas ciertas interdependencias, lo que reduce el tiempo de programación y aumenta la fiabilidad del código.
Se pueden implementar en ensamblador con las directivas PROC y ENDP que, aunque no generan código
son bastante útiles para dejar bien claro dónde empieza y acaba un módulo. Reglas para la buena
programación:
- Dividir los problemas en módulos pequeños relacionados sólo por un conjunto de parámetros de
entrada y salida.
- Una sola entrada y salida en cada módulo: un módulo sólo debe llamar al inicio de otro (con CALL)
y éste debe retornar al final con un único RET, no debiendo existir más puntos de salida y no siendo
recomendable alterar la dirección de retorno.
- Excepto en los puntos en que la velocidad o la memoria son críticas (la experiencia demuestra que
son menos del 1%) debe codificarse el programa con claridad, si es preciso perdiendo eficiencia. Ese 1%
documentarlo profusamente como se haría para que lo lea otra persona.
- Los módulos han de ser «cajas negras» y no deben modificar el entorno exterior. Esto significa que no
deben actuar sobre variables globales ni modificar los registros (excepto aquellos registros y variables en que
devuelven los resultados, lo que debe documentarse claramente al principio del módulo). Tampoco deben
depender de ejecuciones anteriores, salvo excepciones en que la propia claridad del programa obligue a lo
contrario (por ejemplo, los generadores de números aleatorios pueden depender de la llamada anterior).
Para el paso de parámetros entre módulos existen varios métodos que se exponen a continuación. Los
20 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
parámetros pueden pasarse además de dos maneras: directamente por valor, o bien indirectamente por
referencia o dirección. En el primer caso se envía el valor del parámetro y en el segundo la dirección inicial
de memoria a partir de la que está almacenado. El tipo de los parámetros habrá de estar debidamente
documentado al principio de los módulos.
- Paso de parámetros en los registros: Los módulos utilizan ciertos registros muy concretos para
comunicarse. Todos los demás registros han de permanecer inalterados, por lo cual, si son empleados
internamente, han de ser preservados al principio del módulo y restaurados al final. Este es el método
empleado por el DOS y la BIOS en la mayoría de las ocasiones para comunicarse con quien los llama. Los
registros serán preservados preferiblemente en la pila (con PUSH) y recuperados de la misma (con POP en
orden inverso); de esta manera, los módulos son reentrantes y pueden ser llamados de manera múltiple
soportando, entre otras características, la recursividad (sin embargo, se requerirá también que las variables
locales se generen sobre la pila).
- Paso de parámetros a través de un área común: se utiliza una zona de memoria para la comunicación.
Este tipo de módulos no son reentrantes y hasta que no acaben de procesar una llamada no se les debe llamar
de nuevo en medio de la faena.
- Paso de parámetros por la pila. En este método, los parámetros son apilados antes de llamar al módulo
que los va a recoger. Este debe conocer el número y tamaño de los mismos, para equilibrar el puntero de pila
al final antes de retornar (método de los compiladores de lenguaje Pascal) o en caso contrario el programa
que llama deberá encargarse de esta operación (lenguaje C). La ventaja del paso de parámetros por la pila es
el prácticamente ilimitado número de parámetros admitido, de cómodo acceso, y que los módulos siguen
siendo reentrantes. Un ejemplo puede ser el siguiente:
datoL DW ?
datoH DW ?
...
PUSH datoL ; apilar parámetros
PUSH datoH
CALL moduloA ; llamada
ADD SP,4 ; equilibrar pila
...
En el ejemplo, tenemos la variable dato de 32 bits dividida en dos partes de 16. Dicha variable es
colocada en la pila empezando por la parte menos significativa. A continuación se llama a MODULOA, el
cual comienza por preservar BP (lo usará posteriormente) para respetar la norma de caja negra. Se carga BP
con SP debido a que el 8086 no permite el direccionamiento indexado sobre SP. Como la instrucción CALL
se dirige a una dirección cercana (NEAR), en la pila se almacena sólo el registro IP. Por tanto, en [BP+0]
está el BP del programa que llama, en [BP+2] el registro IP del programa que llama y en [BP+4] y [BP+6] la
variable enviada, que es el caso más complejo (variables de 32 bits). Dicha variable es cargada en DX:AX
antes de proceder a usarla (también deberían apilarse AX y DX para conservar la estructura de caja negra).
Al final, se retorna con RET y el programa principal equilibra la pila aumentando SP en 4 unidades para
compensar el apilamiento previo de dos palabras antes de llamar. Si MODULOA fuera un procedimiento
lejano (FAR) la variable estaría en [BP+6] y [BP+8], debido a que al llamar al módulo se habría guardado
21 de 22 12/10/00 19:08
EL LENGUAJE ENSAMBLADOR DEL 80x86 file:///C|/librosVirtuales/UniversoDigital/05.html
también en la pila el CS del programa que llama. El lenguaje Pascal hubiera retornado con RET 4, haciendo
innecesario que el programa que llama equilibre la pila. Sin embargo, el método del lenguaje C expuesto es
más eficiente porque no requiere que el módulo llamado conozca el número de parámetros que se le envían:
éste puede ser variable (de hecho, el C apila los parámetros antes de llamar en orden inverso, empezando por
el último: de esta manera se accede correctamente a los primeros N parámetros que se necesiten).
22 de 22 12/10/00 19:08
EL ENSAMBLADOR EN ENTORNO DOS file:///C|/librosVirtuales/UniversoDigital/06.html
Antes de que el COMMAND.COM pase el control al programa que se pretende ejecutar, se crea un
bloque de 256 bytes llamado PSP (Program Segment Prefix), cuya descripción detallada se verá en el
próximo capítulo. En él aparecen datos tales como la dirección de retorno al dos cuando finalice el programa,
la dirección de retorno en caso de Ctrl-Break y en caso de errores críticos. Además de la cantidad de
memoria disponible y los posibles parámetros suministrados del programa. Cuando el programa toma el
control, DS y ES apuntan al PSP. Tipos de programas:
Si el programa es COM podemos terminarlo con la interrupción 20h (INT 20h), o simplemente con un
RET si la pila no está desequilibrada (apunta a un INT 20h que hay en la posición 0 del PSP); otra manera de
acabar es por medio de la función 4Ch del sistema (disponible desde el DOS 2.0) que acaba cualquier
programa sin problemas y sin ningún tipo de requerimientos adicionales, tanto COM como EXE.
Los programas de tipo COM se cargan en memoria tal y como están en disco, entregándoseles el control.
Los de tipo EXE, que pueden llegar a manejar múltiples segmentos de código de hasta 64 Kb, se almacenan
en disco «semiensamblados». En realidad, al ser cargados en memoria, el DOS tiene que realizar la última fase
de montaje, calculando las direcciones de memoria absolutas. Por ello, estos programas tienen un formato
especial en disco, generado por los ensambladores y compiladores, y su imagen en memoria no se
corresponde realmente con lo que está grabado en el disco, aunque esto al usuario no le importe. Por ello, no
se extrañe el lector de haber visto alguna vez ficheros EXE de más de 640 Kb: evidentemente, no se cargan
enteros en memoria aunque lo parezca. Los programas COM no hacen referencias a datos o direcciones
separados más de 64 Kb, por lo que todos los saltos y desplazamientos son relativos a los registros de
segmento (no se cambia CS ni DS) con lo que no es necesaria la fase de «montaje». No obstante, un
programa COM puede hacer lo que le de la gana con los registros de segmento y acceder a más de 64 Kb
de memoria, por cuenta y riesgo del programador. En general, la programación en ensamblador está hoy en
día relegada a pequeños programas residentes, controladores de dispositivos o rutinas de apoyo a programas
hechos en otros lenguajes, por lo que no es estrictamente necesario trabajar con programas EXE realizados
en ensamblador. Salvo excepciones, la mayoría de los programas desarrollados en este libro serán de tipo
COM ya que los EXE ocuparían algo más, aunque el ensamblador da algo más de comodidad al
programador en los mismos.
El siguiente ejemplo escribe una cadena en pantalla llamando a uno de los servicios estándar de impresión
del DOS (función 9 de INT 21h):
1 de 14 12/10/00 19:09
EL ENSAMBLADOR EN ENTORNO DOS file:///C|/librosVirtuales/UniversoDigital/06.html
Olvidándonos de los comentarios que comienzan por «;», en las primeras lineas las directivas EQU definen
dos constantes para el preprocesador del compilador: cr=13 y lf=10. El programa, de tipo COM, consta de
un único segmento. La directiva ASSUME indica que, por defecto, las instrucciones máquina se ensamblarán
para el registro CS en este segmento (lo más lógico, por otra parte); también conviene asumir el registro DS,
de lo contrario, si hubiera que acceder a una variable, el ensamblador añadiría el prefijo del segmento CS a la
instrucción al no estar seguro de que DS apunta a los datos, consumiendo más memoria. Se pueden añadir los
demás registros de segmento en el ASSUME, aunque es redundante. El ORG 100h es obligatorio en
programas COM, ya que estos programas serán cargados en memoria en la posición CS:100h. Al final, la
dirección del texto a imprimir se coloca en DS:DX (CS=DS=ES=SS en un programa COM recién ejecutado)
y se llama al DOS. El carácter '$' delimita la cadena a imprimir, lo cual es una herencia del CP/M (sería más
interesante que fuera el 0 el delimitador) por razones históricas. Se acaba el programa con INT 20h. El punto
de arranque es indicado con la directiva END, aunque en realidad en los programas COM el punto indicado
(en el ejemplo, «inicio») debe estar forzosamente al principio del programa. Obsérvese que no se genera
código hasta llegar a la línea «inicio:», todo lo anterior son directivas.
Los programas EXE (listado al final de esta sección) requieren algo más de elaboración. En primer lugar,
es necesario definir una pila y reservar espacio para la misma. Al contrario que los programas COM (cuya
pila se sitúa al final del segmento compartido también con el código y los datos) esta característica obliga a
definir un tamaño prudente en función de las necesidades del programa. Téngase en cuenta que en la pila se
almacenan las direcciones de retorno de las subrutinas y al llamar a una función de la BIOS la pila es usada
con intensidad. En general, con medio kilobyte basta para programas tan sencillos como el del ejemplo, e
incluso para otros mucho más complejos. El límite máximo está en 64 Kb. El segmento de pila se nombra
siempre STACK y con el TLINK de Borland es necesario indicar también la clase 'STACK'.
Como se ve, son definidos por separado el segmento de código, pila y datos, lo que también ayuda a
estructurar más el programa. El segmento de código se define como procedimiento FAR, entre otras razones
para que el ensamblador ensamble el RET del final (con el que se vuelve al DOS) como un RETF. La
directiva ASSUME asocia cada registro de segmento con su correspondiente segmento. Como puede
observarse al principio del programa, es necesario preparar «a mano» la dirección de retorno al sistema. El
PUSH DS del principio coloca el segmento del PSP en la pila; el XOR AX,AX coloca un cero en AX (esta
instrucción gasta un byte menos que MOV AX,0) y el PUSH AX mete ese 0 en la pila. Con ello, al volver al
DOS con RET (RETF en realidad) el control pasará a DS:0, esto es, a la primera instrucción del PSP (INT
20h). Aunque pueda parecer un tanto lioso, es un juego de niños y estas tres instrucciones consecutivas
(PUSH DS / XOR AX,AX / PUSH AX) son la manera de empezar de cientos de programas EXE, que
después acaban con RET. En general, a partir del DOS 2.0 es más aconsejable terminar el programa con la
función 4Ch del DOS, que no requiere que CS apunte al PSP ni precisa de preparación alguna en la pila y
2 de 14 12/10/00 19:09
EL ENSAMBLADOR EN ENTORNO DOS file:///C|/librosVirtuales/UniversoDigital/06.html
además permite retornar un código de ERRORLEVEL en AL: en los programas futuros esto se hará con
bastante frecuencia.
También debe observarse cómo se inicializa DS, ya que en los programas EXE por defecto no apunta a
los datos. Ahora puede preguntarse el lector, por curiosidad, ¿qué valdrá «datos»?: datos tiene un valor
relativo asignado por el ensamblador; cuando el programa sea cargado en memoria, en el proceso de montaje
y en función de cuál sea la primera posición de memoria libre, se le asignará un valor determinado por el
montador del sistema operativo.
cr EQU 13
lf EQU 10
; Segmento de datos
datos SEGMENT
texto DB cr,lf,"Texto a imprimir",cr,lf,"$"
datos ENDS
; Segmento de pila
; Segmento de código
codigo SEGMENT
ejemplo PROC FAR
ASSUME CS:codigo, DS:datos, SS:pila
; escribir texto
; volver al DOS
ejemplo ENDP
3 de 14 12/10/00 19:09
EL ENSAMBLADOR EN ENTORNO DOS file:///C|/librosVirtuales/UniversoDigital/06.html
6.4.1. - TASM/MASM.
Es el programa que convierte nuestro listado fuente en código objeto, es decir, lenguaje máquina en el que
sólo faltan las referencias a rutinas externas. Permite la obtención de listados de código y de referencias
cruzadas (símbolos, etiquetas, variables). En general, bastará con hacer TASM nombre_programa (se supone
la extensión .ASM por defecto). El fichero final tiene extensión OBJ. En general, la sintaxis del TASM y
MASM es más o menos equivalente: en el primero se obtiene ayuda con /H y en el segundo con /HELP. Con
TASM, cuando se va a obtener la versión definitiva del programa, o si éste es corto -o el ordenador rápido-
merece la pena utilizar el parámetro /m3, con objeto de que de dos/tres pasadas y optimize más el código.
Por su lado, MASM presenta estadísticas adicionales si se indica /v y se puede cambiar con /Btamaño el nº
de Kb de memoria que destina al fichero fuente, entre 1 y 63. La sintaxis es (tanto para TASM como
MASM):
Se puede omitir el fichero de listado y el de referencias cruzadas. Cuando se emplea MASM 6.X, para
ensamblar los listados de este libro hay que indicar la opción /Zm para mantener la compatibilidad con las
versiones anteriores del ensamblador, siendo además obligatorio indicar la extensión; como se genera
directamente el fichero EXE hay que indicar /c si se desea evitar esto (si no se quiere que linke). La sintaxis
quedaría:
ML /Zm fihero_fuente.asm
A continuación se listan los parámetros comunes a TASM 2.0 (y posterior) y MASM 4.0/5.0 (NO la 6.X):
/a y /s Seleccionan un orden alfabético o secuencial de los segmentos.
Genera un listado de referencias cruzadas en un fichero de extensión CRF listo para ser procesado
por CREF (MASM) añadiendo además números de línea al listado, o bien incluye el listado de
/c referencias cruzadas directamente dentro del listado del programa (caso de TASM). Las referencias
cruzadas son un listado de todos los símbolos del programa, indicando los números de línea del mismo
en que son definidos y referenciados.
De la manera /Dsímbolo[=valor] permite crear el símbolo indicado, cuya presencia puede
comprobarse en el programa con una directiva IF (es útil para definir externamente un símbolo que
indique que el programa está en fase de depuración, de cara a ensamblar cierto código adicional).
/D
Aunque /d (en minúsculas) es un obsoleto parámetro de MASM para obtener un listado de la primera
pasada del ensamblador, MASM 4.0 es capaz de darse cuenta de que se pretende definir un símbolo
con /d a menos que se indique solo /d.
/e Emula las instrucciones de punto flotante del 80x87, apoyándose en una librería al efecto.
Permite indicar el directorio donde el ensamblador debe de buscar los ficheros indicados en el
/Iruta
programa fuente con INCLUDE.
/l[a] Con /l se genera un listado de ensamblaje y con /la un listado expandido.
Con /m se indica el nivel de preservación del sentido de mayúsculas y minúsculas en los símbolos: /ml
hace que se consideres diferentes mayúsculas de minúsculas en todos los símbolos, /mx sólo con los
/m símbolos globales y /mu hace que se mayusculicen todos los símbolos globales. Al ensamblar módulos
para usar desde lenguaje C hay que indicar por lo menos /mx. En MASM 6.X se emplea /Cx en
lugar de /mx, /Cp en lugar de /ml y /Cu en vez de /mu.
/n Suprime las tablas de símbolos en el listado.
Verifica que el código generado para el modo protegido es correcto (al emplear la directiva para
/p
generar instrucciones de modo protegido).
/t Suprime los mensajes si el ensamblaje es correcto.
4 de 14 12/10/00 19:09
EL ENSAMBLADOR EN ENTORNO DOS file:///C|/librosVirtuales/UniversoDigital/06.html
6.4.2. - TLINK/LINK.
El montador o linkador permite combinar varios módulos objeto, realizando las conexiones entre ellos y,
finalmente, los convierte en módulo ejecutable de tipo EXE (empleando el ML de MASM 6.X se obtiene
directamente el fichero EXE ya que invoca automáticamente al linkador). El linkador permite el uso de
librerías de funciones y rutinas. TLINK, a diferencia de LINK, permite generar un fichero de tipo COM
directamente de un OBJ si se indica el parámetro /t, lo que agiliza aún más el proceso. Puede obtenerse ayuda
ejecutándolo sin parámetros. Los parámetros de TLINK son sensibles a mayúsculas y minúsculas, por lo que
/T no es lo mismo que /t. Con LINK se obtiene ayuda indicando /HELP. Aunque los parámetros de uno y
otro son bastante distintos, la sintaxis genérica de ambos es:
Los ficheros no necesarios se pueden omitir (o indicar NUL): para linkar el fichero prog1.obj y el
prog2.obj con la librería math.lib generando PROG1.EXE basta con ejecutar TLINK prog1+prog2,,,math.
Alternativamente se puede indicar TLINK @fichero para que tome los parámetros del fichero de texto
FICHERO, en el caso de que estos sean demasiados y sea incómodo teclearlos cada vez que se linka. Los
ficheros de texto de extensión MAP contienen información útil para el programador sobre la distribución de
memoria de los segmentos.
6.4.3. - EXE2BIN.
Los ficheros EXE generados por TLINK o LINK no son copia exacta de lo que aparece en la memoria,
sino que el DOS -tras cargarlos- debe realizar una última operación de «montaje». Un programa COM en
memoria es una copia del fichero del disco, es algo más corto y más sencillo de desensamblar. Al contrario de
lo que algunos opinaron en su día, el tiempo ha demostrado que nunca llegarían a ser directamente
compatibles con los actuales entornos multitarea.
EXE2BIN permite transformar un fichero EXE en COM siempre que el módulo ocupe menos de 64K y
que esté ensamblado con ORG 100h. Si no se indicó el parámetro /t en TLINK, será necesario este
programa (al igual que cuando se utiliza LINK). Cuando se crean programas SYS (que se diferencian de los
COM básicamente en que no tienen ORG 100h) no se puede ejecutar TLINK /t, por lo que es necesaria la
ayuda de EXE2BIN para convertir el programa EXE en SYS. Sintaxis:
Si el programa no contiene ORG 100h, EXE2BIN genera un fichero binario puro de extensión BIN. Si
además existen referencias absolutas a segmentos, EXE2BIN preguntará el segmento en que va a correr
(algunas versiones permiten indicarlo de la manera /Ssegmento): esto permite generar código para ser
ejecutado en un segmento determinado de la memoria (como pueda ser una memoria EPROM o ROM).
6.4.4. - TLIB/LIB.
El gestor de librerías permite reunir módulos objeto en un único fichero para poder tomar de él las rutinas
5 de 14 12/10/00 19:09
EL ENSAMBLADOR EN ENTORNO DOS file:///C|/librosVirtuales/UniversoDigital/06.html
que se necesiten en cada caso. En este libro no se desarrollan programas tan complejos que justifiquen su
utilización. En cualquier caso, la sintaxis es la siguiente:
Por ejemplo, para añadir el módulo QUICK.OBJ, borrar el SLOW.OBJ y reemplazar el SORT.OBJ por
una nueva versión en LIBRERIA.LIB se ejecutaría:
Si la lista es muy larga se puede incluir en un fichero y ejecutar TLIB @fichero para que la lea del mismo
(si no cabe en una línea del fichero, puede escribirse & al final antes de pasar a la siguiente).
6.4.5. TCREF/CREF.
Esta utilidad genera listados en orden alfabético de los símbolos, como ayuda a la depuración. Con el
MASM la opción /c crea un fichero de referencias cruzadas de extensión CRF (respondiendo afirmativamente
cuando pregunta por el mismo o indicándolo explícitamente en la línea de comandos); la opción /c de TASM
lo incluye en el listado, aunque si se indica el nombre del fichero de referencias cruzadas genera un fichero de
extensión XRF. CREF y TCREF interpretan respectivamente los ficheros CRF y XRF generando un fichero
de texto con extensión REF que contiene el listado de referencias cruzadas. Ej.:
TASM fichero,,,fichero
TCREF fichero
Las referencias cruzadas son un listado de todos los símbolos del programa, indicando los números de
línea del mismo en que son referenciados (la línea en que son definidos se marca con #); estos números de
línea son relativos al listado de ensamblaje del programa (y no al fichero fuente). Es útil para depurar
programas grandes y complejos.
6.4.6. - MAKE.
Esta utilidad se apoya en unos ficheros especiales, al estilo de los BAT del DOS, de cara a automatizar el
proceso de ensamblaje. Sólo es recomendable para programas grandes, divididos en módulos, en los que
MAKE chequea la fecha y hora para ensamblar sólo las partes que hayan sido modificadas.
La utilidad DEBUG incluída en los sistemas MS-DOS, es una herramienta para depuración de programas
muy interesante que permite desensamblar los módulos y, además, ejecutar programas paso a paso, viendo
las modificaciones que sufren los registros y banderas. Se trata de un programa menos complejo, cómodo y
potente que depuradores de código como Turbo Debugger (de Borland) o Codeview (Microsoft), pero en
6 de 14 12/10/00 19:09
EL ENSAMBLADOR EN ENTORNO DOS file:///C|/librosVirtuales/UniversoDigital/06.html
algunos casos es más útil. Veremos ahora los principales comandos del DEBUG, los cuales también son
admitidos en su mayoría por Codeview, por lo que el tiempo invertido en aprenderlos será útil no sólo para
conocer el clásico y mítico DEBUG.
Antes de empezar con ellos, conviene hacer referencia al programa SYMDEB que acompaña al MASM
de Microsoft: se trata de un DEBUG mejorado, con ayuda, más rápido e inteligente (indica el tipo de función
del sistema cuando al tracear un programa éste llama al DOS) y, en la práctica, es 99% compatible. También
admite las instrucciones adicionales del 286 y los NEC V20/V30. Su diferencia principal es que al
abandonarlo para volver al DOS restaura los vectores de interrupción, lo que puede no ser deseable en
algunos casos muy concretos. Además, desde la versión 4.0 se admite el parámetro /S (con SYMDEB /S
nomfich.ext) lo que permite conmutar entre la pantalla de depuración y la de ejecución pulsando la tecla '\'.
Los programas pueden ser de tipo EXE o COM; en el caso de los primeros se les cargará ya montados y
con los registros inicializados, listos para su ejecución. Evidentemente, los programas COM también se
cargan con los registros inicializados y el correspondiente PSP preparado, así como con IP=100h. Los
parámetros opcionales no son los de el DEBUG o SYMDEB sino los que normalmente se suministrarían al
programa a depurar. También se pueden cargar otros ficheros de cualquier extensión o simplemente entrar en
el programa sin cargar ningún fichero. Al entrar, aparecerá el prompt particular del DEBUG: un guión (-).
Entonces se pueden teclear órdenes que constarán generalmente de una sola letra. La mayoría de las mismas
admiten parámetros, que normalmente irán separados por comas. Estos parámetos pueden ser números
hexadecimales de hasta dos o cuatro dígitos, registros y, además:
- Cadenas de caracteres: Encerradas entre comillas simples o dobles. El texto puede a su vez encerrar
fragmentos entrecomillados, empleando comillas distintas a las más exteriores. Ejemplo:
La cadena 'ES:' no será bien traducida a sus correspondientes valores ASCII. Con DEBUG este
problema no existe.
- Direcciones: Pueden expresarse con sus correspondientes valores numéricos o bien apoyándose en algún
registro de segmento, aunque el offset siempre será numérico: 1E93:AD21, CS:100, ES:19AC
El depurador SYMDEB es mucho más flexible y permite también emplear registros de propósito
general en el offset. Sería válida la dirección DS:BX+AX+104.
- Rangos: Son dos direcciones separadas por una coma; o bien una dirección, la letra 'L' y un valor
numérico que indica el número de bytes a partir de la dirección.
El DEBUG del MS-DOS 5.0 y el SYMDEB poseen una ayuda invocable con el comando ?, en la que se
resumen las principales órdenes. A continuación se listan las más interesantes:
7 de 14 12/10/00 19:09
EL ENSAMBLADOR EN ENTORNO DOS file:///C|/librosVirtuales/UniversoDigital/06.html
* A [<dirección>] (assemble): permite ensamblar a partir de CS:IP si no se indica una dirección concreta.
Se admiten las directivas DB y DW del ensamblador. Las instrucciones que requieran indicar un registro de
segmento, con DEBUG hay que ponerlas en una sola línea. Por ejemplo:
XLAT CS: ; mal ensamblado con DEBUG (no así con SYMDEB)
MOV WORD PTR ES:[100],1234 ; error en DEBUG (sí vale con SYMDEB)
CS: ; bien emsamblado con ambos
XLAT
ES: ; y esto también
MOV WORD PTR [100],1234
Los saltos inter-segmento deben especificarse como FAR (ej., CALL FAR [100]) a no ser que sea
evidente que lo son (ej. CALL 1234:5678).
* E <dirección> [<lista>] (enter): permite consultar y modificar la memoria, byte a byte. Por ejemplo, con
E 230 1,2,3 se introducirían los bytes 1, 2 y 3 a partir de DS:230. Si no se indica <lista>, se visualizará la
memoria byte a byte, pudiéndose modificar los bytes deseados, avanzar al siguiente (barra espaciadora) o
retroceder al anterior (signo -). Para acabar se pulsa RETURN.
* R [<registro>] (register): permite visualizar y modificar el valor de los registros. Por ejemplo, si se
ejecuta la orden 'rip', se solicitará un nuevo valor para IP; con RF se muestran los flags y se permite modificar
alguno:
* T [<veces>] (trace): ejecuta una instrucción del programa (a partir de CS:IP) mostrando a continuación
el estado de los registros y la siguiente instrucción. Ejecutar T10 equivaldría a ejecutar 16 veces el comando
T. Si la instrucción es CALL o INT, se ejecutará como tal introduciéndose en la subrutina o servidor de
8 de 14 12/10/00 19:09
EL ENSAMBLADOR EN ENTORNO DOS file:///C|/librosVirtuales/UniversoDigital/06.html
* P [<veces>] (proceed): similar al comando T, pero al encontrarse un CALL o INT lo ejecuta de golpe
sin entrar en su interior (ojo, ¡esto último falla al tracear sobre memoria ROM!).
* S <rango> <lista> (search): busca una cadena de bytes por la memoria. Para buscar la cadena "PEPE"
terminada por cero en un área de 512 bytes desde DS:100 se haría: S 100 L 200 "PEPE",0 (por defecto se
busca en DS:). No se encontraría sin embargo "pepe" (en minúsculas).
* F <rango> <lista> (fill): llena la zona de memoria especificada con repeticiones de la lista de bytes
indicada. Por ejemplo, para rellenar códigos 0AAh 100h bytes a partir de 9800h:0 se ejecutaría F 9800:0 L
100 AA; en vez de AA se podría haber indicado una lista de bytes o cadenas de caracteres.
* C <rango> <dirección> (compare): compara dos zonas de memoria mostrando las diferencias. Por
ejemplo, para comparar 5 bytes de DS:100 y DS:200 se hace: C 100 L 5 200.
* M <rango> <dirección> (move): Más que mover, copia una zona de memoria en otra de manera
inteligente (controlando los posibles solapamientos de los bloques).
* H <valor1> <valor2> (hexaritmetic): muestra la suma y resta de valor1 y valor2, ambos operandos de
un máximo de 16 bits (si hay desbordamiento se trunca el resultado, que tampoco excede los 16 bits).
También existen comandos en DEBUG para acceder a la memoria expandida: XS (obtener el estado de la
memoria expandida), XA npag (localizar npag páginas), XD handle (desalojar el handle indicado) y XM
pagina_logica pagina_fisica handle (mapear páginas).
Con SYMDEB pueden además colocarse, con suma facilidad, puntos de ruptura (breakpoints); con
9 de 14 12/10/00 19:09
EL ENSAMBLADOR EN ENTORNO DOS file:///C|/librosVirtuales/UniversoDigital/06.html
DEBUG se pueden implementar con la orden G (indicando más de una dirección hasta un máximo de 10,
donde debe detenerse el programa si pasa por ellas) aunque es más incómodo. En SYMDEB se pueden
definir con BP dirección, borrarse con BC num_breakpoint, habilitarse con BP num_breakpoint (necesario
antes de emplearlos), deshabilitarse con BD num_breakpoint y listar los definidos con BL. Además,
SYMDEB puede visualizar datos en coma flotante de 32, 64 y 80 bits con el comando D (DS, DL y DT).
SYMDEB es realmente un depurador simbólico (SYMbolic DEBugger) que permite mostrar información
adicional y depurar con mayor comodidad los programas que han sido ensamblados con información de
depuración.
Una posibilidad interesante de DEBUG y SYMDEB es que admiten el redireccionamiento del sistema
operativo. Ello permite, por ejemplo, crear ficheros ASCII con órdenes y después suministrárselas al
programa, como en el siguiente ejemplo: DEBUG < ORDENES.TXT. La última orden de este fichero deberá
ser Q (quit), de lo contrario no se devolvería el control al DOS ni se podría parar el programa (la entrada por
defecto -el teclado- no actúa). También es versátil la posibilidad de redireccionar la salida. Por ejemplo, tras
DEBUG > SALIDA.TXT, se puede teclear un comando para desensamblar (U) y otro para salir (Q): en el
disco aparecerá el fichero con los datos del desensamblaje (se teclea a ciegas, lógicamente, porque la salida
por pantalla ha sido redireccionada al fichero). Por supuesto, también es posible redireccionar entrada y salida
a un tiempo: DEBUG < ORDENES.TXT > SALIDA.
El código de la BIOS, almacenado en las memorias ROM del ordenador, constituye la primera capa de
software de los ordenadores compatibles. La BIOS accede directamente al hardware, liberando a los
programas de usario de las tareas más complejas. Parte del código de la BIOS es actualizado durante el
arranque del ordenador, con los ficheros que incluye el sistema operativo. El sistema operativo o DOS
propiamente dicho se instala después: el DOS no realiza ningún acceso directo al hardware, en su lugar se
apoya en la BIOS, constituyendo una segunda capa de software. El DOS pone a disposición de los
programas de usuario unas funciones muy evolucionadas para acceder a los discos y a los recursos del
ordenador. Por encima del DOS se suele colocar habitualmente al COMMAND.COM, aunque realmente el
COMMAND no constituye capa alguna de software: es un simple programa de utilidad, como cualquier otro,
ejecutado sobre el DOS y que además no pone ninguna función a disposición del sistema (al menos,
documentada), su única misión es cargar otros programas.
FUNCIONES DE LA BIOS
Las funciones de la BIOS se invocan, desde los programas de usuario, ejecutando una interrupción
software con un cierto valor inicial en los registros. La BIOS emplea un cierto rango de interrupciones, cada
una encargada de una tarea específica:
10 de 14 12/10/00 19:09
EL ENSAMBLADOR EN ENTORNO DOS file:///C|/librosVirtuales/UniversoDigital/06.html
INT 1Fh: Apunta a la tabla de los caracteres ASCII 128-255 (8x8 puntos).
La mayoría de las interrupciones se invocan solicitando una función determinada (que se indica en el
registro AH al llamar) y se limitan a devolver un resultado en ciertos registros, realizando la tarea solicitada. En
general, sólo resultan modificados los registros que devuelven algo, aunque BP es corrompido en los servicios
de vídeo de las máquinas más obsoletas.
El DOS emplea varias interrupciones, al igual que la BIOS; sin embargo, cuando se habla de funciones del
DOS, todo el mundo sobreentiende que se trata de llamar a la INT 21h, la interrupción más importante con
diferencia.
Las funciones del DOS se invocan llamando a la INT 21h e indicando en el registro AH el número de
función a ejecutar. Sólo modifican los registros en que devuelven los resultados, devolviendo normalmente el
acarreo activo cuando se produce un error (con un código de error en el acumulador). Muchas funciones de
los lenguajes de programación frecuentemente se limitan a llamar al DOS.
En general, se debe intentar emplear siempre las funciones que requieran la menor versión posible del
DOS; sin embargo, no es necesario buscar la compatibilidad con el DOS 1.0: esta versión no soporta
subdirectorios, y el sistema de ficheros se basa en el horroroso método FCB. Los FCB ya no están
soportados siquiera en la ventana de compatibilidad DOS de OS/2, siendo recomendable ignorar su
existencia y trabajar con los handles, al estilo del UNIX, que consisten en unos números que identifican a los
ficheros cuando son abiertos. Existen 5 handles predefinidos permanentemente abiertos: 0 (entrada estándar
-teclado-), 1 (salida estándar -pantalla-), 2 (salida de error estándar -también pantalla-), 3 (entrada/salida por
puerto serie) y 4 (salida por impresora): la pantalla, el teclado, etc. pueden ser manejados como simples
ficheros.
11 de 14 12/10/00 19:09
EL ENSAMBLADOR EN ENTORNO DOS file:///C|/librosVirtuales/UniversoDigital/06.html
Las funciones precedidas de un asterisco son empleadas o mencionadas en este libro, y pueden
consultarse en el apéndice al efecto al final del mismo.
ENTRADA/SALIDA DE CARACTERES
GESTION DE FICHEROS
12 de 14 12/10/00 19:09
EL ENSAMBLADOR EN ENTORNO DOS file:///C|/librosVirtuales/UniversoDigital/06.html
MANEJO DE DISCO
CONTROL DE PROCESOS
GESTION DE MEMORIA
FUNCIONES MISCELANEAS
13 de 14 12/10/00 19:09
EL ENSAMBLADOR EN ENTORNO DOS file:///C|/librosVirtuales/UniversoDigital/06.html
14 de 14 12/10/00 19:09
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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.
Interrupciones internas o excepciones: Las genera la propia CPU cuando se produce una situación
anormal o cuando llega el caso. Por desgracia, IBM se saltó olímpicamente la especificación de Intel
que reserva las interrupciones 0-31 para el procesador.
INT 0: error de división, generada automáticamente cuando el cociente no cabe en el registro o
el divisor es cero. Sólo puede ser generada mediante DIV o IDIV. Hay una sutil diferencia de
comportamiento ante esta interrupción según el tipo de procesador: el 8088/8086 y los NEC
V20 y V30 almacenan en la pila, como cabría esperar, la dirección de la instrucción que sigue a
la que causó la excepción. Sin embargo, el 286 y superiores almacenan la dirección del DIV o
IDIV que causa la excepción.
INT 1: paso a paso, se produce tras cada instrucción cuando el procesador está en modo traza
(utilizada en depuración de programas).
INT 2: interrupción no enmascarable, tiene prioridad absoluta y se produce incluso aunque estén
inhibidas las interrupciones (con CLI) para indicar un hecho muy urgente (fallo en la alimentación
o error de paridad en la memoria).
INT 3: utilizada para poner puntos de ruptura en la depuración de programas, debido a que es
una instrucción de un solo byte muy cómoda de utilizar.
INT 6: código de operación inválido (sólo a partir del 286). Se produce al ejecutar una
instrucción indefinida, en la pila se almacena el CS:IP de la instrucción ilegal.
1 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
Interrupciones hardware: Son las generadas por la circuitería del ordenador en respuesta a algún
evento. Las más importantes son:
INT 8: Se produce con una frecuencia periódica determinada por el canal 0 del chip
temporizador 8253/8254 (en la práctica, unas 18,2 veces por segundo). Como desde esta
interrupción se invoca a su vez a INT 1Ch -porque así lo dispuso IBM-, es posible ligar un
proceso a INT 1Ch para que se ejecute periódicamente.
INT 0Ah, 0Bh, 0Ch, 0Dh, 0Eh, 0Fh: Puertos serie, impresora y controladores de disquete.
INT 70h, 71h, 72h, 73h, 74h, 75h, 76h, 77h: Generadas en los AT y máquinas superiores por
el segundo chip controlador de interrupciones.
Interrupciones software: Producidas por el propio programa (instrucción INT) para invocar ciertas
subrutinas. La BIOS y el DOS utilizan algunas interrupciones a las que se puede llamar con
determinados valores en los registros para que realicen ciertos servicios. También existe alguna que
otra interrupción que se limita simplemente a apuntar a modo de puntero a una tabla de datos.
Los vectores de interrupción pueden ser desviados hacia un programa propio que, además, podría quedar
residente en memoria. Si se reprograma por completo una interrupción y ésta es de tipo hardware, hay que
realizar una serie de tareas adicionales, como enviar una señal fin de interrupción hardware al chip controlador
de interrupciones. Si se trata además de la interrupción del teclado del PC o XT, hay que enviar una señal de
reconocimiento al mismo ... en resumen: conviene documentarse debidamente antes de intentar hacer nada.
Todos estos problemas se evitan si la nueva rutina que controla la interrupción llama al principio (o al final) al
anterior gestor de la misma, que es lo más normal, como se verá más adelante.
1. «El elegante»: es además el más cómodo y compatible. De hecho, algunos programas de DOS
funcionan también bajo OS/2 si han sido diseñados con esta técnica. Basta con llamar al servicio 25h
del DOS (INT 21h) y decirle qué interrupción hay que desviar y a dónde:
2. El «psé»: es menos seguro y compatible (ningún programa que emplea esta técnica corre en OS/2) y
consiste en hacer casi lo que hace el DOS pero sin llamarle. Es además mucho más incómodo y largo,
pero muy usado por programadores despistados:
3. El «método correcto» es similar al «psé», consiste en cambiar el vector «de un tirón» (cambiar a la vez
2 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
segmento y offset con un REP MOVS) con objeto de evitar una posible interrupción no enmascarable
que se pueda producir en ese momento crítico en que ya se ha cambiado el offset pero todavía no el
segmento (CLI no inhibe la interrupción no enmascarable). Este sistema es todavía algo más engorroso,
pero es el mejor y es el que utiliza el DOS en el método (1).
4. El «método incorrecto» es muy usado por los malos programadores. Es similar al «psé» sólo que sin
inhibir las interrupciones mientras se cambia el vector, con el riesgo de que se produzca una
interrupción cuando se ha cambiado sólo medio vector. Los peores programadores lo emplean sobre
todo para cambiar INT 8 ó INT 1Ch, que se producen con una cadencia de 18,2 veces por segundo.
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.
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
3 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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:
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)
4 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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.
5 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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
6 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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.
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.
7 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
#include <dos.h>
main()
{
struct REGPACK r;
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.
8 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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.
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).
9 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
FIGURA 7.4.3.3:
/*********************************************************************
10 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
#include <dos.h>
#include <graphics.h>
void main()
{
struct REGPACK r;
int gdrv, gmodo, coderr, i, x, color, pixel;
char paleta[17];
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
11 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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];
12 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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();
}
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()
{
13 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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,
14 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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.
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;
15 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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-.
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 *
; * *
; ********************************************************************
red SEGMENT
ASSUME CS:red, DS:red
ORG 100h
inicio:
16 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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»
17 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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
18 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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.
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.
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
19 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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 *
; * *
; ********************************************************************
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
20 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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
21 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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
22 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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
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.
23 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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
24 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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>
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);
25 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
if (banco!=bancos-1)
limite=32768; /* todo el segmento de 64 Kb */
else
limite=(vram-banco*64)*512; /* palabras último banco */
unsigned testvesa(void)
{
struct REGPACK r;
char far *mem;
unsigned vesa;
26 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
7.5. - EL TECLADO.
En este apartado se estudiará a fondo el funcionamiento del teclado en los ordenadores compatibles, a tres
niveles: bajo, intermedio y alto. En el capítulo 12 se documenta el funcionamiento del hardware del teclado,
interesante para ciertas aplicaciones concretas, aunque para la mayor parte de las labores de programación no
es necesario llegar a tanto.
27 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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).
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
28 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
(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:
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.
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
29 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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
30 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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:
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
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.
31 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
- 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.
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).
- 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).
32 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
- 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.
Normalmente no será necesario distinguir entre un teclado expandido o estándar, aunque en algunos casos
habrá que tener en cuenta la posible pulsación de una tecla expandida y su código 0E0h asociado. En todo
caso, el bit 4 de 0040h:0096h indica si el teclado es expandido; sin embargo es suicida fiarse de esto y es más
seguro chequear por otros medios la presencia de funciones de la BIOS para teclado expandido antes de
usarlas. En teoría, las BIOS de AT del 15 de noviembre de 1985 en adelante soportan las funciones 5, 10h y
11h; los de XT a partir del 10 de enero de 1986 soportan la 10h y la 11h. Sin embargo, en la práctica todas
ellas normalmente están disponibles también en cualquier máquina más antigua si tiene instalado un KEYB
eficiente, venga equipada o no con teclado expandido. Por ello, lo ideal es chequear la presencia de estas
funciones por otros procedimientos. Por ejemplo: llamar a la función 12h con AL=0. Por desgracia, si la
función no está implementada no devuelve el acarreo activo para indicar el error. Pero hay un truco: si el
resultado sigue siendo AX=1200h, las funciones de teclado expandido no están soportadas. Esto se debe a
que al no estar implementada la función, nadie ha cambiado el valor de AX: además, en caso de estar
implementada no podría devolver 1200h porque ello significaría una contradicción entre AH y AL.
MOV AX,1200h
INT 16h ; invocar función teclado expandido
CMP AX,1200h
JE no_expandido ; función no soportada
JMP si_expandido ; función soportada
Posibilidades avanzadas.
La rutina de la BIOS del AT (y de los KEYB) que lee el buffer del teclado, cuando no hay teclas y tiene
que esperar por las mismas ejecuta de manera regular la función 90h (AH=90h) de la interrupción 15h
indicando una espera de teclado al llamar (AL=2). De esta manera, un hipotético avanzado sistema operativo
podría aprovechar ese tiempo muerto para algo más útil. Así mismo, cuando un carácter acaba de ser
introducido en el buffer del teclado, se ejecuta la función 91h para indicar que ya ha finalizado la entrada y hay
caracteres disponibles. En general, estas características no son útiles en el entorno DOS y, por otra parte, han
sido deficientemente normalizadas. Por ejemplo, al acentuar incorrectamente se generan dos caracteres
(además del familiar pitido): el KEYB del MS-DOS sólo ejecuta una llamada a la INT 15h con la función 91h
(pese a haber introducido dos caracteres en el buffer) y el de DR-DOS hace las dos llamadas...
Lo que sí puede resultar más interesante es la función de intercepción de código del teclado: las BIOS de
AT no demasiado antiguas y el programa KEYB, tras leer el código de rastreo en AL, activan el acarreo y
ejecutan inmediatamente la función 4Fh de la INT 15h para permitir que alguien se de por enterado de la
tecla y opcionalmente aproveche para manipular AL y simular que se ha pulsado otra tecla: ese alguien puede
devolver además el acarreo borrado para indicar al KEYB que no continúe procesando esa tecla y que la
ignore (en caso contrario se procedería a interpretarla normalmente). Para verificar si esta función está
disponible en la BIOS basta con ejecutar la función 0C0h de la INT 15h que devuelve un puntero en ES:BX
y comprobar que el bit 4 de la posición direccionada por ES:[BX+5] está activo. Alternativamente, puede
verificarse la presencia del programa KEYB, lo que también permite emplear esta función en los PC/XT,
aunque es más arriesgado. Para detectar la presencia del KEYB del MS-DOS en memoria basta con llamar a
33 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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.
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.
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,
34 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
los sectores lógicos se relacionan con la estructura física por la siguiente fórmula:
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.
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).
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 |
35 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
| 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.
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 con
| esta doble palabra en versiones del sistema 3.30 o anteriores (n
| que en todas sus versiones, hasta la 6.0 incluida, es un DOS 3.3
36 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
El byte del tipo de disco (offset 21) intenta identificar el tipo de disco, aunque no lo consigue en muchos
casos dada la ilógica utilización que se ha hecho de él. La recomendación es hacer lo que viene haciendo el
DOS desde la 3.30: no hacer caso de lo que dice este byte para identificar los discos. La única excepción tal
vez sea el valor 0F8h que identifica a los dispositivos no removibles:
+---------------------------------------------------------------------+
| 0FEh - discos de 5¼-160 Kb (1 cara, 8 sectores/pista, 40 pistas) |
| 0FFh - discos de 5¼-320 Kb (2 caras, 8 sectores/pista, 40 pistas) |
| 0FCh - discos de 5¼-180 Kb (1 cara, 9 sectores/pista, 40 pistas) |
| 0FDh - discos de 5¼-360 Kb (2 caras, 9 sectores/pista, 40 pistas) |
| 0F9h - discos de 5¼-1,2 Mb (2 caras, 15 sectores/pista, 80 pistas) |
| 0F9h - discos de 3½-720 Kb (2 caras, 9 sectores/pista, 80 pistas) |
| 0F8h - discos duros y algunos virtuales |
| 0F0h - discos de 3½-1,44 Mb (2 caras, 18 sectores/pista, 80 pistas) |
| 0F0h - discos de 3½-2,88 Mb (2 caras, 36 sectores/pista, 80 pistas) |
| 0F0h - restantes formatos de disco |
+---------------------------------------------------------------------+
Tipos de Discos
7.6.3. - LA FAT.
Después del sector de arranque, aparecen en el disco una serie de sectores que constituyen la Tabla de
Localización de Ficheros (File Alocation Table o FAT). Consiste en una especie de mapa que indica qué
zonas del disco están libres, cuáles ocupadas, dónde están los sectores defectuosos, etc. Normalmente hay
dos copias consecutivas de la FAT (véase el offset 16 del sector de arranque), ya que es el área más
importante del disco de la que dependen todos los demás datos almacenados en él. No deja de resultar
extraño que ambas copias de la FAT estén físicamente consecutivas en el disco: si accidentalmente se
estropeara una de ellas (por ejemplo, rayando con un bolígrafo el disco) lo más normal es que la otra también
resultara dañada. En general, muchos programas de chequeo de disco no se molestan en verificar si ambas
FAT son idénticas (empezando por algunas versiones de CHKDSK). Por otra parte, hubiera sido mejor
elección haberla colocado en el centro del disco: dada la frecuencia de los accesos a la misma, de cara a
localizar los diferentes fragmentos de los ficheros, ello mejoraría notablemente el tiempo de acceso medio.
Aunque cierto es que los cachés de disco y los buffers del config.sys pueden hacer casi milagros... a costa de
memoria.
Antes de seguir adelante, conviene hacer un pequeño paréntesis y explicar el concepto de cluster: un
cluster es la unidad mínima de información a la que accede el DOS, desde el punto de vista lógico.
Normalmente consta de varios sectores (ver offset 13 del sector de arranque): dos en un disquete de 360 Kb,
uno en un disquete de alta densidad, y entre 4 y 16 -normalmente- en un disco duro. El disco queda dividido,
por tanto, en un cierto número de clusters. La FAT es realmente un mapa que contiene 12 ó 16 bits -como
veremos- por cada cluster, indicando su estado:
37 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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:
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.
38 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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
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
39 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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?.
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
| offset 8 (3 bytes): Extensión del nombre del fichero | | bit 1: activo si el fichero
| offset 11 (1 byte): Byte de atributos | | bit 2: activo si el fichero
| offset 12 (10 bytes): Reservado (PASSWORD cifrada DR-DOS) | | bit 3: activo si esa entrada
| offset 22 (2 bytes): Hora*2048 + minutos*32 + segundos/2 | | la etiqueta de volume
| offset 24 (2 bytes): (año-1980)*512 + mes*32 + día | | bit 4: activo si es un subdi
| offset 26 (2 bytes): Primera entrada en la FAT | | bit 5: bit de archivo usado
| offset 28 (4 bytes): Tamaño del fichero en bytes | | bits 6,7: no utilizados
+-----------------------------------------------------------+ +-----------------------------
ENTRADA DE DIRECTORIO
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
40 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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.
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á.
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) |
41 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
| 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).
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):
42 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
Estado de recalibramiento del disquete. Esta variable indica varias cosas: si se ha producido
Byte 40h:3Eh una interrupción de disquete, o si es preciso recalibrar alguna disquetera debido a un reset
anterior.
Estado de los motores. En esta variable se indica, además del estado de los motores de las 4
Byte 40h:3Fh posibles disqueteras (si están encendidos o no), la última unidad que fue seleccionada y la
operación en curso sobre la misma.
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
Byte 40h:40h 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).
Estado de la última operación: se actualiza tras cada acceso al disco, indicando los errores
Byte 40h:41h
producidos (0 = ninguno).
A partir de esta dirección, 7 bytes almacenan el resultado de la última operación de disquete
Bytes 40h:42h
o disco duro. Se trata de los 7 bytes que devuelve el NEC765 tras los principales comandos.
Control del soporte (AT). Esta variable almacena, entre otros, la última velocidad de
Byte 40h:8Bh
transferencia seleccionada.
Información del controlador de disquete (AT). Se indica si la unidad soporta 80 cilindros
Byte 40h:8Fh
(pues sí, la verdad) y si soporta varias velocidades de transferencia.
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
Byte 40h:90h
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:
43 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
Se corresponde con el byte 1 del comando 'Specify' del 765, que indica el step rate (el tiempo de
byte 0: acceso cilindro-cilindro, a menudo es 0Dh = 3 ó6 ms) y el head unload time (normalmente, 0Fh =
240 ó480 ms).
Es el byte 2 del comando 'Specify': los bits 7..1 indican el head load time (normalmente 01h = 2
byte 1:
ó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.
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.
44 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
Se puede acceder a varios niveles, siendo mejor el más alto por razones de compatibilidad:
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
45 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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
46 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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.
7.7. - EL PSP.
Como se vio en el capítulo anterior, antes de que el COMMAND.COM pase el control al programa que
se pretende ejecutar, se crea un bloque de 256 bytes llamado PSP (Program Segment Prefix), cuya
descripción detallada se da a continuación.
La dirección del PSP en los programas COM viene determinada por la de cualquier registro de segmento
(CS=DS=ES=SS) nada más comenzar la ejecución del mismo. Sin embargo, en los programas de tipo EXE
sólo viene determinada por DS y ES. En cualquier caso, existe una función del DOS para obtener la dirección
del PSP, cuyo uso recomienda el fabricante del sistema en aras de una mayor compatibilidad con futuras
versiones del sistema operativo. La función es la 62h y está disponible a partir del DOS 3.0.
En la siguiente información, los campos del PSP que ocupen un byte o una palabra han de interpretarse
como tal; los que ocupen 4 bytes deben interpretarse en la forma segmento:offset. En negrita se resaltan los
campos más importantes.
- offsets 0 al 1: palabra 20CDh, correspondiente a la instrucción INT 20h. En CP/M se podía terminar un
programa ejecutando un salto a la posición 0. En MS-DOS, un programa COM ¡también!.
- offsets 2 al 3: una palabra con la dirección de memoria (segmento) del último párrafo disponible en el
sistema. Teniendo en cuenta dónde acaba la memoria y el punto en que está cargado nuestro programa, no es
difícil saber la memoria que queda libre. Supuesto ES apuntando al PSP:
- 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.
47 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
- 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 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 40h al 41h: desde el DOS 5.0, versión del sistema a devolver cuando se invoca la función 30h.
- offsets 50h al 52h: código de INT 21h/RETF. No recomendado hacer CALL PSP:5Ch para llamar al
DOS.
- 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
48 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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 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».
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
49 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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.
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:
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:
50 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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!:
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:
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.
51 de 52 12/10/00 19:10
ARQUITECTURA DEL PC, AT Y PS/2 BAJO DOS file:///C|/librosVirtuales/UniversoDigital/07.html
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).
52 de 52 12/10/00 19:10
untitled file:///C|/librosVirtuales/UniversoDigital/08.html
Daremos un breve repaso a los tipos de memoria asociados a los ordenadores compatibles en la
actualidad. Conviene también echar un vistazo al apéndice I, donde se describe de manera más esquemática,
para completar la explicación.
Es la memoria RAM comprendida entre los 0 y los 640 Kb; es la memoria utilizada por el DOS para los
programas de usuario. Los 384 Kb restantes hasta completar el megabyte se reservan para otros usos, como
memoria para gráficos, BIOS, etc. En muchas máquinas, un buen fragmento de esta memoria está ocupado
por el sistema operativo y los programas residentes, quedando normalmente no más de 560 Kb a disposición
del usuario.
Este término, de reciente aparición, designa el área comprendida entre los 640 y los 1024 Kb de memoria
del sistema. Entre 1989 y 1990 aparecieron programas capaces de gestionar este área para aprovechar los
huecos de la misma que no son utilizados por la BIOS ni las tarjetas gráficas. La memoria superior no se toma
de la memoria instalada en el equipo, sino que está en ciertos chips aparte relacionados con la BIOS, los
gráficos, etc. Por ello, un AT con 1 Mb de RAM normalmente posee 640 Kb de memoria convencional y
384 Kb de memoria extendida. Los segmentos A0000 y B0000 están reservados para gráficos, aunque rara
vez se utilizan simultáneamente. El segmento C0000 contiene la ROM del disco duro en XT (en AT el disco
duro lo gestiona la propio BIOS del sistema) y/o BIOS de tarjetas gráficas. El segmento D0000 es empleado
normalmente para el marco de página de la memoria expandida. El segmento E0000 suele estar libre y el
F0000 almacena la BIOS del equipo. Los modernos sistemas operativos DOS permiten (en los equipos 386
ó 386sx y superiores) colocar memoria física extendida en el espacio de direcciones de la memoria superior;
con ello es factible rellenar los huecos vacíos y aprovecharlos para cargar programas residentes. Ciertos
equipos 286 también soportan esta memoria, gracias a unos chips de apoyo, pero no es frecuente.
El primer adaptador de vídeo de IBM era sólo para texto y empleaba 4 Kb. Después han ido apareciendo
la CGA (16 Kb), EGA (64-256 Kb), VGA (256 Kb) y SVGA (hasta 2 Mb). Como sólo hay 128 Kb
reservados para gráficos en el espacio de direcciones del 8086, las tarjetas más avanzadas tienen paginada su
memoria y con una serie de puertos de E/S se indica qué fragmento del total de la memoria de vídeo está
siendo direccionado (en la VGA, sólo 64 Kb en A0000).
Surgió en los PC/XT como respuesta a la necesidad de romper el límite de los 640 Kb, y se trata de un
sistema de paginación. Consiste en añadir chips de memoria en una tarjeta de expansión, así como una cierta
circuitería que permita colocar un fragmento de esa memoria extra en lo que se denomina marco de página
de memoria expandida, que normalmente es el segmento D0000 del espacio de direcciones del 8086 (64
Kb). Este marco de página está dividido en 4 bloques de 16 Kb. Allí se pueden colocar bloques de 16 Kb
extraídos de esos chips adicionales por medio de comandos de E/S enviados a la tarjeta de expansión. Para
que los programas no tengan que hacer accesos a los puertos y para hacer más cómodo el trabajo, surgió la
especificación LIM-EMS (Lotus-Intel-Microsoft Expanded Memory System) que consiste básicamente en un
1 de 12 12/10/00 19:11
untitled file:///C|/librosVirtuales/UniversoDigital/08.html
driver instalable desde el config.sys que pone a disposición de los programas un amplio abanico de funciones
invocables por medio de la interrupción 67h. La memoria expandida está dividida en páginas lógicas de 16
Kb que pueden ser colocadas en las normalmente 4 páginas físicas del marco de página. Los
microprocesadores 386 (incluido obviamente el SX) permiten además convertir la memoria extendida en
expandida, gracias a sus mecanismos de gestión de memoria: en estas máquinas la memoria expandida es
emulada por EMM386 o algún gestor similar.
Es la memoria ubicada por encima del primer mega en los procesadores 286 y superiores. Sólo se puede
acceder a la mayoría de esta memoria en modo protegido, por lo que su uso queda relegado a programas
complejos o diversos drivers que la aprovechen (discos virtuales, cachés de disco duro, etc.). Hace ya
bastante tiempo se diseñó una especificación para que los programas que utilicen la memoria extendida
puedan convivir sin conflictos: se trata del controlador XMS. Este controlador implementa una serie de
funciones normalizadas que además facilitan la utilización de la memoria extendida, optimizando las
transferencias de bloques en los 386 y superiores (utiliza automáticamente palabras de 32 bits para acelerar el
acceso). La especificación XMS viene en el programa HIMEM.SYS, HIDOS.SYS y en algunas versiones
del EMM386. El controlador XMS también añade funciones normalizadas para acceder a la memoria
superior.
Desde el punto de vista del software, es memoria (convencional, expandida o extendida) empleada por un
controlador de dispositivo (driver) para almacenar las partes del disco de más frecuente uso, con objeto de
acelerar el acceso a la información. A nivel hardware, la memoria caché es una pequeña RAM ultrarrápida
que acompaña a los microprocesadores más avanzados; los programas no tienen que ocuparse de la misma.
También incorporan memorias caché algunos controladores de disco duro, aunque se trata básicamente de
memoria normal y corriente para acelerar los accesos.
Los chips de ROM no han evolucionado tanto como las memorias RAM; por ello es frecuente que un 486
a 66 MHz tenga una BIOS de sólo 8 bits a 8 Mhz. A partir de los procesadores 386 (también 386sx) y
superiores, existen unos mecanismos de gestión de memoria virtual que permiten colocar RAM en el espacio
lógico de direcciones de la ROM. Con ello, es factible copiar la ROM en RAM y acelerar sensiblemente el
rendimiento del sistema, especialmente con los programas que se apoyan en la BIOS. También los chipset de
la placa base pueden añadir soporte para esta característica. La shadow RAM normalmente son 384 Kb que
reemplazan cualquier fragmento de ROM ubicado entre los 640-1024Kb de RAM durante el proceso de
arranque (boot) del sistema. En ocasiones, el usuario puede optar entre 384 Kb de shadow ó 384 Kb más de
memoria extendida en el programa SETUP de su ordenador.
Son 64 bytes de memoria (128 en algunas máquinas) ubicados en el chip del reloj de tiempo real de la
placa base de los equipos AT y superiores. A esta memoria se accede por dos puertos de E/S y en ella se
almacena la configuración y fecha y hora del sistema, que permanecen tras apagar el ordenador (gracias a las
pilas). Evidentemente no se puede ejecutar código sobre la RAM CMOS (Ni pueden esconderse virus, al
contrario de lo que algunos mal informados opinan. Otra cosa es que utilicen algún byte de la CMOS para
controlar su funcionamiento).
2 de 12 12/10/00 19:11
untitled file:///C|/librosVirtuales/UniversoDigital/08.html
Se trata de los primeros 64 Kb de la memoria extendida (colocados entre los 1024 y los 1088 Kb).
Normalmente, cuando se intentaba acceder fuera del primer megabyte (por ejemplo, con un puntero del tipo
FFFF:1000 = 100FF0) un artificio de hardware lo impedía, convirtiendo esa dirección en la 0:0FF0 por el
simple procedimiento de poner a cero la línea A20 de direcciones del microprocesador en los 286 y
superiores. Ese artificio de hardware lo protagoniza el chip controlador del teclado (8042) ya que la línea A20
pasa por sus manos. Si se le insta a que conecte los dos extremos (enviando un simple comando al
controlador del teclado) a partir de ese momento es el microprocesador quien controla la línea A20 y, por
tanto, en el ejemplo anterior se hubiera accedido efectivamente a la memoria extendida. Los nuevos sistemas
operativos DOS habilitan la línea A20 y, gracias a ello, están disponibles otros 64 Kb adicionales. Para ser
exactos, como el rango va desde FFFF:0010 hasta FFFF:FFFF se puede acceder a un total de 65520 bytes
(64 Kb menos 16 bytes) de memoria. Téngase en cuenta que las direcciones FFFF:0000 a la FFFF:000F
están dentro del primer megabyte. En el HMA se cargan actualmente el DR-DOS 5.0/6.0 y el MS-DOS 5.0
y posteriores; evidentemente siempre que el equipo, además de ser un AT, disponga como mínimo de 64 Kb
de memoria extendida. En ciertos equipos poco compatibles es difícil habilitar la línea A20, por lo que el
HIMEM.SYS de Microsoft dispone de un parámetro que se puede variar probando docenas de veces hasta
conseguirlo, si hay suerte (además, hay BIOS muy intervencionistas que dificultan el control de A20).
Vamos ahora a conocer con profundidad la manera en que el sistema operativo DOS gestiona la memoria;
un tema poco tratado, ya que esta información no está oficialmente documentada por Microsoft.
Los bloques de memoria en el DOS son agrupaciones de bytes siempre múltiplos enteros de 16 bytes: en
realidad son agrupaciones de párrafos. La memoria de un PC -siempre bajo DOS- está, por tanto, dividida
en grupos de párrafos. Por tanto, una palabra de 16 bits permite almacenar la dirección del párrafo de
cualquier posición de memoria dentro del megabyte direccionable por el 8086. Todo bloque de memoria tiene
asociado un propietario, que bien puede ser el DOS o un programa residente que haya solicitado al DOS el
control de dicho bloque. Cuando se ejecuta un programa, el sistema crea dos bloques para el mismo: el
bloque de memoria del programa y el bloque de memoria del entorno.
Cuando se ejecuta un programa, el DOS busca el mayor bloque de memoria disponible (convencional o
superior, según sea el caso) y se lo asigna -y no el bloque más cercano a la dirección 0, como algunos
afirman-. Este área recibe el nombre de bloque de programa o segmento de programa. La dirección del
primer párrafo del mismo es de suma importancia y se denomina PID (Process ID, identificador de proceso).
En los primeros 256 bytes de este área el DOS crea el PSP ya conocido -256 bytes- formado por varios
campos de información relacionada con el programa. Tras el PSP viene el código del programa ejecutable.
Para los objetivos de este capítulo basta con conocer dos campos del PSP: el primero está en su offset 0 y
son dos bytes (por tanto, los primeros dos bytes del PSP) que contienen la palabra 20CDh (ó 27CDh en
algunos casos). Esto se corresponde con el código de operación de la instrucción ensamblador INT 20h (o
INT 27h); esto es así por razones históricas heredadas del CP/M. Por ello, cuando un programa finaliza,
puede hacerlo con un salto al inicio del PSP (un JMP 0 en los programas COM) donde se ejecuta el INT
20h, aunque normalmente el programador ejecuta directamente el INT 20h que es más seguro. El otro campo
del PSP que nos interesa es el offset 2Ch: en él hay una palabra que indica el párrafo donde comienza el
bloque de entorno asociado al programa.
3 de 12 12/10/00 19:11
untitled file:///C|/librosVirtuales/UniversoDigital/08.html
de entorno definidas con el mandato SET del sistema, así como con algunos comandos como PATH,
PROMPT, etc. Por ejemplo, la orden PATH C:\DOS es análoga a SET PATH=C:\DOS. Las variables de
entorno pueden consultarse con SET (sin parámetros). las variables de entorno sirven para crear información
que puedan usar múltiples programas, aunque se usan poco en la realidad. Cuando un programa es cargado,
además del bloque de memoria del programa se crea el bloque del entorno. Se trata de una vulgar copia del
espacio de entorno del COMMAND.COM; de esta manera, el programa en ejecución tiene acceso a las
variables de entorno del sistema aunque no las puede modificar (estaría modificando una mera copia). Las
variables de entorno se almacenan en formato ASCIIZ ordinario (esto es, terminadas por un byte a cero) y
tienen una sintaxis del tipo VARIABLE=SU VALOR. Tras la última de las variables hay otro byte más a cero
para indicar el final. Después de esto, y sólo a partir del DOS 3.0, viene una palabra que indica el número de
cadenas ASCIIZ especiales que vienen a continuación: normalmente 1, que contiene una información muy útil:
la especificación completa del nombre del programa que está siendo ejecutado -incluida la unidad y ruta de
directorios- lo que permite a los programas saber su propio nombre y desde qué directorio están siendo
ejecutados y, por tanto, dónde deben abrir sus ficheros (por educación no es conveniente hacerlo en el
directorio raíz o en el actual). En el espacio de entorno del COMMAND, este añadido del DOS 3.0 y
posteriores parece no estar definido.
Todos los bloques de memoria (tanto programa como entorno) vienen precedidos por una cabecera de un
párrafo (16 bytes) que almacena información relativa al mismo. Esta cabecera recibe el nombre técnico de
MCB (Memory Control Block) y tiene la siguiente estructura:
En el offset 0 se sitúa el byte de marca (4Dh si no es el último MCB de la cadena de MCB's en memoria,
5Ah si es el último), en el offset 1 hay una palabra que indica el PID del programa propietario del bloque, en
el offset 3 otra palabra indica el tamaño (como siempre, párrafos) del bloque, sin incluir este párrafo del
MCB. Los bytes que van del 5 al 7 están reservados. Entre el 8 y el 15 se sitúa el nombre del programa
propietario, aunque esta información sólo existe en los bloques de programa y con MS-DOS 4.0 ó posterior
(también en DR-DOS 5.0/6.0, aunque este operativo es aparentemente un DOS 3.31). El nombre acaba con
un cero si tiene menos de 8 caracteres (en DR-DOS 5.0 acaba siempre con un cero, truncándose el 8º
carácter si lo había; esta errata ha sido corregida en DR-DOS 6.0).
Como todos los bloques de memoria están ubicados unos tras otros, y además se conoce el tamaño de los
mismos, es factible hacer un programita que recorra la cadena de bloques de memoria hasta que se encuentre
uno cuyo byte de marca valga 5Ah (último MCB), pudiéndose identificar los programas residentes cargados y
la memoria que emplean. La dirección del primer MCB era al principio un secreto de Microsoft, aunque hoy
casi todo el mundo sabe que las siguientes líneas:
4 de 12 12/10/00 19:11
untitled file:///C|/librosVirtuales/UniversoDigital/08.html
MOV AH,52h
INT 21h
MOV AX,ES:[BX-2]
devuelven en AX la dirección del primer MCB de la cadena, utilizando la función indocumentada 52h del
sistema operativo.
El siguiente esquema aclarará la relación existente entre el bloque de programa y el de entorno. Los
valores numéricos que figuran son arbitrarios (pero correctos).
Básicamente existen cinco tipos de bloques de memoria: bloques de programa, de entorno, del sistema,
bloques de datos y bloques libres. Los dos primeros ya han sido ampliamente explicados. Los bloques del
sistema se corresponden con el kernel o núcleo del sistema operativo o los dispositivos instalables;
normalmente tienen su PID como 0008. En los nuevos sistemas operativos y en las máquinas donde la cadena
de bloques de memoria puede avanzar por encima de los 640 Kb, las zonas correspondientes a RAM de
vídeo y extensiones BIOS suelen tener un PID 0007 en DR-DOS (que indica área excluida) ó 0008
(MS-DOS 5.0) y son consideradas como bloques de memoria ordinarios, aunque sólo sea para saltarlos de
alguna manera. Los bloques libres tienen un PID 0000. El PID 0006 (sólo aparece en DR-DOS) indica que
se trata de un bloque de memoria superior XMS.
Los bloques de datos aparecen en raras ocasiones, debido al uso de las funciones del sistema operativo
para localizar bloques de memoria. Cuando un programa se ejecuta, tiene asignada la mayor parte de la
memoria para sí, pero es perfectamente factible que solicite al DOS una reducción de la memoria asignada
(función 4Ah) y, con los Kb que haya liberado, puede volver a llamar al DOS para crear bloques de memoria
(función 48h) o destruirlos (con la función 49h).
5 de 12 12/10/00 19:11
untitled file:///C|/librosVirtuales/UniversoDigital/08.html
Resulta triste ver como algunos sofisticados programas residentes llegan incluso a autorrelocalizarse en
memoria machacando parte del PSP con objeto de economizar algunos bytes; después un alto porcentaje de
los mismos se olvida de liberar el espacio de entorno, que para nada utilizan y que suele ocupar incluso más
memoria que todo el PSP.
La manera de liberar el espacio de entorno antes de que un programa quede residente es la siguiente
(necesario DOS 3.0 como mínimo si se obtiene la dirección del PSP utilizando la función 62h):
MOV AH,62h
INT 21 ; obtener dirección del PSP en BX
MOV ES,BX
MOV ES,ES:[2Ch] ; dirección del espacio de entorno
MOV AH,49h ; función para liberar bloque
INT 21h ; bloque destruido
Alternativamente, se puede liberar directamente el bloque de memoria del entorno poniendo directamente
un 0 en su PID, aunque es menos elegante. Si ES apunta al PSP:
La información siguiente explica las particularidades de los bloques de memoria con MS-DOS 4.0 y
posteriores; no es válida para DR-DOS aunque algunos aspectos concretos puedan ser comunes. Desde el
MS-DOS 3.1, el primer bloque de memoria es un segmento de datos del sistema, que contiene los drivers
instalados desde el CONFIG.SYS. A partir del DOS 4.0, este bloque de memoria está dividido en
subbloques, cada uno de ellos precedidos de un bloque de control de memoria con el siguiente formato:
Por tanto, desde el DOS 4.0, una vez localizado el primer MCB, puede despreciarse y tomar el que viene
inmediatamente a continuación (párrafo siguiente) para recorrer los subsegmentos conectados. En el DOS 5.0
y siguientes, los bloques propiedad del sistema tienen el nombre "SC" (System Code, código del sistema o
áreas de memoria superior excluidas) o bien "SD" (System Data, con controladores de dispositivo, etc.).
6 de 12 12/10/00 19:11
untitled file:///C|/librosVirtuales/UniversoDigital/08.html
Desde la versión 5.0 del DOS, estos bloques "SD" contienen subbloques con las mismas características que
los del DOS 4.0.
Adicionalmente, el DOS 5.0 introdujo los bloques denominados UMB que recorren la memoria superior,
en las diferentes áreas en que puede estar fragmentada. Acceder a estos bloques de control de memoria es
bastante complicado: el segmento donde empiezan está almacenado en el offset 1Fh de la tabla de
información sobre buffers de disco, cuya dirección inicial a su vez se obtiene en el puntero largo que devuelve
en ES:BX+12h la función indocumentada Get List of Lists (52h): normalmente el resultado es el segmento
9FFFh. En general, es más sencillo ignorar la memoria superior como una entidad independiente y recorrer
toda la memoria sin más. Sin embargo, para poder acceder a los bloques de memoria superior éstos han de
estar ligados a los de la memoria convencional: para conectarlos, si no lo están, puede emplearse la función,
tradicionalmente indocumentada (aunque recientemente ha dejado de serlo) Get or Set Memory Allocation
Strategy (58h) del DOS: es conveniente preservarla antes y volver a restaurar esta información después de
alterarla. En cualquier caso, el formato de los bloques de control UMB es el siguiente:
offset 0: Byte con valor 5Ah para el último bloque y 4Dh en otro caso.
offset 1: Palabra con el PID.
offset 3: Palabra con el tamaño del bloque en párrafos.
offset 8: 8 Bytes: "UMB" si es el primer bloque UMB y "SM" si es el último.
La organización de la memoria varía según la versión del sistema operativo instalada. En líneas generales,
todo lo comentado hasta ahora -excepto lo del apartado anterior- es válido para cualquier versión del DOS.
Sin embargo, en las máquinas que tienen memoria superior, las cosas pueden cambiar un poco en esta zona
de memoria: si tienen instalado algún gestor de memoria extraño, este área puede estar desconectada por
completo de los primeros 640 Kb. Con DR-DOS el usuario puede utilizar el comando MEMMAX para
habilitar o inhibir el acceso a la memoria superior; desde el MS-DOS 5.0 existen funciones específicas del
sistema para estas tareas.
El programa de ejemplo listado más abajo recorre toda la memoria sin adentrarse en las particularidades
de ningún sistema operativo. Tan sólo se toma la molestia de intentar detectar si existe memoria superior y, en
ese caso, mostrar también su contenido. Este algoritmo puede no enseñar todo lo que podría enseñar gracias
a las últimas versiones del DOS, pero sí gran parte, y funciona en todas las versiones. Para comprobar si
existe memoria superior utiliza una técnica muy sencilla: al alcanzar el último bloque de memoria, se
comprueba si el siguiente empezaría en el segmento 9FFFh en vez del A000h como cabría esperar en una
máquina de 640Kb (sólo suelen tener memoria superior las máquinas que al menos tienen 640 Kb). Si esto es
así no se considera que el bloque sea el último y se prosigue con el siguiente, saltando la barrera de los 640
Kb. En este caso, obviamente, los 16 bytes que faltan para completar los 640 Kb de memoria son
precisamente un MCB. Esta técnica funciona sólo a partir del MS-DOS 5.0; en DR-DOS 6.0, si la memoria
superior está inhibida con MEMMAX -U, no funciona (DR-DOS 6.0 se encarga de machacar el último
MCB de la memoria convencional y no deja ni rastro) aunque sí con MEMMAX +U. También se imprime el
nombre de los programas, aunque en DOS 3.30 y versiones anteriores salga basura. Además, el PID de tipo
6 se interpreta como un bloque de memoria superior XMS -que se estudiará en el siguiente apartado de este
mismo capítulo- bajo DR-DOS 6.0, imprimiéndose también el nombre.
La primera acción de MAPAMEM al ser ejecutado es rebajar la memoria que tiene asignada hasta el
mínimo necesario; por ello en el resultado figura ocupando sólo 1440 bytes y teniendo tras de sí un gran
bloque libre. Es conveniente que los programas rebajen al principio la memoria asignada con objeto de
facilitar el trabajo bajo ciertos entornos pseudo-multitarea soportados por el DOS; de hecho, es norma
común en el código generado por los compiladores realizar esta operación al principio. Sin embargo, no todo
el mundo se preocupa de ello y, a fin de cuentas, tampoco es tan importante.
7 de 12 12/10/00 19:11
untitled file:///C|/librosVirtuales/UniversoDigital/08.html
Un ejemplo de la salida que puede producir este programa es el siguiente, tomado de una máquina con
memoria superior y bajo los dos sistemas operativos más comunes (aunque en los ejemplos los espacios de
entorno han coincidido junto al bloque de programa, ello no siempre sucede así). Las diferentes ocupaciones
de memoria de los programas en ambos sistemas operativos se deben frecuentemente a que se trata de
versiones distintas:
Tipo Ubicación Tamaño PID Propietario Tipo Ubicación Tamaño PID Propietario
-------- --------- ------- ----- --------------- -------- --------- ------- ----- ---------------
Sistema 0000-003F 1.024 Interrupciones Sistema 0000-003F 1.024 Interrupciones
Sistema 0040-004F 256 Datos del BIOS Sistema 0040-004F 256 Datos del BIOS
Sistema 0050-023C 7.888 Sistema Operat. Sistema 0050-0252 8.240 Sistema Operat.
Sistema 023E-02FD 3.072 0008 Sistema 0254-045F 8.384 0008
Programa 02FF-031E 512 02FF COMMAND Sistema 0461-0464 64 0008
Entorno 0320-033F 512 02FF COMMAND Programa 0466-050E 2.704 0466 COMMAND
Datos 0341-0358 384 02FF COMMAND Libre 0510-0513 64 0000 <Nadie>
Programa 035A-03EE 2.384 035A MATAGAME Entorno 0515-0544 768 0466 COMMAND
Entorno 03F0-0408 400 040A KEYRESET Entorno 0546-0567 544 0569 MAPAMEM
Programa 040A-041D 320 040A KEYRESET Programa 0569-05C2 1.440 0569 MAPAMEM
Entorno 041F-0437 400 0439 MAPAMEM Libre 05C4-9FFE 631.728 0000 <Nadie>
Programa 0439-0492 1.440 0439 MAPAMEM Sistema A000-D800 229.392 0008
Libre 0494-9FFE 636.592 0000 <Nadie> Sistema D802-E159 38.272 0008
Sistema A000-DEFF 258.048 0007 Libre E15B-E17F 592 0000 <Nadie>
Sistema DF01-E477 22.384 0008 Programa E181-E18D 208 E181 DOSVER
Sistema E479-E483 176 0008 Programa E18F-E23C 2.784 E18F NLSFUNC
Sistema E485-E48D 144 0008 Programa E23E-E3AF 5.920 E23E GRAPHICS
Sistema E48F-E591 4.144 0008 Programa E3B1-E533 6.192 E3B1 SHARE
Sistema E593-E7DA 9.344 0008 Programa E535-E637 4.144 E535 DOSKEY
Sistema E7DC-E806 688 0008 Programa E639-E7E2 6.816 E639 PRINT
Sistema E808-E810 144 0008 Programa E7E4-E840 1.488 E7E4 RCLOCK
Sistema E812-E81A 144 0008 Programa E842-E862 528 E842 DISKLED
Sistema E81C-E8DE 3.120 0008 Programa E864-ECF0 18.640 E864 DATAPLUS
Programa E8E0-EA51 5.920 E8E0 GRAPHICS Programa ECF2-ED59 1.664 ECF2 HBREAK
Programa EA53-EA60 224 EA53 CLICK Programa ED5B-ED7E 576 ED5B ANSIUP
Programa EA62-EA6E 208 EA62 DOSVER Programa ED80-ED8C 208 ED80 PATCHKEY
Programa EA70-EA7F 256 EA70 ALTDUP Programa ED8E-ED93 96 ED8E TDSK
Area XMS EA81-EA8F 240 0006 B1M92VAC Datos ED95-F6D4 37.888 ED8E TDSK
Programa EA91-EAC0 768 EA91 VSA Libre F6D6-F6FF 672 0000 <Nadie>
Area XMS EAC2-EB17 1.376 0006 RCLOCK
Area XMS EB19-EB30 384 0006 DISKLED
Programa EB32-EDB4 10.288 EB32 VWATCH
Area XMS EDB6-EEEC 4.976 0006 DATAPLUS
Area XMS EEEE-EF4F 1.568 0006 HBREAK
Libre EF51-EFFE 2.784 0000 <Nadie>
Sistema F000-F5FF 24.576 0007
Sistema F601-F6FF 4.080 0008
El controlador XMS implementa una serie de funciones para acceder de manera sencilla a la memoria
extendida. En principio, hay funciones para asignar y liberar el HMA (frecuentemente ya estará ocupado por
el sistema operativo), para controlar la línea A20 (en la actualidad suele estar permanentemente habilitada),
para averiguar la memoria extendida disponible, para asignar dicha memoria a los programas que la solicitan
(a los que devuelve un handle de control, igual que cuando se abre un fichero), liberarla, devolver la dirección
física para quien desee realizar transferencias directas y lo más interesante: para mover bloques, bien sea
entre zonas de la memoria extendida o entre la memoria convencional y la extendida, de la manera más óptima
y rápida según el tipo de CPU que se trate. Digamos que la memoria extendida XMS es como un gran banco
o almacén de memoria torpe, del que podemos traer o llevar datos y nada más.
Adicionalmente, el controlador XMS añade funciones para gestionar la memoria superior. Los bloques de
8 de 12 12/10/00 19:11
untitled file:///C|/librosVirtuales/UniversoDigital/08.html
memoria superior no son accesibles de manera directa por los programas, a menos que éstos sean
expresamente cargados en este área con HILOAD ó LOADHIGH. Sin embargo, los programas pueden
solicitar zonas de memoria superior al controlador XMS, que además de la memoria extendida gestiona
también estas áreas. Estos bloques de memoria son gestionados de manera independiente a los de la memoria
convencional, existiendo funciones específicas del controlador XMS para localizar y liberar los bloques. Con
DR-DOS 6.0 y algunos gestores de memoria, en la memoria superior pueden residir tanto bloques de
memoria DOS gestionados por el sistema (normalmente, como consecuencia de un HILOAD para instalar
programas residentes), así como auténticos bloques de memoria XMS. Realmente, las zonas que emplea el
DR-DOS no son sino bloques de este tipo de memoria.
El MS-DOS 5.0 y posteriores, sin embargo, reservan toda la memoria superior para sus propios usos
-cargar programas residentes- cuando se indica DOS=UMB en el CONFIG.SYS; por lo que si alguna
aplicación solicita memoria superior XMS no la encontrará. Pero se puede emplear la función 58h para
conectar la memoria superior y a continuación, con la misma función, cambiar la estrategia de asignación de
memoria para que el sistema asigne memoria superior en respuesta a las funciones ordinarias de asignación de
memoria. Después es conveniente restaurar la estrategia de asignación y el estado de la memoria superior a la
situación inicial (también se puede consultar previamente con la función 58h).
La hecho de que un programa pueda solicitar memoria superior al sistema es una posibilidad interesante:
ello permite a los programas residentes auto-relocalizarse de una manera sencilla a estas zonas, anticipándose
a la actuación de usuarios inexpertos que podrían olvidarse del HILOAD o el LOADHIGH. Por otra parte,
se economiza algo de memoria al poder suprimirse el PSP en la copia. Con MS-DOS 5.0 y posteriores, no
obstante, el programa deberá dejar algo residente en memoria convencional (si no se termina residente, el
sistema libera los bloques asignados en memoria superior) o bien modificar el PID de los bloques en memoria
superior para que al terminar sin quedar residente el DOS no los libere.
Para poder emplear los servicios del controlador XMS hay que verificar primero que está instalado el
programa HIMEM.SYS o alguno equivalente (el EMM386 del DR-DOS 6.0 integra también las funciones
del HIMEM.SYS, así como el QEMM386). Para ello se chequea la entrada 43h en la interrupción Multiplex,
comprobando si devuelve 80h en el registro AL (y no 0FFh como otros programas residentes):
Antes de llamar a la INT 2Fh se comprueba que esta interrupción está apuntando a algún sitio (con el
segmento distinto de 0) ya que en algunas versiones 2.x del DOS está sin inicializar y el sistema se cuelga si se
invoca sin precauciones. Las funciones del controlador XMS no se invocan por medio de ninguna
interrupción, como sucede con las del DOS o la BIOS. En su lugar, una vez detectada la presencia del mismo
se le debe interrogar preguntándole dónde está instalado, por medio de la subfunción 10h:
9 de 12 12/10/00 19:11
untitled file:///C|/librosVirtuales/UniversoDigital/08.html
Posteriormente, cuando haya que utilizar un servicio o función del controlador XMS se colocará el número
del mismo en AH y se ejecutará un CALL gestor_XMS. Para utilizar las llamadas al XMS es preciso que en
la pila queden al menos 256 bytes libres. En un apéndice al final del libro se listan y documentan todas las
funciones XMS.
Si por cualquier motivo fuera necesario en un programa residente interceptar las llamadas al controlador
XMS realizadas por los programas de aplicación, hay que decir que ello es posible. Por supuesto, no es tan
sencillo como desviar un vector de interrupción: hay que modificar el código del propio controlador. Por
fortuna, todos los controladores XMS suelen comenzar con una instrucción de salto larga o corta (JMP
XXXX:XXXX, JMP XXXX, JMP SHORT XX) y, si ésta ocupa menos de 5 bytes, los restantes están
cubiertos de instrucciones NOP (código de operación 90h). Se pueden modificar los primeros bytes del
mismo para poner un salto hacia nuestra propia rutina, que luego acabe llamando a su vez al controlador
previo (el RAMDRIVE de Microsoft, por ejemplo, realiza esta complicada maniobra).
La memoria expandida, como se comentó al principio del capítulo, es una técnica de paginación para
solventar la limitación de 640 Kb de memoria de los PC. Hasta la versión 3 del controlador de memoria
expandida, esta extensión consiste en un segmento de memoria de 64 Kb (en la dirección 0D0000h o
0E0000h, a veces otras como 0C8000h, etc.) dividido en cuatro páginas adyacentes de 16 Kb. Ese
segmento se denomina marco de página de la memoria expandida. Las cuatro páginas son las páginas
físicas numeradas entre 0 y 3. Cuando un programa solicita memoria expandida, se le asigna un handle de
control (un número de 16 bits) que la referencia, así como cierto número de páginas lógicas asociado al
mismo. A partir de ese momento, cualquier página lógica puede ser mapeada sobre una de las cuatro páginas
físicas. De este modo, es factible acceder simultáneamente a cuatro páginas lógicas entre todas las
disponibles. Por ello es posible incluso asignar la misma página lógica a más de una página física, aunque es un
tanto absurdo. La principal utilidad de la memoria expandida es de cara a almacenar grandes estructuras de
datos evitando en lo posible un acceso a disco. La memoria expandida se implementa con una extensión del
hardware, aunque algunos equipos 286 ya la tienen integrada en la placa base. En los 386 y superiores, la
CPU puede ser colocada en modo virtual 86, una variante del modo protegido en la que la memoria
expandida puede ser emulada por las técnicas de memoria virtual de este microprocesador, sin necesidad de
una extensión hardware. Algunos sistemas de memoria expandida real (no emulada) pueden soportar incluso
una reinicialización del PC sin perder el contenido de esa memoria.
Para utilizar la memoria expandida hay que invocar la interrupción 67h. Para detectar la presencia del
controlador hay dos métodos. El primero consiste en buscar un dispositivo "EMMXXXX0", ya que el gestor
de memoria expandida se carga desde el CONFIG.SYS y define un controlador de dispositivo de caracteres
con ese nombre. Es tan sencillo como intentar abrir un fichero con ese nombre y comprobar si existe. Desde
la línea de comandos del DOS se puede hacer así:
IF EXIST EMMXXXX0 ECHO HAY CONTROLADOR EMS
Existe el riesgo de que en lugar de un controlador con ese nombre se trate ¡de un fichero que algún
gracioso haya creado!: para cerciorarse, hay unas funciones de control IOCTL en el DOS para asegurar que
10 de 12 12/10/00 19:11
untitled file:///C|/librosVirtuales/UniversoDigital/08.html
se trata de un dispositivo y no de un fichero. Sin embargo, no es recomendable este método para detectar el
EMM en los programas residentes y en los controladores de dispositivo: existe otro medio más conveniente
para esos casos, que también puede ser empleado de manera general en cualquier otra aplicación. Consiste
en buscar la cadena "EMMXXXX0" en el offset 10 del segmento apuntado por el vector 67h (despreciando
el offset de dicho vector) ¡así de sencillo!.
Las funciones del EMM se invocan colocando en AH el número de función y ejecutando la INT 67h: a la
vuelta, AH normalmente valdrá 0 para indicar que todo ha ido bien. En un apéndice al final del libro se listan y
documentan todas las funciones EMS. Estas funciones se numeran a partir de 40h, aunque desde la 4Fh sólo
están disponibles a partir de la versión 4.0 del controlador, si bien en muchos casos no son necesarias. Las
principales funciones (soportadas por EMS 3.2) son:
40h Obtener el estado del controlador (ver si es operativo y la memoria EMS puede funcionar bien).
41h Obtener el segmento del marco de página (no tiene por qué se 0D000h ni 0E000h).
42h Preguntar el número de páginas libres que aún no están asignadas.
43h Asignar páginas (esta función devuelve un handle de control, igual que cuando se abre un fichero).
44h Mapear páginas (colocar una cierta página lógica 0..N en una de las físicas 0..3).
45h Liberar las páginas asignadas, para que puedan usarlas futuros programas (¡es vital!).
46h Preguntar la versión del controlador de memoria expandida.
47h Salvar el contexto del mapa de páginas (usado por los TSR para no alterar el marco de página).
48h Restaurar el contexto del mapa de páginas (usado por los TSR para no alterar el marco de página).
4Dh Obtener información de todos los handles que hay y las páginas que tienen asignadas.
La memoria expandida, lejos de ser sólo un invento obsoleto para superar los 640K en los viejos
ordenadores, es una de las memorias más versátiles disponibles bajo DOS. Muchos programas pueden ver
incrementado notablemente el rendimiento si se desarrollan empleando esta memoria en lugar de la XMS. La
razón es que, con la memoria extendida, hay que traerla (copiarla) a la memoria convencional, procesarla y
volverla a copiar a la memoria extendida. Sin embargo, con la memoria expandida EMS, una rapidísima
función coloca en el espacio de direcciones del 8086 la memoria que va a ser accedida: allí mismo puede ser
procesada sin necesidad de movimiento físico. Esto es debido a que la conmutación páginas de memoria
expandida se hace, dicho entre comillas, seleccionando el chip de RAM que se utiliza, sin existir movimiento
físico de datos. En algunos casos, sin embargo, la EMS no aumenta el rendimiento: por ejemplo, al construir
un disco virtual, habrá que transferir datos desde la memoria convencional a la XMS ó la EMS; en cualquier
caso se va a producir un movimiento físico (¿qué mas da que sea hacia la EMS que hacia la XMS?).
En los modernos sistemas operativos, la memoria expandida soportada a partir de las versiones 4.0 del
EMM (Expanded Memory Manager) cubre un amplio espectro del espacio de direcciones dentro del
megabyte gestionado por el MS-DOS. Aquí, las páginas no han de ser necesariamente consecutivas; son más
de 4 y tampoco tienen que ser necesariamente de 16 Kb. Sin embargo, por defecto -y por razones de
compatibilidad- las cuatro primeras páginas físicas están colocadas adyacentemente por encima de los 640K
y son de 16 Kb, no siendo recomendable modificar esta especificación. Por ejemplo, en el sistema 386 en
que se escribieron las primeras versiones de este libro, con un EMM 4.0, las páginas físicas 0 a la 3 estaban
ubicadas a partir de la dirección 0C8000h; las páginas 4 a la 27h estaban ubicadas entre la dirección 10000h
a la 9FFFFh, cubriendo también los primeros 640 Kb (excepto los primeros 64 Kb).
Si alguien está pensando en desviar la interrupción 67h desde un programa residente, para interceptar y
manipular las llamadas de los programas de aplicación a esa interrupción, ya puede ir olvidándose. La razón
es que los 386 y superiores están en modo virtual 86 con los controladores EMS instalados. Esto significa que
cuando un programa invoca una interrupción, como la INT 67h, la CPU -de la manera que está programada-
pasa inmediatamente a continuación a ejecutar una rutina en modo protegido fuera del espacio de direcciones
del MS-DOS. Con algunos gestores de memoria, como el EMM386 del DR-DOS 6.0, no sucede nada: ese
programa supervisor retorna a la tarea virtual y ejecuta el código ubicado en el espacio de direcciones del
11 de 12 12/10/00 19:11
untitled file:///C|/librosVirtuales/UniversoDigital/08.html
MS-DOS. Sin embargo, con QEMM386, el controlador de memoria está ubicado fuera de ese espacio de
direcciones, y ya no vuelve a él. Si se mira con el DEBUG a donde apunta la INT 67h en una máquina con
QEMM (por ejemplo, traceando una llamada a la interrupción), se verá que este vector apunta al siguiente
código:
INT 28h
IRET
Evidentemente, ¡ese no es el controlador de memoria!. Para acceder a él hay que ejecutar una interrupción
de verdad. Supongo que a través de la especificación VCPI (Virtual Control Program Interface) que regula el
acceso a los modos extendidos del 386, habrá algún medio de poder acceder al código del controlador EMS,
o interceptar las llamadas. Sin embargo, no es tan fácil como cambiar un vector...
12 de 12 12/10/00 19:11
SUBPROCESOS, RECUBRIMIENTOS Y FILTROS file:///C|/librosVirtuales/UniversoDigital/09.html
La función EXEC del DOS (4Bh) es el pilar que sustenta la ejecución de programas desde dentro de otros
programas, así como la carga de subrutinas de un mismo programa desde disco (overlays). Si no existiera la
función EXEC, el proceso sería arduo: habría que reservar memoria, cargar el fichero ejecutable en memoria,
relocalizarlo si es de tipo EXE, crear su PSP y demás áreas de datos (entorno, etc)... por fortuna, la función
EXEC se ocupa de todo ello. Además, esta función posee una característica no documentada hasta el DOS
5.0 (sí ha sido documentada desde dicha versión), que es la posibilidad de cargar un programa sin ejecutarlo,
lo cual puede ser interesante de cara a la creación de depuradores de código.
Para llamar a la función EXEC para cargar y ejecutar un programa se pone un 0 en AL. Hay que apuntar
DS:DX a la dirección del nombre del programa (una cadena ASCIIZ, esto es, terminada por cero) que puede
incluir la ruta de directorios y debe incluir la extensión. También hay que apuntar en ES:BX a una estructura
de datos (bloque de parámetros) que se interpreta de la siguiente forma:
Segmento donde está el entorno a copiar para crear el del programa cargado. A 0 si es el del
offset 0:
programa padre. Los programas hijos siempre accederán a una copia y no al original.
Doble palabra que apunta a los parámetros del programa a ejecutar (los que ese programa admite,
offset 2:
por sí solo, en la línea de comandos). Tiene el mismo formato que el contenido de PSP:80h.
offset 6: Doble palabra que apunta al primer FCB a copiar en el proceso hijo.
offset 10: Doble palabra que apunta al segundo FCB a copiar en el proceso hijo.
offset 14: Si se carga sin ejecutar, devuelve el SS:SP inicial del subprograma.
offset 18: Si se carga sin ejecutar, devuelve el CS:IP inicial del subprograma.
El subprograma cargado hereda los ficheros abiertos del programa padre. Antes de llamar a esta función,
el ordenador debe tener suficiente memoria libre. Cuando se ejecuta un programa COM ordinario, toda la
memoria del sistema está asignada al mismo (el mayor bloque en realidad, lo que en la práctica significa toda
la memoria). Por tanto, un programa COM que desee cargar otros programas debe primero rebajar la
memoria que el DOS le ha asignado y quedarse sólo con la que necesita. Con los programas EXE, la
cantidad de memoria que les asigna el DOS inicialmente depende del compilador y las opciones de
compilación; en ensamblador suele ser también toda la memoria, por lo que es deber de éste liberar la que no
necesita. Para ello, se calcula cuanta memoria necesita el programa y se llama a la función del sistema para
modificar el tamaño del bloque de memoria del propio programa (función 4Ah del DOS, pasando en ES la
dirección del PSP).
En los programas COM, la pila está apuntando al final del segmento (SP está próximo a 0FFFEh). Por
ello, si el programa va a ocupar menos de 64 Kb, será preciso mover SP más abajo para que no se salga del
futuro bloque de memoria del programa. Si no se toma esta precaución, SP apuntará dentro del siguiente
bloque de memoria, que es más que probablemente el que utilizará EXEC, con lo que el ordenador debería
colgarse a no ser que haya mucha suerte.
Tras llamar a la función EXEC, en teoría todos los registros son destruidos, según la documentación oficial,
incluidos SS:SP. Esto significa que antes de llamar a EXEC deben apilarse los registros que no se desee
alterar y guardar en un par de variables SS y SP. Tras llamar a EXEC, inmediatamente a continuación y antes
de hacer nada se deben recargar SS y SP, para proceder después a recuperar de la pila los demás registros.
Este comportamiento de EXEC parece romper la tónica habitual de comportamiento del DOS. Sin embargo,
lo cierto es que esto sólo sucedía en el DOS 2.X: aunque Microsoft no lo diga oficialmente, las versiones
posteriores del sistema sólo corrompen DX y BX al llamar a EXEC.
El siguiente programa de ejemplo, de tipo COM, realiza todas las tareas necesarias para cargar otro
1 de 7 12/10/00 19:12
SUBPROCESOS, RECUBRIMIENTOS Y FILTROS file:///C|/librosVirtuales/UniversoDigital/09.html
programa. Como ejemplo, he decidido cargar el COMMAND.COM, aunque el programa a ejecutar podría
ser cualquier otro; la ventaja de COMMAND es que crea una nueva sesión de intérprete de comandos y
permite comprobar con comodidad qué ha sucedido con la memoria.
; ********************************************************************
; * *
; * SHELL.ASM 1.0 - Demostración de carga de subprograma. *
; * *
; ********************************************************************
shell SEGMENT
ASSUME CS:shell, DS:shell
ORG 100h
inicio:
MOV SP,TAMTOT ; redefinir la pila
MOV AH,4Ah
MOV BX,TAMTOT/16
INT 21h ; redimensionar bloque memoria
LEA DX,hola_txt
MOV AH,9
INT 21h ; mensaje de bienvenida
LEA BX,exec_info
MOV WORD PTR [BX],0
MOV WORD PTR [BX+2],80h ; PSP
MOV WORD PTR [BX+4],CS
MOV WORD PTR [BX+6],5Ch ; FCB 0
MOV WORD PTR [BX+8],CS
MOV WORD PTR [BX+0Ah],6Ch ; FCB 1
MOV WORD PTR [BX+0Ch],CS
LEA DX,nombre
MOV AX,4B00h
INT 21h ; cargar y ejecutar programa
PUSH CS
POP DS ; DS = CS
LEA DX,adios_txt
MOV AH,9
INT 21h ; mensaje de despedida
MOV AX,4C00h
INT 21h ; terminar
shell ENDS
END inicio
C:\COMPILER\86\AREA>shell
2 de 7 12/10/00 19:12
SUBPROCESOS, RECUBRIMIENTOS Y FILTROS file:///C|/librosVirtuales/UniversoDigital/09.html
C:\COMPILER\86\AREA>mapamem
MAPAMEM 2.2
- Información sobre la memoria del sistema.
C:\COMPILER\86\AREA>exit
C:\COMPILER\86\AREA>_
La subfunción EXEC para cargar un programa sin ejecutarlo se selecciona con AL=1; ES:BX apunta al
bloque de parámetros que se definió para el caso normal de carga+ejecución. Esta subfunción asigna el PID,
no obstante, al PSP del subprograma cargado.
La subfunción de EXEC para cargar un overlay o recubrimiento, se llama con los mismos valores en los
3 de 7 12/10/00 19:12
SUBPROCESOS, RECUBRIMIENTOS Y FILTROS file:///C|/librosVirtuales/UniversoDigital/09.html
registros que la anterior, exceptuando AL (que ahora vale 3). Sin embargo el bloque de parámetros apuntado
por ES:BX es ahora mucho más sencillo:
Offset 0: Segmento donde cargar el overlay (la memoria ha de asignarla el programa principal).
Offset 2: Factor de reubicación, si se trata de un fichero EXE (normalmente el mismo valor que el anterior,
si el subprograma va a correr en el mismo segmento en que es cargado).
El overlay puede haber sido ensamblado, por ejemplo, con un desplazamiento relativo nulo (ORG 0) de
manera que para llamarlo hay que hacer un CALL FAR al segmento donde ha sido cargado, con un offset 0.
Claro que también se puede calcular la distancia que hay entre el segmento del programa principal y el del
overlay, multiplicarlo por 16 y utilizarlo como offset en la llamada al mismo segmento del programa principal.
Sin embargo, esto requiere que el overlay sea ensamblado con cierto offset ... a calcular. Quienes proponen
este segundo método -que los hay- andaban ese día más bien despistados. En general, la programación con
overlays es compleja, y más aún si los overlays constan de varios segmentos internos.
Para conocer si la función EXEC se ha realizado correctamente o ha fracasado, se puede utilizar la función
4Dh del DOS (Obtener código de retorno), que devuelve en AH: 0 (terminación normal), 1 (programa
abortado por Ctrl-Break), 2 (terminación por error crítico) ó 3 (terminación residente). Al llamar a la función
4Dh, se borra la información que devuelve (sólo funciona la primera llamada). En AL se devuelve el valor que
retorna el programa que finaliza (valor de ERRORLEVEL).
9.2. - FILTROS.
El DOS es un sistema operativo que soporta el redireccionamiento. Las posibilidades son, sin embargo,
muy limitadas. La razón es la ineficiencia del sistema en las operaciones de entrada y salida, que obliga a las
aplicaciones a hacer accesos directos al hardware. Por ejemplo: con el comando interno CTTY, a través de
un puerto serie es factible poner a un PC como servidor remoto de otro. Esto permite operar en la línea de
comandos desde el terminal remoto ubicado a varios metros de distancia. Sin embargo, nada más ejecutar un
programa, el teclado del PC con el emulador de terminal dejará de funcionar y será preciso utilizar ¡el del
propio servidor!: la razón es que muy pocos programas usan el DOS para leer el teclado; no digamos para
escribir en la pantalla...
Sin embargo, aún en la actualidad muchos usuarios de PC trabajan en la línea de comandos, donde sí es
posible, como se ha mencionado, utilizar el DOS como un sistema con dispositivos de entrada y salida
estándar que soportan el redireccionamiento. El redireccionamiento bajo DOS es empleado sobre todo para
procesar ficheros de texto.
Un filtro es un programa normal que lee datos de la entrada estándar (por defecto, el teclado), los procesa
de alguna manera y los deposita en la salida estándar (por defecto, la pantalla). Tanto la entrada como la
salida estándar, popularmente conocidas como STDIN y STDOUT, respectivamente, así como la salida
estándar para errores (STDERR) son dispositivos permanentemente abiertos en el DOS. Tienen asociados un
handle de control, como cualquier fichero: 0 para STDIN (denominado CON), 1 para STDOUT (también
conocido por CON), 2 para STDERR (también CON), 3 para la salida serie (denominada AUX) y 4 para la
impresora (conocida por PRN).
Por tanto, un filtro normal debe limitarse a leer, con las funciones de manejo de ficheros ordinarias,
información procedente del handle 0; tras procesarla debe escribirla en el handle 1. Si se produce un error en
el proceso, o hay una salida de log que no deba mezclarse con la salida deseada por el usuario, se puede
escribir el mensaje en el handle 2. El redireccionamiento y el sistema de ficheros por handle fue incluido a
partir del DOS 2.0 (en versiones anteriores no hay siquiera subdirectorios).
4 de 7 12/10/00 19:12
SUBPROCESOS, RECUBRIMIENTOS Y FILTROS file:///C|/librosVirtuales/UniversoDigital/09.html
Cuando se ejecuta una orden del tipo COMANDO | FILTRO, el intérprete de comandos cierra la salida
estándar y crea un fichero auxiliar (de nombre extraño); a continuación abre ese fichero para salida: como al
cerrar la salida estándar se había liberado el handle 1, ese handle será asignado al nuevo fichero. Esto significa
que toda la salida de COMANDO no irá a la pantalla (CON) sino al fichero auxiliar. Cuando se acabe de
ejecutar COMANDO, el intérprete de mandatos cerrará el fichero auxiliar y volverá a abrir la salida estándar,
restaurando el sistema al estado normal. Pero la cosa no queda ahí, evidentemente: a continuación se cierra la
entrada estándar y se abre como entrada el fichero auxiliar recién creado, que pasará a ser el nuevo
dispositivo de entrada por defecto. Seguidamente, se carga y ejecuta FILTRO, que tomará los datos del
fichero auxiliar en lugar del teclado. Al final, el fichero auxiliar es cerrado y borrado, abriéndose y
restaurándose la entrada por defecto normal. Si se ejecuta DIR | SORT, aparte del directorio ordenado
aparecerán dos extraños ficheros con 0 bytes (este era su tamaño cuando se ejecutó DIR): el DOS crea dos
ficheros auxiliares para sustituir la entrada y salida estándar, aunque en este ejemplo sólo se emplee uno de
ellos. Actuarán los dos si se utilizan filtros encadenados que obliguen a redireccionar simultáneamente tanto la
entrada como la salida a ficheros auxiliares, en una orden del tipo DIR | SORT | MORE. A partir del
DOS 5.0, si está definida la variable de entorno TEMP los ficheros auxiliares se crean donde ésta indica y no
en el directorio activo, por lo que a simple vista podrían no verse dichos ficheros.
Cuando se utilizan los redirectores habituales ('<', '>', '<<' y '>>') suceden procesos similares, todos ellos
desencadenados por COMMAND.COM, con objeto de alterar la salida y entrada por defecto para trabajar
con un fichero en su lugar. Por tanto, los filtros son programas que no tienen que preocuparse de cual es la
entrada o salida; su codificación es extremadamente sencilla y puede realizarse en cualquier lenguaje de alto o
bajo nivel. El siguiente programa en C estándar, NULL.C, es un filtro nulo que no realiza tarea alguna: se
limita a enviar todo lo que recibe (por tanto, DIR es lo mismo que DIR | NULL):
#include <stdio.h>
void main()
{
int c;
El siguiente filtro, algo más útil, transforma en minúsculas todo lo que pasa por él, teniendo cuidado con los
caracteres españoles (Ñ, Ü, Ç, etc.). Lee bloques de medio Kbyte de una sola vez para reducir el número de
llamadas al DOS y ganar velocidad. Si se ejecuta sin más (sin emplear '|' ni '<' ni ningún símbolo de
redireccionamiento o filtro) se limita a leer líneas del teclado y a reescribirlas en minúsculas, hasta que se
acaba la entrada estándar (teclear Ctrl-Z y Return al final).
; ********************************************************************
; * *
; * MIN.ASM 1.0 - Filtro para poner en minúsculas ASCII Español. *
; * *
; ********************************************************************
segmento SEGMENT
ASSUME CS:segmento, DS:segmento
STDIN EQU 0
STDOUT EQU 1
ORG 100h
inicio:
CALL lee_entrada ; leer de STDIN
JCXZ fin_filtro ; en CX, bytes leídos
PUSHF
5 de 7 12/10/00 19:12
SUBPROCESOS, RECUBRIMIENTOS Y FILTROS file:///C|/librosVirtuales/UniversoDigital/09.html
CALL pon_minusculas
CALL escribe_salida ; escribir en STDOUT
POPF
JNC inicio
fin_filtro: MOV AX,4C00h ; CF = 1 si fin de fichero
INT 21h
lee_entrada PROC
LEA DX,buffer
MOV CX,512
MOV BX,STDIN
MOV AH,3Fh
INT 21h ; leer
MOV CX,AX
RET
lee_entrada ENDP
escribe_salida PROC
LEA DX,buffer
MOV BX,STDOUT
MOV AH,40h
INT 21h ; escribir
RET
escribe_salida ENDP
pon_minusculas PROC
PUSH CX
LEA BX,buffer
procesa_car: MOV AL,[BX]
CMP AL,'A'
JB car_ok
CMP AL,128
JAE car8
CMP AL,'Z'
JA car_ok
OR AL,32
car_ok: MOV [BX],AL
INC BX
LOOP procesa_car
POP CX
RET
car8: MOV AH,'ñ'
CMP AL,'Ñ'
JE trad_ok
MOV AH,'ç'
CMP AL,'Ç'
JE trad_ok
MOV AH,'ü'
CMP AL,'Ü'
JE trad_ok
MOV AH,'é'
CMP AL,'É'
JE trad_ok
MOV AH,AL
trad_ok: MOV AL,AH
JMP car_ok
pon_minusculas ENDP
segmento ENDS
END inicio
6 de 7 12/10/00 19:12
SUBPROCESOS, RECUBRIMIENTOS Y FILTROS file:///C|/librosVirtuales/UniversoDigital/09.html
7 de 7 12/10/00 19:12
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
En este capítulo vamos a abordar uno de los temas más estrechamente relacionados con la programación
de sistemas: la creación de programas residentes. El DOS es un sistema monousuario y monotarea, diseñado
para atender sólo un proceso en un momento dado. Los programas residentes, aquellos que permanecen en
memoria tras ser ejecutados, surgieron como intento de superar esta limitación. Algunos de estos programas
residentes proporcionan en la práctica multitarea real (tales como colas de impresión o relojes), pero otros
están muertos a menos que el usuario los active. A la hora de construir programas residentes el ensamblador
es el lenguaje más apto: es el más potente, el programador controla totalmente la máquina sin depender de
facetas ocultas del compilador y, además, es el lenguaje más sencillo para crear programas residentes (en
inglés, TSR: Terminate and Stay Resident). Para los programas más complejos puede ser necesario, en
cambio, utilizar algún lenguaje de alto nivel próximo a la máquina. Sin duda, los programas residentes que
pretendan captar gran número de usuarios, deben cumplir dos requisitos: por un lado, ocupar poca memoria;
por otro, estar disponibles rápidamente cuando son requeridos y, también, ser fiables y crear pocos
conflictos. Esto último es importante, ya que un programa residente puede funcionar más o menos bien pero
no del todo: si bien la máquina puede resistirse a colgarse, pueden aparecer anomalías o conflictos con
algunas aplicaciones. En particular, es muy común la circunstancia de que dos programas residentes sean
incompatibles entre sí.
Un programa residente o TSR es un programa normal y corriente que, tras ser cargado, permanece parcial
o totalmente en memoria al finalizar su ejecución. Ello es posible utilizando una función específica del sistema
operativo. Los programas residentes pueden ser activados mediante una combinación de teclas o bien actuar
con cierta periodicidad, asociados a la interrupción del temporizador. También pueden interceptar funciones
del DOS o de la BIOS para cambiar o modificar su funcionamiento. Al final, casi siempre resulta totalmente
inevitable desviar alguna interrupción hacia una nueva rutina que la gestione, con objeto de activar el programa
residente. Como en casi todos los aspectos de la programación, existen unos cuantos principios
fundamentales que conviene respetar:
1. Los programas residentes no deben alterar el funcionamiento normal del resto del ordenador. Esto
significa que deben preservar el estado de todo lo que van a modificar durante su ejecución,
restaurándolo después antes de retornar al programa principal, lo cual no se limita por supuesto a los
registros de la CPU, sino que incluye también la pantalla, los discos, el estado de la memoria expandida
y extendida, etc. Cuando se produce la interrupción que activa el programa residente, los registros de
la CPU pueden tener un valor que hay que interpretar o bien pueden ser aleatorios. Este último es el
caso de la interrupción periódica del temporizador: el programa residente sólo puede fiarse de CS:IP,
los demás registros deberán ser inicializados antes de empezar a operar (lógicamente, habrán de ser
primero preservados para ser restaurados al final).
2. No se pueden invocar libremente desde un programa residente los servicios del sistema operativo. Si el
lector es la primera vez que oye esto, quizá se quede extrañado. Tal vez se pregunte qué sucedería si
desde un programa residente se llama (pongamos por ejemplo, una vez cada segundo) a la función de
impresión del DOS para sacar una 'A' por la pantalla. Lo que puede suceder -y acabará sucediendo, si
no a la primera 'A', a la segunda o la tercera- es que el ordenador se cuelgue. Esto es debido a que el
DOS es un sistema operativo no reentrante, entre otras razones porque conmuta a una pila propia al
ser invocado. Por ello, si se llama a un servicio del DOS desde un programa residente, es posible que
en ese momento el DOS ya estuviese realizando otra función del programa principal y lo que vamos a
conseguir es que se vuelva loco y pierda el control cuando se acabe la tarea residente (el contenido
previo de la pila ha sido destrozado). Para utilizar el DOS desde un programa residente hay que
conocer cómo están organizadas las pilas del sistema operativo, así como determinar el estado del
1 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
DOS para saber si se puede interrumpir en ese momento o si hay que esperar. Utilizar el DOS es
prácticamente indispensable a la hora de acceder al disco, por lo que más adelante en este capítulo lo
veremos con detenimiento. Para utilizar el DOS hay que emplear funciones más o menos secretas del
sistema no documentadas por Microsoft, si bien esto no es peligroso: esta empresa las utiliza y las ha
utilizado siempre profusamente en sus propios programas, por lo que resulta más que seguro esperar
que futuras versiones del DOS sigan soportándolas.
3. La BIOS no es tampoco completamente reentrante. Por fortuna, la BIOS utiliza la pila del programa
que le llama. Por ello, para utilizar funciones de la BIOS desde un programa residente basta con
asegurar que el sistema no está ya ejecutando una función BIOS incompatible (normalmente, una
interrupción 10h en el caso de las funciones de vídeo o la 13h en las de disco).
4. El hardware puede ser accedido sin limitaciones desde los programas residentes, si bien el nivel de uso
que puede hacerse está limitado por el sentido común (puede haber problemas, por ejemplo, si un
programa residente cambia la posición del cabezal de un disquete cuando el programa principal estaba
ejecutando una función del DOS o la BIOS para acceder al disquete).
5. Los programas residentes tienen una causa que provoca su activación. Si cuando ya están activos, se
vuelve a reproducir la causa, estamos ante un problema de reentrada que compete exclusivamente al
programador. Por lo general, se suele denegar una demanda de activación cuando el programa
residente ya estaba activo (si el programa tiene pila propia esto es además obligatorio). Pongamos por
caso que se pulsa CTRL-ALT-R para mostrar un reloj residente en pantalla, ¿qué sucederá si se
vuelve a pulsar CTRL-ALT-R con el reloj ya activado?. Para solucionar esto, existen dos caminos: uno
de ellos es utilizar una variable que indique que el programa ya está activo. El otro, es utilizar para
desactivar el programa la misma secuencia de teclas que para activarlo. Lógicamente, los programas
que realicen algo periódicamente (pongamos por caso 18,2 veces por segundo) basta con que se
limiten a no pillarse los dedos, esto es, utilizar menos de 1/18,2 segundos de tiempo de CPU para sus
tareas.
El siguiente programa residente no realiza tarea alguna, tan sólo es una demostración de la manera general
de proceder para crear un programa residente. En principio, el código de instalación está colocado al final,
con objeto de no dejarlo residente y economizar memoria. La rutina de instalación (MAIN) se encarga de
preservar el vector de la interrupción periódica y desviarlo para que apunte a la futura rutina residente.
También se instala una rutina de control de la interrupción 10h. Finalmente, se libera el espacio de entorno
para economizar memoria y se termina residente. El procedimiento CONTROLA_INT8 puede ser
modificado por el lector para que el programa realice una tarea útil cualquiera 18,2 veces por segundo: de la
manera que está, se limita a llamar al anterior vector de la INT 8 y a comprobar que no se está ejecutando
ninguna función de vídeo de la BIOS (que no se ha interrumpido la ejecución de una INT 10h). Esto significa
que el lector podrá utilizar libremente los servicios de vídeo de la BIOS, si bien para utilizar por ejemplo los
de disquetes habría que desviar y monitorizar también INT 13h; por supuesto además que no se puede llamar
al DOS en este TSR (no se puede hacer INT 21h directamente desde el código residente). Por cierto, si se
fija el lector en la manera de controlar la INT 10h verá que al final se retorna al programa principal con IRET:
los flags devueltos son los del propio programa que llamó y no los de la INT 10h real. Con la INT 10h se
puede hacer esto, ya que los servicios de vídeo de la BIOS no utilizan el registro de estado para devolver
ninguna condición. Sin embargo, con otras interrupciones BIOS (ej. 16h) o las del DOS habría que actuar
con más cuidado para que la rutina de control no altere nada el funcionamiento normal.
Puede que el lector haya visto antes programas residentes que no toman la precaución de monitorizar la
interrupción 10h o la 13h de la BIOS, y tal vez se pregunte si ello es realmente necesario. La respuesta es
tajantemente que sí. Como se verá en el futuro en otro programa de ejemplo, reentrar a la BIOS sin más
puede provocar conflictos.
2 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
demores SEGMENT
ASSUME CS:demores, DS:demores
ORG 100h
inicio:
JMP main
controla_int08 PROC
PUSHF
CALL CS:ant_int08 ; llamar al gestor normal de INT 8
STI
CMP CS:in10,0
JNE fin_int08 ; estamos dentro de INT 10h
;
; Colocar aquí el proceso a ejecutar 18,2 veces/seg.
; que puede invocar funciones de INT 10h
fin_int08:
IRET
controla_int08 ENDP
controla_int10 PROC
INC CS:in10 ; indicar entrada en INT 10h
PUSHF
CALL CS:ant_int10
DEC CS:in10 ; fin de la INT 10h
IRET
controla_int10 ENDP
main: PUSH ES
MOV AX,3508h
INT 21h ; obtener vector de INT 8
MOV ant_int08_seg,ES
MOV ant_int08_off,BX
MOV AX,3510h
INT 21h ; obtener vector de INT 10h
MOV ant_int10_seg,ES
MOV ant_int10_off,BX
POP ES
LEA DX,controla_int08
MOV AX,2508h
INT 21h ; nueva rutina de INT 8
LEA DX,controla_int10
MOV AX,2510h
INT 21h ; nueva rutina de INT 10h
PUSH ES
MOV ES,DS:[2Ch] ; dirección del entorno
MOV AH,49h
INT 21h ; liberar espacio de entorno
3 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
POP ES
demores ENDS
END inicio
Un programa residente que ya está instalado en memoria puede volver a ser cargado desde disco y esto
hay que tenerlo en cuenta. Puede que el programa sea de éstos que se cargan una sola vez y carecen de
parámetros. En ese caso, no sucederá nada porque sea creada en memoria una nueva copia del mismo: es
problema del usuario. Sin embargo, si una recarga posterior puede provocar un cuelgue del sistema o,
simplemente, el programa tiene opciones y se pretende modificar los parámetros de la copia ya residente,
entonces se hace necesario que el programa tenga capacidad para buscarse en memoria y encontrarse a sí
mismo en el caso de que ya estuviera cargado.
El método más simple es también el más simplón -inútil- y consiste en apoyarse en los vectores de
interrupción. Por ejemplo, si el programa quedó residente interceptando la interrupción 9, basta con mirar a
dónde apunta dicha interrupción y comprobar un grupo de bytes o alguna identificación que permita
determinar si el programa que la gestiona es ya una copia de él mismo. El inconveniente de este método, fácil
de deducir, es que si se carga más de un programa residente que emplee la INT 9, sólo el último cargado será
capaz de encontrarse a sí mismo en memoria.
Otro método alternativo es rastrear la cadena de bloques de memoria del sistema operativo buscando
programas residentes y comprobándolos uno por uno. Este método es bastante rápido, habida cuenta de que
no van a existir más de 20-50 bloques de memoria. Sin embargo, la organización de la memoria en los PCs es
a veces tan anárquica que este método (que debería ser el más elegante) es un poco peligroso en cuanto a la
seguridad, aunque mucho menos que el anterior. Lo cierto es que puede ser difícil intentar recorrer la memoria
superior, habida cuenta del desigual tratamiento que recibe en las diversas versiones del DOS y con los
diversos controladores de memoria que pueden estar instalados.
Por cierto, la idea de rastrear toda la memoria (1 Mb), buscando desesperadamente una cadena de
identificación, no es nueva. Sin embargo es tremendamente lenta llevada a la práctica. Es incómoda (hay que
considerar el caso de que el propio programa que busca se encuentre a sí mismo, en particular en áreas como
los buffers de transferencia con disco del DOS) y bastante salvaje.
Finalmente, existe la posibilidad de utilizar el mismo sistema que emplea el DOS para comprobar la
presencia de sus propios programas residentes (como el KEYB, GRAPHICS, GRAFTABL, SHARE,
PRINT, etc) basado en la interrupción Multiplex (2Fh). Este sistema es el más seguro, aunque un tanto
laborioso. Consiste en llamar a la INT 2F con un valor en el registro AH que indica quién está llamando, y
otro valor en AL para decir por qué está llamando (normalmente 0). Los valores 00-BFh en AH están
4 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
reservados para el DOS, y de C0h-FFh para las aplicaciones. A la vuelta, AL devuelve un valor 0 para
indicar que el programa no está instalado pero está permitida la instalación, un valor 1 para decir que no está
instalado ni tampoco está permitida la instalación. Si devuelve FFh, significa que el programa ya estaba
instalado. Por ejemplo, el KEYB del DOS llama a INT 2Fh con AX=AD80h, donde ADh significa que quien
pregunta es el KEYB -y no otro programa- para conocer si ya está instalado o no. En caso de que lo esté
(AL=FFh a la vuelta), también se devuelve en ES:DI la dirección del KEYB ya residente (que es lo solicitado
con AL=80h). En el caso concreto del KEYB, si a la vuelta AL<>FFh se interpreta que el programa no está
aún residente, por lo que se procede a su instalación (en este caso, curiosamente incluso aunque AL=1).
Esta técnica cuenta con la complicación que supone decidir qué valor emplear en la interrupción multiplex.
Es evidente que dos programas residentes no pueden utilizar el mismo. Los programas menos eficientes
utilizan un valor fijo predeterminado, con lo que limitan las posibilidades del usuario. Sin embargo, para
solucionarlo existen varias alternativas, que se verán más adelante.
Aviso: Aunque no es frecuente, algunas versiones 2.X del sistema no tienen inicializado el vector de la INT
2Fh. Por ello, es una buena práctica asegurarse de que esta interrupción apunta a algo antes de llamarla (por
ejemplo, verificando que el segmento es distinto de cero). Por otro lado, el comando PRINT del DOS en las
versiones 2.X del sistema gestiona de tal manera la INT 2Fh que ninguna otra aplicación puede emplearla.
Por ello, el método de la interrupción Multiplex está más bien reservado para versiones 3.0 o superiores
(también la 2.X si el usuario prescinde de PRINT).
Se trata de una tarea bastante sencilla en sí, aunque hay que tener en cuenta una serie de factores. En
primer lugar, el programa debe restaurar todos los vectores de interrupción que había interceptado. Ello
significa que si ha sido instalado tras él otro programa residente que modifica uno de los vectores que él
interceptaba, ya no es posible restaurarlo. Por ello, un primer requisito para permitir la desinstalación es que
sea el último programa residente cargado que utiliza un vector de interrupción dado. Esto es fácil de
verificar, basta con comprobar que todas las interrupciones interceptadas siguen apuntando a una copia de él.
Si esta prueba es superada satisfactoriamente, puede procederse a restaurar los vectores de interrupción y
liberar la memoria ocupada de una de las dos siguientes maneras:
1. Pasando en ES el segmento donde está cargado el programa y llamando a la función 49h del DOS
para liberar el bloque de memoria.
2. Liberando directamente el bloque de memoria al colocar una palabra a cero en los bytes del MCB que
identifican al propietario del bloque. Este método puede ser más seguro si está instalado un gestor de
memoria expandida extraño, aunque es menos elegante y quizá menos recomendable.
Por lo general, no tiene mucho sentido que un usuario elimine un programa residente después de haber
cargado otro -aunque ello sea posible- ya que se origina un hueco en la memoria que normalmente no se
utilizará para nada -el DOS asigna siempre el mayor bloque disponible al cargar cualquier aplicación-, aunque
esto es realmente problema exclusivo del usuario.
Como se verá después, ciertos programas residentes sofisticados permiten ser desinstalados aún sin ser los
últimos instalados; sin embargo, estos programas residentes tienen que tener algo en común: comportarse de la
misma manera y actuar también de una manera definida. Ello significa que si entre dos programas residentes
que cumplen el mismo convenio el usuario instala un programa que no lo respeta, se pierden todas las
posibilidades.
5 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
Para solucionar el problema de que dos programas residentes no pueden utilizar el mismo valor de
identificación en la interrupción Multiplex, los señores de BMB Compuscience Canada pensaron un buen
sistema, publicado en el INTERRUP.LST de Ralf Brown, que expongo a continuación.
La idea consiste en asignar dinámicamente el valor del registro AH empleado al llamar a la interrupción
Multiplex. Para ello se empieza, por ejemplo, con AH=0C0h. Se coloca un 0 en AL para solicitar chequeo de
instalación y se hace que los registros ES:DI valgan 0EBEBh:0BEBEh (porque sí), llamando a continuación a
la INT 2Fh. A la vuelta se devuelve en 0 en AL para indicar programa no instalado, un 1 para señalar
además que no se debe instalar, y FFh para decir que ya está instalado... ¿quién?: un programa cuyo nombre
de fabricante abreviado (MMMM), nombre de producto (PPPPPPPP) y versión (NNNN) están en ES:DI
de la forma "BMB MMMMPPPPPPPPvNNNN". Si se comprueba que ese programa no es el buscado, se
incrementa AH y si AH es menor o igual a 0FFh se repite el proceso. De este bucle puede salirse de dos
maneras: encontrando el programa buscado (y su ubicación en memoria) o sin encontrarle, en cuyo caso
también se habrá localizado algún valor de AH aún no utilizado por ninguna tarea residente (a no ser que el
usuario haya instalado ya 64 programas residentes con esta técnica). Lógicamente, el programa residente
debe interceptar también INT 2Fh y devolver (cuando alguien pregunta por él) un valor FFh en AL y, si
además el que preguntaba llamaba con ES:DI=0EBEBh:0BEBEh entonces debe devolver en ES:DI la
información antes mencionada. Lo de emplear 0EBEBh y 0BEBEh constituye un mecanismo similar a un
password, para evitar que al programa que llama a INT 2Fh se le modifique ES:DI sin que lo sepa.
El convenio anterior adolece de un defecto importante: ya puestos a determinar con tanto detalle el
fabricante, nombre y versión del programa, ¿por qué no colocar más información útil?. Por ejemplo, sería
interesante disponer de información sobre los contenidos previos de los vectores de interrupción que el
programa ha desviado, lo cual permitiría su desinstalación aunque no sea el último cargado, ser desinstalado
por parte de otros programas o incluso emplear ciertas técnicas de relocalización en memoria para evitar la
fragmentación de la misma cuando es desinstalado. Con objeto de aumentar la eficacia, el autor de este libro
desarrolló un método nuevo, extensión del expuesto en el apartado anterior, que permitiera sacar mayor
partido de la interrupción Multiplex. Al igual que el anterior, el nuevo convenio también está publicado en el
INTERRUP.LST, lo que garantiza su difusión y la inversión de quienes decidan emplearlo.
El método es similar al anterior, con la diferencia de que en ES:DI está almacenado en el momento de
llamar el valor 1492h:1992h. En AH se indica, como siempre, el número de entrada de la interrupción
Multiplex y en AL se coloca un 0 solicitando chequeo de instalación. Tras llamar, si AL devuelve un 1 ó un
0FFh significa que esa entrada ya está empleada, si devuelve un 0 significa que está libre y que puede ser
utilizada. Hasta ahora, todo sucede como es costumbre en los programas que utilizan la interrupción Multiplex.
Sin embargo, por el hecho de haber llamado con ES:DI=1492h:1992h, el programa residente sabe que quien
lo llama es alguien que respeta el convenio. Por ello, además de devolver un 0FFFFh en AX, modifica ES y
DI para apuntar a una tabla con la siguiente información:
6 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
El valor ubicado en ES:DI-14 puede ser útil de cara a deducir el tamaño de la parte del PSP que
permanece residente, ya que se considera que la ubicación del programa comienza en el offset 0 relativo al
segmento definido en ES:DI-16 y, por tanto, el tamaño del programa definido en ES:DI-12 es relativo
también con offset 0 a ese segmento. Si bien se puede opinar que son demasiados campos, son sólo poco
más de 16 bytes los que se añaden al programa residente. Además, muchas de las variables anteriores han de
estar definidas necesariamente: ¿por qué no juntarlas de una manera convenida?. En la tabla anterior se define
un puntero a una estructura con información sobre los vectores interceptados. No se respeta sin embargo el
formato de los encabezamientos de interrupción propuesto en la BIOS del PS/2 (la intención de IBM es
buena, pero ha llegado demasiado tarde).
En las primeras versiones de este convenio ya no existían más reglas. Sin embargo, al final comprendí la
necesidad de ampliar las prestaciones. Por ello, el convenio fue ampliado con dos tablas más, opcionales, que
es conveniente rellenar incluso también en aquellos TSR más sencillos que ocupan menos de 64 Kb y son
totalmente reubicables (no contienen referencias absolutas a segmentos). Estas tablas permitirían a un
hipotético sistema operativo mover los programas residentes para evitar la fragmentación de la memoria,
tarea que mientras tanto puede realizar algún programa de utilidad. Aquellos TSR que contengan referencias
en su propio código o datos cambiando el segmento (sólo puede ocurrir normalmente en los programas EXE)
el convenio establece que deben soportar el parámetro /SR: ante él, al ser recargados en memoria desde
disco (necesario para la reubicación) deben instalarse silenciosamente sin chitar, autoinhibiéndose a
continuación. En general, la mayoría de los programas residentes escritos en ensamblador son relocalizables,
así como los elaborados en el modelo Tiny del C, por lo que no es muy complejo realizar esta tarea. La única
pega que se puede poner es que, por desgracia, ¡pocos programas usan este convenio!.
7 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
01h WORD offset a una variable que puede inhibir o activar el TSR
---Si el bit 0 en el offset 00h está a 0:
03h DWORD puntero a cadena ASCIIZ con el nombre del fichero ejecutable que
soporta el parámetro /SR (instalación e inhibición silenciosa)
07h DWORD puntero a la primera variable a inicializar en la copia recargada
de disco desde el TSR aún residente.
0Bh DWORD puntero a la última variable (todas están en el mismo bloque).
La variable que activa o inhibe el TSR permite paralizarlo momentáneamente antes de realizar ciertas
tareas críticas, si bien no está pensada su utilización de cara a relocalizarlo en memoria o a desinstalarlo.
A continuación se listan dos rutinas que habrá de incorporar todo programa que desee emplear este
convenio (u otras equivalentes). Las rutinas las he denominado mx_get_handle y mx_find_tsr. La primera
permite buscar un valor para la interrupción Multiplex aún no empleado por otra tarea residente, tanto si ésta
es del convenio como si no. La segunda sirve para que el programa residente se busque a sí mismo en la
memoria. En esta segunda rutina se indica el tamaño de la cadena de identificación (la que contiene el nombre
del fabricante, programa y versión) en CX. Si no se encuentra el programa residente en la memoria, puede
repetirse la búsqueda con CX indicando sólo el tamaño del nombre del fabricante y el programa, sin incluir el
de la versión: así se podría advertir al usuario que tiene instalada ya otra versión distinta.
mx_get_handle PROC
MOV AH,0C0h
mx_busca_hndl: PUSH AX
MOV AL,0
INT 2Fh
CMP AL,0FFh
POP AX
JNE mx_si_hueco
INC AH
JNZ mx_busca_hndl
mx_no_hueco: STC
RET
mx_si_hueco: CLC
RET
mx_get_handle ENDP
mx_find_tsr PROC
MOV AH,0C0h
mx_rep_find: PUSH AX
PUSH CX
PUSH SI
PUSH DS
PUSH ES
PUSH DI
MOV AL,0
PUSH CX
INT 2Fh
POP CX
8 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
CMP AL,0FFh
JNE mx_skip_hndl ; no hay TSR ahí
CLD
PUSH DI
REP CMPSB ; comparar identificación
POP DI
JE mx_tsr_found ; programa buscado hallado
mx_skip_hndl: POP DI
POP ES
POP DS
POP SI
POP CX
POP AX
INC AH
JNZ mx_rep_find
STC
RET
mx_tsr_found: ADD SP,4 ; «sacar» ES y DI de la pila
POP DS
POP SI
POP CX
POP AX
CLC
RET
mx_find_tsr ENDP
La rutina mx_unload desinstala un programa residente que verifique el convenio; basta con indicar el
número de interrupción Multiplex que emplea el TSR. El proceso de desinstalación falla si se ha instalado
después un TSR que no verifica el convenio y tiene alguna interrupción en común, ya que la rutina no puede
en ese caso recorrer la cadena de vectores para modificarla anulando la tarea residente. Para que un TSR se
auto-desinstale basta con que suministre a esta rutina su propio número de identificación. El método empleado
por la rutina para cambiar los vectores de interrupción no es muy ortodoxo, pero simplifica el algoritmo y
posee un nivel de seguridad razonable. Esta rutina da dos pasadas: el objeto de la primera es sólo asegurar
que el TSR puede ser desinstalado antes de empezar a cambiar ningún vector. En la segunda, se cambian los
enlaces entre los vectores y se libera la memoria, bien llamando al DOS o al controlador XMS (según quién la
haya asignado). Hay una maniobra más o menos complicada para hacer que el vector 2Fh sea el último
restaurado, con objeto de poder seguir la cadena de interrupciones hasta el propio TSR invocando la INT
2Fh.
mx_unload PROC
PUSH ES
CALL mx_ul_tsrcv?
JNC mx_ul_able
POP ES
RET
mx_ul_able: XOR AL,AL
XCHG AH,AL
MOV BP,AX ; BP=entrada Multiplex del TSR
MOV CX,2
mx_ul_pasada: PUSH CX ; siguiente pasada
LEA SI,tabla_vectores
MOV CL,ES:[SI-1]
MOV CH,0 ; CX = nº vectores
9 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
mx_ul_masvect: POP AX
PUSH AX ; pasada en curso
DEC AL
PUSH CX
mx_ul_2f: MOV AL,ES:[SI] ; vector en curso
JNZ mx_ul_pasok
CMP CX,1 ; ¿último vector?
JNE mx_ul_noult
MOV AL,2Fh
LEA SI,tabla_vectores
mx_ul_busca2f: CMP ES:[SI],AL ; ¿INT 2Fh?
JE mx_ul_pasok
ADD SI,5
JMP mx_ul_busca2f
mx_ul_noult: CMP AL,2Fh ; ¿restaurar INT 2Fh?
JNE mx_ul_pasok
ADD SI,5
JMP mx_ul_2f
mx_ul_pasok: PUSH ES
PUSH AX
MOV AH,0
SHL AX,1
SHL AX,1
DEC AX
MOV CS:mx_ul_tsroff,AX
MOV CS:mx_ul_tsrseg,0 ; apuntar a tabla vectores
POP AX
PUSH AX
MOV AH,35h
INT 21h ; vector en ES:BX
POP AX
MOV CL,4
SHR BX,CL
MOV DX,ES
ADD DX,BX ; INT xx en DX (aprox.)
MOV AH,0C0h
mx_ul_masmx: CALL mx_ul_tsrcv?
JNC mx_ul_tsrcv
JMP mx_ul_otro
mx_ul_tsrcv: PUSH ES:[DI-16] ; ...TSR del convenio en ES:DI
PUSH ES:[DI-12]
MOV DI,ES:[DI-8] ; offset a la tabla de vectores
MOV CL,ES:[DI-1]
MOV CH,0 ; número de vectores en CX
mx_ul_buscav: CMP AL,ES:[DI]
JE mx_ul_usavect ; este TSR usa vector analizado
ADD DI,5
LOOP mx_ul_buscav
ADD SP,4 ; no lo usa
JMP mx_ul_otro
mx_ul_usavect: POP CX ; tamaño del TSR
POP BX ; segmento del TSR
CMP DX,BX
JB mx_ul_otro ; la INT xx no le apunta
ADD BX,CX
CMP DX,BX
JA mx_ul_otro ; la INT xx le apunta
PUSH AX
XOR AL,AL
XCHG AH,AL
CMP AX,BP ; ¿es el propio TSR?
POP AX
10 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
JNE mx_ul_chain ; no
POP ES ; sí: ¡posible reponer vector!
POP CX
POP BX
PUSH BX
PUSH CX
PUSH ES
DEC BX
JNZ mx_ul_norest ; no es la segunda pasada
POP ES ; segunda pasada...
PUSH ES
PUSH DS
MOV BX,CS:mx_ul_tsroff ; restaurar INT's
MOV DS,CS:mx_ul_tsrseg
CLI
MOV CX,ES:[SI+1]
MOV [BX+1],CX
MOV CX,ES:[SI+3]
MOV [BX+3],CX
STI
POP DS
mx_ul_norest: POP ES
POP CX
ADD SI,5 ; siguiente vector
DEC CX
JZ mx_unloadable ; no más, ¡desinstal-ar/ado!
JMP mx_ul_masvect
mx_ul_chain: MOV CS:mx_ul_tsroff,DI ; ES:DI almacena la dirección
MOV CS:mx_ul_tsrseg,ES ; de la variable vector
MOV DX,ES:[DI+1]
MOV CL,4
SHR DX,CL
MOV CX,ES:[DI+3]
ADD DX,CX ; INT xx en DX (aprox.)
MOV AH,0BFh
mx_ul_otro: INC AH ; a por otro TSR
JZ mx_ul_exitnok ; ¡se acabaron!
JMP mx_ul_masmx
mx_ul_exitnok: ADD SP,6 ; equilibrar pila
POP ES
STC
RET ; imposible desinstalar
mx_unloadable: POP CX
DEC CX
JZ mx_ul_exitok ; desinstalado
JMP mx_ul_pasada ; 1ª pasada exitosa: por la 2ª
mx_ul_exitok: TEST ES:info_extra,111b ; ¿tipo de instalación?
MOV ES,ES:segmento_real ; segmento real del bloque
JZ mx_ul_freeml ; cargado en RAM convencional
CMP xms_ins,1
JNE mx_ul_freeml ; no hay controlador XMS (¿?)
MOV DX,ES
MOV AH,11h
CALL gestor_XMS ; liberar memoria superior
POP ES
CLC
RET
mx_ul_freeml: MOV AH,49h
INT 21h ; liberar bloque de memoria ES:
POP ES
CLC
RET
11 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
Los dos programas siguientes constituyen dos pequeñas utilidades de apoyo a los TSR de este convenio.
TSRLIST lista los TSR del convenio que están instalados en el ordenador, con información detallada;
TSRKILL permite eliminar uno o todos los TSR que estén instalados en cualquier orden, no sólo
necesariamente el último que fue cargado. Lógicamente, si entre varios programas que respetan el convenio
hay uno que lo viola, TSRKILL puede no ser capaz de desinstalar un TSR del convenio. En ese caso, se
informa de qué vector ha sido el culpable. Ejemplo de salida de TSRLIST /V:
La entrada multiplex 210 (0D2h) de que informa TSRLIST es utilizada por QEMM386; TSRLIST
también informa de las entradas que están siendo utilizadas por programas que no respetan el convenio,
aunque lógicamente no da más información.
/********************************************************************/
/* */
/* TSRLIST 1.3 - Utilidad de listado de TSR's normalizados - BC++ */
/* */
/********************************************************************/
#include <dos.h>
#include <string.h>
12 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
void cabecera(),
listar_tsr(),
obtener_item();
if (raro) {
printf("\n- ID de programas residentes que incumplen convenio: ");
for (entrada=0; entrada<64; entrada++)
if (tsr_raro[entrada]) printf("%2d; ", entrada+0xc0);
if (vect) printf("\n");
}
if (!vect) printf("\n- Ejecute con /V para listado de vectores.\n");
}
r.r_ax=entrada << 8;
r.r_es=0x1492; r.r_di=0x1992;
intr (0x2f, &r);
return ((r.r_ax==0xFFFF) &&
(peek(r.r_es,r.r_di-4)==9002) && (peek(r.r_es,r.r_di-2)==10787));
}
13 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
printf("\n");
}
######################################################################
14 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
/********************************************************************/
/* */
/* TSRKILL 1.3 - Utilidad de desinstalación de TSRs normalizados. */
/* Compilar en el modelo «Large» de Borland C. */
/* */
/********************************************************************/
#include <dos.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
struct tsr_info {
unsigned segmento_real;
unsigned offset_real;
unsigned ltsr;
unsigned char info_extra;
unsigned char multiplex_id;
unsigned vectores_id;
unsigned extension_id;
unsigned long validacion;
char autor_nom_ver[80];
};
int tsr_convenio(),
mx_unload(),
existe_xms();
void liberar_umb(),
desinstalar();
if (mxid==-1) {
for (mxid=0xc0; mxid<=0xFF; mxid++)
if (tsr_convenio(mxid, &tsr)) desinstalar (mxid);
}
else
desinstalar (mxid);
}
15 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
if (correcto || (vector<0x100)) {
strcpy (cadaux, nombre); p=cadaux;
while (*p) if ((*p++)==':') *(p-1)=0; p=cadaux;
while (*p++); strcpy (cadena, p); /* nombre programa */
strcat (cadena, " ");
while (*p++); strcat (cadena, p); /* versión */
strcat (cadena, " de ");
strcat (cadena, cadaux); /* autor */
}
if (correcto)
printf(" - Desinstalado el %s\n", cadena);
else {
if (vector==0x100)
printf (" - No hay TSR %u o no es del convenio.\n", mxid);
else if (vector==0x101)
printf (" - HBREAK es «demasiado fuerte» para TSRKILL.\n");
else if (vector==0x102)
printf (" - 2MGUI es «demasiado fuerte» para TSRKILL.\n");
else {
printf (" - El %s no se puede desinstalar: ", cadena);
printf ("fallo en el vector %02X.\n", vector);
}
}
}
16 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
*interrupción = vector;
*tsrnombre = tsr->autor_nom_ver;
if (strstr(*tsrnombre, "HBREAK")!=NULL) {
posible=0; *interrupción=0x101; }
if (strstr(*tsrnombre, "2MGUI")!=NULL) {
posible=0; *interrupción=0x102; }
if (posible) {
for (i=0; i<numvect; i++) {
vector = peekb(FP_SEG(tsr), tsr->vectores_id+5*i);
sgm = peek(FP_SEG(tsr), tsr->vectores_id+5*i+3);
ofs = peek(FP_SEG(tsr), tsr->vectores_id+5*i+1);
if ((tablaptr[i][0]==0) && (tablaptr[i][1]==0)) {
interr=MK_FP(sgm, ofs);
setvect (vector, interr);
}
else {
asm cli
poke (tablaptr[i][0], tablaptr[i][1], ofs);
poke (tablaptr[i][0], tablaptr[i][1]+2, sgm);
asm sti
}
}
r.r_ax=entrada << 8;
r.r_es=0x1492; r.r_di=0x1992;
intr (0x2f, &r);
*info = MK_FP(r.r_es, r.r_di-16);
return ((r.r_ax==0xFFFF) &&
(peek(r.r_es,r.r_di-4)==9002) && (peek(r.r_es,r.r_di-2)==10787));
}
int existe_xms ()
{
struct REGPACK r;
17 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
asm {
push es; push si; push di;
mov ax,4310h
int 2Fh
mov word ptr controlador,bx
mov word ptr controlador+2,es
mov ah,11h
mov dx,segmento
call controlador
pop di; pop si; pop es;
}
}
La interrupción Multiplex presenta un elevado nivel de polución debido al gran número de programas que
la utilizan incorrectamente. En algunos casos se soluciona el problema instalando primero los programas
conflictivos y después los que trabajan bien. Lo mínimo que se puede exigir a un programa residente que
utilice esta interrupción es que soporte el chequeo de instalación (la llamada con AL=0) y devuelva una señal
de reconocimiento afirmativo (AL=0FFh) si está empleando esa entrada en cuestión. Sin embargo, algunos no
llegan ni a eso. Por fortuna, son tan malos que casi nadie los emplea. Sin embargo, con objeto de solucionar
estos casos, Ralf Brown -autor del INTERRUP.LST- ha desarrollado un método alternativo basado en la
interrupción 2Dh. Esta interrupción no ha sido empleada hasta ahora por el DOS ni por ninguna aplicación
importante. La propuesta AMIS (Alternate Multiplex Interrupt Specification) implementa un sistema
estandarizado de interface con los programas residentes. Habida cuenta de que las principales empresas
desarrolladoras de software de sistemas ojean el INTERRUP.LST antes de utilizar una interrupción, para
evitar conflictos entre aplicaciones, es de esperar que la propia Microsoft no utilice tampoco la INT 2Dh para
sus propósitos en futuras versiones del DOS. Por tanto, no es muy arriesgado seguir este convenio. La
información que expongo a continuación se corresponde con la versión 3.4 de la especificación.
Los programas que emplean la INT 2Dh deben interceptarla e implementar una serie de funciones. Como
luego veremos, no es necesario que soporten todas las que propone el convenio. A la hora de llamar a la INT
2Dh se indicará en AH, tal como se hacía con la interrupción Multiplex, el número de entrada y en AL la
función. Todo el funcionamiento se basa en invocar funciones en el programa residente. El inconveniente de
ejecutar código en la copia residente es que ocupa algo más de memoria, y la necesidad de implementar
dichas funciones. La ventaja de ejecutar código en la copia residente es que ésta puede, en donde sea
procedente, restaurar el estado del sistema de manera más completa o realizar tareas específicas que sean
necesarias. Por citar un ejemplo, TSRKILL no puede desinstalar las conocidas utilidades HBREAK o
2MGUI, que, en cambio, con la propuesta AMIS podrían haber soportado una función de desinstalación
accesible por cualquier agente externo. Existen las siguientes funciones:
- Función 0: Chequeo de instalación. Si no hay un TSR utilizando ese número se devuelve un 0 en AL. En
caso contrario se devuelve un 0FFh en AL; en CX se devuelve además el número de versión del interface
AMIS que soporta el TSR (ej. CX=340h para la v3.4); en DX:DI se entrega la dirección de la cadena de
identificación, con el siguiente formato:
18 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
Offset 16 (hasta 64 bytes): Cadena ASCIIZ (terminada en 0) con la descripción del producto; este
campo puede constar simplemente de un cero si no se desea inicializarlo.
- Función 1: Obtener punto de entrada. Como llamar a la INT 2Dh puede ser relativamente lento (debido
al elevado número de programas residentes que puede haber instalados) con esta función se solicita al TSR un
punto de entrada alternativo para poder llamarlo de una manera más directa sin la INT 2Dh. Si devuelve un 0
en AL, significa que el TSR debe ser invocado obligatoriamente vía INT 2Dh. Si devuelve un 0FFh en AL ello
implica que soporta una llamada directa, cuyo punto de entrada devuelve en DX:BX.
- Función 2: Desinstalación. A la entrada, se indica al TSR en DX:BX el punto donde deberá saltar tras
su autodesinstalación (si la soporta). A la vuelta, el TSR devuelve un código en AL que se interpreta:
0 - Función no implementada.
1 - Fallo.
2 - No es posible desinstalar ahora, el TSR lo intentará cuando pueda.
3 - Es seguro desinstalar, pero el TSR no dispone de rutina al efecto. El TSR está aún habilitado y
devuelve en BX el segmento del bloque de memoria donde reside.
4 - Es seguro desinstalar, pero el TSR no dispone de rutina al efecto. El TSR está inhibido y devuelve en
BX el segmento del bloque de memoria donde reside.
5 - No es seguro desinstalar ahora. Intentar de nuevo más tarde.
0FFh - Todo ha ido bien, TSR desinstalado: retorna con AX corrompido a la dirección DX:BX.
- Función 3: Solicitud de POP-UP. Esta función está diseñada sólo para los programas residentes que
muestran menús en pantalla al ser activados (normalmente con una combinación de teclas). El valor que
devuelve en AL se interpreta:
0 - Función no implementada.
1 - Imposible determinar.
2 - La interrupción indicada ha sido interceptada.
3 - La interrupción indicada ha sido interceptada, DX:BX apunta a la rutina que la gestiona.
4 - Se devuelve en DX:BX la lista de interrupciones interceptadas.
0FFh - Esa interrupción no ha sido interceptada.
Esto en principio significa que el TSR puede hacer casi lo que le da la gana cuando le preguntan qué
interrupciones controla. Los valores 1 al 3 sólo están definidos por compatibilidad con versiones anteriores de
la especificación (v3.3), el autor del convenio avisa que no serán quizá soportados en otras versiones. Por
19 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
tanto, lo más normal es que el TSR devuelva un valor 4 sin hacer caso del valor de BL (de lo contrario, el
programa que llama tendría que hacer un molesto bucle comprobando todas las interrupciones). Sería una
lástima que un TSR devolviera un valor 0. El formato de la lista de interrupciones interceptadas es:
Offset 0 (1 bytes): Número del vector (el último de la lista es siempre 2Dh).
Offset 1 (2 bytes): Offset a la rutina de control de interrupción.
La rutina de control de interrupción respeta este formato, propuesto por IBM en las BIOS de PS/2:
Offset 0 (2 bytes): Salto corto a donde realmente empieza la rutina de control (10EBh).
Offset 2 (4 bytes): Dirección previa de ese vector de interrupción.
Offset 6 (2 bytes): Valor 424Bh (consejo de IBM).
Offset 8 (1 byte): Banderín de EOI, 0 si es interrupción software o controlador secundario de la
interrupción hardware, 80h si es el controlador primario de la interrupción hardware (debe enviar un comando
EOI al controlador de interrupciones 8259).
Offset 9 (2 bytes): Salto corto a la rutina de reset hardware (que retornará con RETF).
Offset 0Bh (7 bytes): Reservados (a 0).
Offset 12h: Rutina que controla la interrupción.
- Funciones 5 y siguientes: Reservadas para futuras versiones del convenio, devuelven 0 al no estar
implementadas.
Por supuesto, los programas que cumplan la propuesta AMIS deben asignar dinámicamente el número de
entrada que van a utilizar en la INT 2Dh, buscando uno libre. Para chequear su instalación han de emplear los
16 bytes que indican el nombre del fabricante y el programa. Como dije al principio, no es preciso que un
programa soporte todas estas funciones: para cumplir con la versión 3.4 de la especificación basta con
implementar las funciones 0, 2 (sin obligación de disponer de rutina de desinstalación) y la 4 (devolviendo un
valor 4).
Cualquiera de los tres métodos expuestos es válido para lograr una correcta localización del programa
residente en memoria. El más sencillo es el primero (aunque ES:DI puede estar asignado de la manera que el
lector considere oportuna, por supuesto). Sin embargo, son los dos últimos los más recomendables, por las
prestaciones que ofrecen. El más completo es la propuesta AMIS.
20 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
los 171 últimos bytes del PSP que no son vitales para el sistema: en efecto, en el offset 5Ch comienza el
primer FCB; los 7 bytes anteriores corresponden al FCB extendido -circunstancia que poco suelen poner de
relieve los libros técnicos- por lo que el único área que es obligatorio respetar es la zona 00-54h: 85 bytes
(incluso este área podría ser también casi totalmente ocupada, como se dijo antes, pero después de finalizar
la ejecución del programa). Por comodidad, se respetarán los primeros 96 bytes, justo 6 párrafos: moviendo
el programa hacia atrás un número entero de párrafos, al final resulta sencillo desviar los vectores de
interrupción decrementando su segmento en 6 unidades menos antes de desviarlos. Esta treta sólo es factible,
por supuesto, en programas de un solo segmento, tipo COM. Los de tipo EXE normalmente dejarán
residente todo el PSP, ya que es un segmento previo al programa (de hecho, al terminar residente hay que
añadir el tamaño del PSP) y sería complicada la reubicación.
Es cierto que estas técnicas, con programas que se mueven a si mismos dando vueltas por la memoria,
automodificándose ... no son consideradas elegantes por los programadores conservadores, y no se pueden
hacer estas salvajadas en entornos con protección de memoria (UNIX, etc.); de hecho, Niklaus Wirth se
llevaría sin duda las manos a la cabeza. Sin embargo el DOS y el 8086 las permiten y pueden ser bastante
útiles, en especial para los programadores de sistemas. Además, escondiendo bien los fuentes, lo más
probable es que nadie se entere de ello...
Los TSR más eficientes deben detectar la presencia de memoria superior e instalarse automáticamente en
ella, por varios motivos. Por un lado, se mejora el rendimiento en aquellas máquinas con usuarios inexpertos
que no emplean el HILOAD o el LOADHIGH del sistema. Por otro, un programa residente puede ocupar
mucho más espacio en disco que lo que luego ocupará en memoria. Si se utiliza LOADHIGH o HILOAD, el
sistema intenta reservar memoria para poder cargar el fichero desde disco. Esto significa que puede haber
casos en que no tenga suficiente memoria para cargar el programa, con lo que lo cargará en memoria
convencional. Sin embargo, ese TSR tal vez hubiera cabido en la memoria superior: si es el propio TSR el que
se auto-relocaliza (copiándose a sí mismo) hacia la memoria superior, este problema desaparece. Tratándose
de programas de un solo segmento real, como los COM, no es problema alguno realizar la operación de
copia.
Con DR-DOS y, en general, con ciertos controladores de memoria (tales como QEMM) la memoria
superior es gestionada por la especificación de memoria extendida XMS (véase apartado 8.3). Para utilizar la
memoria superior en estos sistemas hay que detectar la presencia del controlador XMS y pedirle la memoria
(también habrá que llamarle después para liberarla). Con MS-DOS 5.0 y posteriores sólo existe memoria
superior XMS si NO se indica DOS=UMB en el CONFIG.SYS; sin embargo, la mayoría de los usuarios
suelen indicar esta orden con objeto de que el MS-DOS permita emplear LOADHIGH y DEVICEHIGH.
Por desgracia, con MS-DOS, cuando el DOS gestiona la memoria superior, se la roba toda al controlador
XMS. Por tanto, habrá que pedírsela al DOS. Con MS-DOS, el procedimiento general es el siguiente:
Primero, preservar el estado de la estrategia de asignación de memoria y el estado de los bloques de memoria
superior (si están o no conectados con los de la memoria convencional). A continuación, se conectan los
bloques de memoria superior con los de la convencional, por si no lo estaban. Seguidamente, se modifica la
estrategia de asignación de memoria, estableciendo -por ejemplo- un best fit en memoria superior.
Finalmente, se asigna memoria utilizando la función convencional de asignación (48h). Tras estas operaciones,
habrá de ser restaurada la estrategia de asignación de memoria y el estado de los bloques de memoria
superior.
Es conveniente intentar primero asignar memoria superior XMS: si falla, se puede comprobar si la versión
del DOS es 5 (o superior) y aplicar el método propio que requiere este sistema. De esta manera, los TSR
podrán asignar memoria superior sea cual sea el sistema operativo, controlador de memoria o configuración
del sistema activos. Sin embargo, con el método propio del DOS 5.0 hay un inconveniente: al acabar la
21 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
ejecución del código de instalación del TSR, el DOS ¡libera el bloque de memoria que se asignó con la
función 48h!. Para evitar esto, hay dos métodos: uno, consiste en terminar residente (aunque sea dejando sólo
los primeros 96 bytes del PSP) con objeto de que el sistema respete el bloque de memoria creado. Si no se
desea este ligero derroche de memoria convencional, hay un método más contundente. Consiste en engañar al
DOS y, tras asignar el bloque de memoria, modificar en su correspondiente bloque de control la información
del propietario (PID), haciéndole apuntar -por ejemplo- a sí mismo. De esta manera, al acabar el programa,
el DOS recorrerá la cadena de bloques de memoria y no encontrará ninguno que pertenezca al programa que
finaliza... conviene también, en este caso, que los dos primeros bytes del bloque de memoria superior
contengan la palabra 20CDh (ubicada al inicio de los PSP), con objeto de que algunos programas de
diagnóstico lo confundan con un programa (no obstante, el comando MEM del DOS no requiere este detalle
y lo tomaría directamente por un programa). También hay que crear el nombre del programa en los 8 últimos
bytes del MCB manipulado. Las siguientes rutinas asignan memoria superior XMS (UMB_alloc) o memoria
superior DOS 5 (UPPER_alloc):
UMB_alloc PROC
PUSH BX
PUSH CX
PUSH DX
CMP xms_ins,1
JNE no_umb_disp ; no hay controlador XMS
MOV DX,AX ; número de párrafos
MOV AH,10h ; solicitar memoria superior
CALL gestor_XMS
CMP AX,1 ; ¿ha ido todo bien?
MOV AX,BX ; segmento UMB/código de error
JNE XMS_fallo ; fallo
POP DX ; ok
POP CX
POP BX
CLC
RET
no_umb_disp: MOV AX,0
XMS_fallo: POP DX
POP CX
POP BX
STC
RET
UMB_alloc ENDP
UPPER_alloc PROC
PUSH AX
MOV AH,30h
INT 21h
CMP AL,5
POP AX
JAE UPPER_existe
STC
JMP UPPER_fin ; necesario DOS 5.0 mínimo
UPPER_existe: PUSH AX ; preservar párrafos...
MOV AX,5800h
22 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
INT 21h
MOV alloc_strat,AX ; preservar estrategia
MOV AX,5802h
INT 21h
MOV umb_state,AL ; preservar estado UMB
MOV AX,5803h
MOV BX,1
INT 21h ; conectar cadena UMB's
MOV AX,5801h
MOV BX,41h
INT 21h ; High Memory best fit
POP BX ; ...párrafos requeridos
MOV AH,48h
INT 21h ; asignar memoria
PUSHF
PUSH AX ; guardado el resultado
MOV AX,5801h
MOV BX,alloc_strat
INT 21h ; restaurar estrategia
MOV AX,5803h
MOV BL,umb_state
XOR BH,BH
INT 21h ; restaurar estado cadena UMB
POP AX
POPF
JC UPPER_fin ; hubo fallo
PUSH DS
DEC AX
MOV DS,AX
INC AX
MOV WORD PTR DS:[1],AX ; manipular PID
MOV WORD PTR DS:[16],20CDh ; simular PSP
PUSH ES
MOV CX,DS
MOV ES,CX
MOV CX,CS
DEC CX
MOV DS,CX
MOV CX,8
MOV SI,CX
MOV DI,CX
CLD
REP MOVSB ; copiar nombre de programa
POP ES
POP DS
CLC
UPPER_fin: RET
UPPER_alloc ENDP
La rutina UMB_alloc requiere una variable (xms_ins) que indique si está instalado el controlador de
memoria extendida, así como otra (gestor_XMS) con la dirección del mismo. La rutina UPPER_alloc necesita
una variable de palabra (alloc_strat) y otra de tipo byte (umb_state) en que apoyarse. El método expuesto
consiste en modificar el PID para evitar que el DOS desasigne la memoria al acabar la ejecución del
programa; también se coloca oportunamente la palabra 20CDh para simular un PSP y se asigna al nuevo
bloque de programa el mismo nombre que el del bloque de programa real. Los programas con autoinstalación
en memoria superior deberían tener un parámetro (al estilo del /ML de los de DR-DOS) para forzar la
instalación en memoria convencional si el usuario así lo requiere.
23 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
El auténtico empleo de memoria extendida para instalar programas residentes, aprovechando el modo
protegido en que está el ordenador con el controlador de memoria expandida instalado, no será tratado en
este libro. En particular, algún emulador de coprocesador para 386 emplea esas técnicas. Aquí nos
limitaremos a un objetivo más modesto, en los primeros 64 Kb de memoria extendida accesibles desde DOS.
El DR-DOS 6.0 fue el primer sistema operativo DOS que permitía instalar programas residentes en los
primeros 64 Kb de la memoria extendida, zona comúnmente conocida por HMA. La ventaja de cargar aquí
las utilidades residentes es que no ocupan memoria, dicho entre comillas (al menos, no memoria
convencional ni superior). El inconveniente principal es que este área es bastante limitada (en la práctica, algo
menos de 20 Kb libres) y la instalación un tanto compleja. Ciertos programas del sistema (COMMAND,
KEYB, NLSFUNC, SHARE, TASKMAX) se pueden cargar en esta zona -algunos incluso lo hacen
automáticamente-. Otro inconveniente es la complejidad de la instalación: normalmente los programas se
cargarán en el segmento 0FFFEh con un offset variable y dependiente de la zona en que sean instalados. Por
ello, el primer requisito que han de cumplir es el de ser relocalizables: en la práctica, la rutina de instalación
habrá de montar el código en memoria asignando posiciones absolutas a ciertos modos de direccionamiento.
El MS-DOS 5.0 también utiliza el HMA para cargar programas residentes; sin embargo no está tan
normalizado como en el caso del DR-DOS y es probable que en futuras versiones cambie el método. De una
manera torpe, Microsoft eligió a DISPLAY.SYS para ocupar parte del área que el propio DOS deja libre en
el HMA tras instalarse. Este fichero es utilizado en la conmutación de páginas de códigos (factible en
máquinas con EGA y VGA) para adaptar el juego de caracteres a ciertas lenguas. Hubiera sido mucho más
inteligente elegir el KEYB y otros programas similares que casi todo el mundo tiene instalados.
Por consiguiente, limitaremos el estudio al caso del DR-DOS. La información que viene a continuación fue
obtenida por la labor investigadora del autor de este libro, que la envió posteriormente a Ralf Brown para
incluirla en el Interrupt List. Conviene hacer ahora hincapié en que esta manera de gestionar el HMA, a nivel
de bloques de memoria, es propia del DR-DOS 6.0, y no de otras versiones anteriores de este sistema,
aunque probablemente sí de las posteriores. Para comprobar que en una máquina está presente el DR-DOS
puede verificarse la presencia de una variable de entorno del tipo «OS=DRDOS» y otra «VER=X.XX» con
la versión. En todo caso, es mucho más seguro utilizar una función del sistema al efecto:
El DR-DOS 6.0 implementa un nuevo servicio para gestionar la carga de programas en el HMA. Con las
siguientes líneas:
MOV AX,4458h
INT 21h
MOV SI,ES:[BX+10h] ; variable exclusiva de DR-DOS
MOV DI,ES:[BX+14h] ; otra variable de DR-DOS
24 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
reside en el HMA se puede utilizar esta técnica, para compartir la memoria con el sistema operativo.
En el HMA los bloques de memoria forman una cadena pero mucho más simple que en los demás tipos de
memoria. En concreto, tienen una cabecera de sólo 5 bytes: los dos primeros apuntan al offset del siguiente
bloque de memoria (cero si éste era el último) y los dos siguientes el tamaño de este bloque. Téngase en
cuenta que los bloques no han de estar necesariamente seguidos, por lo que la información del tamaño no
debe emplearse para direccionar al siguiente bloque: ¡para algo están los primeros dos bytes!. El quinto byte
puede tomar un valor entre 0 y 5 para indicar el tipo de programa, por este orden: System, KEYB,
NLSFUNC, SHARE, TaskMAX, COMMAND. Como se ve, no se almacena el nombre en formato ASCII
sino con un código. Los programas creados por el usuario pueden utilizar cualquiera de los códigos, aunque
quizá el más recomendable sea el 0 (de todas maneras, puede haber varios bloques con el mismo código).
Para cargar un programa residente aquí, primero se recorre la cadena de bloques libres hasta encontrar
uno del tamaño suficiente -si lo hay, claro está-. A continuación, se rebaja el tamaño de este bloque
modificando su cabecera. Después, se crea una cabecera para el nuevo bloque (que se sitúa al final del bloque
libre empleado, siempre tendiendo hacia direcciones altas) y se consulta la variable del DOS que indica el
primer bloque ocupado: el nuevo bloque creado habrá de apuntarle; a su vez, esta variable del DOS ha de
ser actualizada ya que desde ahora el primer bloque ocupado (bueno, en realidad el último) es el recién
creado. Ha de tenerse en cuenta que si lo que sobra del bloque libre que va a ser utilizado son menos de 16
bytes, se le debe desechar -porque así lo establece el sistema-, eliminándolo de la lista encadenada por el
simple procedimiento de hacer apuntar su predecesor a su sucesor. Lógicamente, si el bloque no tenía
predecesor -si era el primer bloque- lo que hay que hacer es modificar la variable del DOS que indica el
primer bloque libre para que apunte a su sucesor. En general, se trata de gestionar una lista encadenada, lo
que más que un problema de ensamblador lo es de sentido común. No eliminar los posibles bloques libres de
menos de 16 bytes es saltarse una norma del sistema operativo y podría tener consecuencias imprevisibles
con futuros programas cargados.
Una vez reservado espacio para el nuevo programa, habrá de copiarse este desde la memoria
convencional hacia el HMA, con una simple instrucción de transferencia. Allí -o antes de realizar la
transferencia- habrá de relocalizarse el código. Lo normal en los programas del sistema -y, por consiguiente,
lo más recomendable- es que nuestras aplicaciones corran en la dirección 0FFFEh:XXXX y no la
0FFFFh:XXXX como en principio podría suponerse, aunque quizá se trate de un detalle irrelevante. Por
último, se han de desviar los correspondientes vectores de interrupción a las nuevas rutinas del programa
residente. Obviamente, el programa principal instalador deberá acabar normalmente -y no residente-.
En general, la gestión del HMA es engorrosa porque el sistema realiza poco trabajo sucio, delegándoselo
al programa que quiera emplear este área.
25 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
derecha posible en cada tipo de pantalla activa. Con /C se puede modificar el valor del byte de atributos
empleado para colorear el reloj. /ML fuerza la instalación en memoria convencional. Por último, con /U se
puede desinstalar de la memoria, en los casos en que sea posible.
Es posible ejecutarlo cuando ya está instalado con objeto de cambiar sus parámetros o programar la
alarma. Si las coordenadas elegidas están fuera de la pantalla -ej., al cambiar a un modo de menos columnas
o filas- el resultado puede ser decepcionante (esto no sucede si /X=72). Si se produce un cambio de modo
de pantalla o una limpieza de la misma, el reloj seguirá apareciendo correctamente casi al instante -se refresca
su impresión 4 veces por segundo-.
Una vez cargado, se puede controlar la presencia o no en pantalla pulsado Ctrl-Alt-R o AltGr-R (sin
necesidad de volver a ejecutar el programa con los parámetros ON u OFF). Cuando se expulsa el reloj de la
pantalla, se restaura el contenido anterior a la aparición del reloj. Por ello, si se han producido cambios en el
monitor desde que apareció el reloj, el fragmento de pantalla restaurado puede quedar feo, aunque también
quedaría feo de todas maneras si se rellenara de espacios en blanco. De hecho, esto último es lo que sucede
cuando se trabaja con pantallas gráficas.
Cuando comienza a sonar la alarma, estando o no el reloj en pantalla, se puede pulsar Ctrl-Alt-R o
AltGr-R para cancelarla; de lo contrario avisará durante 15 segundos. Este es el único caso en que AltGr-R o
Ctrl-Alt-R no servirá para activar o desactivar el reloj (una posterior pulsación, sí). Después de haber sonado,
la alarma quedará desactivada y no volverá a actuar, ni siquiera al cabo de 24 horas.
El programa utiliza el convenio CiriSOFT para detectar su presencia en memoria, por lo que es
desinstalable incluso aunque no sea el último programa residente cargado, siempre que tras él se hayan
instalado sólo programas del convenio (o al menos otros que no utilicen las mismas interrupciones). Posee su
propia rutina de desinstalación (opción /U), con lo que no es necesario utilizar la utilidad general de
desinstalación. También está equipado con las rutinas que asignan memoria superior XMS o, en su defecto,
memoria superior solicitada al DOS 5.0: por ello, aunque el fichero ejecutable ocupa casi 6 Kb, sólo hacen
falta 1,5 Kb libres de memoria superior para instalarlo en este área, lo que se realiza automáticamente en
todos los entornos operativos que existen en la actualidad. Evidentemente, también se instala en memoria
convencional y sus requerimientos mínimos son un PC/XT y (recomendable) DOS 3.0 o superior.
Se utiliza la función de impresión en pantalla de la BIOS, con lo cual el reloj se imprime también en las
pantallas gráficas (incluida SuperVGA). Por ello, es preciso desviar la INT 10h con objeto de detectar su
invocación y no llamarla cuando ya se está dentro de ella (el reloj funciona ligado a la interrupción periódica y
es impredecible el estado de la máquina cuando ésta se produce). Si se anula la rutina que controla INT 10h,
en los modos gráficos SuperVGA de elevada resolución aparecen fuertes anomalías al deslizarse la pantalla
(por ejemplo, cuando se hace DIR) e incluso cuando se imprime; sin embargo, la BIOS es dura como una
roca (no se cuelga el ordenador, en cualquier caso). En los modos de pantalla normales no habría tanta
conflictividad, aunque conviene ser precavidos. La impresión del reloj se produce sólo 4 veces por segundo
para no ralentizar el ordenador; aunque se realizara 18,2 veces por segundo tampoco se notaría un retraso
perceptible. La interrupción periódica es empleada no sólo para imprimir el reloj sino también para hacer
sonar la música, enviando las notas adecuadamente al temporizador a medida que se van produciendo las
interrupciones. No se utiliza INT 1Ch porque la considero menos segura y fiable que INT 8; sin embargo se
toma la precaución de llamar justo al principio al anterior controlador de la interrupción. De la manera que
está diseñado el programa, es sencillo modificar las melodías que suenan, o crear una utilidad de música
residente por interrupciones para amenizar el uso del PC. Los valores para programar el temporizador, según
la nota que se trate, se obtienen de una tabla donde están ya calculados, ya que sería difícil utilizar la coma
flotante al efecto. Al leer el teclado, se tiene la precaución de comprobar si al pulsar Ctrl-Alt-R o AltGr-R la
BIOS o el KEYB han colocado un código Alt-R en el buffer. Esto suele suceder a menos que el KEYB no
sea demasiado compatible (Ctrl-Alt equivale, en teoría, a Alt a secas). Si así es, ese carácter se saca del
buffer para que no lo detecte el programa principal (si se sacara sin cerciorarse de que realmente está, en
26 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
caso de no estar el ordenador se quedaría esperando una pulsación de tecla). El método utilizado para
detectar la pulsación de AltGr en los teclados expandidos no funciona con el KEYB de DR-DOS 5.0/6.0
(excepto en modo KEYB US), aunque esto es un fallo exclusivo de dicho controlador.
Sin duda, la parte más engorrosa del programa es la interpretación de los parámetros en la línea de
comandos, tarea incómoda en ensamblador. Aún así, el programa es bastante flexible y se puede indicar, por
ejemplo, un parámetro /A=000020:3:48 para programar la alarma a las 20:03:48. Sin embargo, el uso del
ensamblador para este tipo de programas es más que recomendable: además de aumentar la fiabilidad del
código, el consumo de memoria es más que asequible, incluso en máquinas modestas.
Como se dijo al principio del capítulo, desde un programa residente no se pueden emplear directamente
los servicios del DOS. Si se salta esta norma se pueden crear programas que funcionen bajo determinadas
circunstancias, pero nada robustos. Por ejemplo, una utilidad para volcar la pantalla a un fichero en disco al
pulsar una cierta combinación de teclas, podría funcionar correctamente si es ejecutada desde la línea de
comandos, o desde dentro de un editor de texto. Sin embargo, si es invocada mientras se ejecuta un comando
DIR o mientras el programa principal está accediendo al disco o, simplemente, ejecutando cualquier función
del DOS tal como consultar la fecha, nuestra utilidad dejaría de funcionar correctamente. Y el fallo no consiste
en que la pantalla no se vuelque en disco, o se vuelque mal: el problema es que el ordenador se cuelga, siendo
preciso reinicializarlo.
El término no reentrante que se aplica al DOS significa que no puede ser empleado simultáneamente por
dos procesos, sin embargo se trata de un código serialmente reusable como veremos. El DOS posee tres
pilas internas: la pila de E/S (I/O Stack), la pila de disco (Disk Stack) y la pila auxiliar (Auxiliary Stack). Las
funciones 0 a la 0Ch utilizan la pila de E/S; las restantes utilizan la pila de disco. Si se llama al DOS durante un
error crítico (por ejemplo, DIR B: cuando no hay disquete en la unidad) se utiliza la pila auxiliar. La existencia
de estas pilas locales significa que si el DOS es llamado cuando ya estaba ejecutando una función (y ya había
conmutado a la pila interna correspondiente) volverá a inicializar el puntero de pila y en la nueva reentrada se
cargará el contenido previo de la pila. Si estaba ejecutando una función 0-0Ch y se le llama solicitando una
0Dh o superior, no habrá problemas, ya que hay dos pilas separadas para cada caso; sin embargo no suele
haber tanta suerte. Algunas funciones del DOS son tan simples que éste no conmuta a ninguna pila interna: la
33h, 50h, 51h, 62h y 64h: con ellas sí es reentrante; con las demás (que además son la mayoría y las más
interesantes) por desgracia no lo es.
Para solucionar este problema hay dos métodos: interrumpir al DOS sólo cuando no esté ejecutando
alguna función; esto es, cuando no está dentro de una INT 21h. Alternativamente, el programa residente
27 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
puede salvar todo el contexto del DOS, incluyendo las tres pilas internas, para restaurarlas después de haber
realizado su tarea. En este libro trataremos especialmente el primer método, tradicionalmente el más empleado
y el más probado.
Para detectar si el ordenador está ejecutando código del DOS (si está dentro de una INT 21h) se podría
desviar esta interrupción y colocar una nueva rutina que incrementara una variable indicativa al principio,
llamara a la INT 21h original y después volviera a decrementar la variable antes de retornar. Así, por ejemplo,
desde una interrupción de teclado o periódica, se podría comprobar si el DOS ya está trabajando antes de
llamarle (variable distinta de cero). Sin embargo, más que una variable habría que tener dos (una para indicar
que la pila E/S está en uso y otra para la pila de disco). Por otro lado, la rutina debería ser algo más
sofisticada todavía, ya que hay funciones del DOS que no retornan (las de terminar programa: la 0, 31h y
4Ch) y esto, si no se tiene cuidado, significaría no decrementar como es debido la variable que indica que se
ha abandonado la INT 21h. Además, para liar aún más el asunto, ¿qué hacer con los errores críticos?. Y,
para colmo, todavía hay más: si el DOS está dentro de la INT 21h, función 0Ah (entrada en buffer por
teclado), nuestra variable diría que no es posible usar el DOS en ese momento, ya que está ya en uso, cuando
está científicamente demostrado que en este caso sí es reentrante si se utiliza una función 0Dh o superior (en la
línea de comandos, el DOS está ejecutando precisamente esa función de entrada por teclado).
Por fortuna, el DOS viene aquí en nuestro socorro: no será preciso diseñar la compleja rutina propuesta,
ya que el propio sistema posee una variable interna que indica si en ese momento puede ser interrumpido. Se
trata de la variable no documentada InDOS. Existe una función secreta del DOS para obtener la dirección
de esta variable, de un byte, que valdrá 0 en el caso de que el DOS esté libre y pueda ser llamado desde un
programa residente. Esa variable se incrementa automática y adecuadamente con las llamadas a la INT 21h, y
se decrementa al salir.
No hay mejor manera de aprender a construir programas residentes fiables y eficientes que espiar cómo lo
hace el fabricante del sistema operativo con los suyos propios. El comando PRINT del DOS, cuando se
queda residente, desvía un montón de interrupciones, entre ellas la 1Ch (equivalente a la 8) y la 28h. La
interrupción 28h (Idle) es invocada por el DOS en las operaciones de entrada por teclado, cuando se
encuentra libre de otras tareas, para permitir a los programas residentes aprovechar ese tiempo muerto de
CPU. Desde dentro de una INT 28h se puede usar el DOS incluso aunque InDOS sea igual a 1. El comando
PRINT, cuando entra en acción, realiza además una serie de tareas adicionales: preserva el DTA activo (área
de transferencia a disco), el PSP del programa interrumpido, los vectores de INT 1Bh (Ctrl-Break), INT 23h
(Ctrl-C), INT 24h (manipulador de errores críticos); desvía esos vectores hacia unas rutinas propias; a
continuación establece un DTA y un PSP propios. Tras enviar los caracteres a la impresora, leyéndolos del
disco (con las funciones del DOS, por supuesto) vuelve a restaurar todo lo salvado. Pero vayamos más
despacio.
Para obtener la dirección de InDOS se puede emplear la función 34h del DOS, que devuelve un puntero
en ES:BX a dicha variable. La dirección de InDOS es constante, por lo que se puede inicializar al instalar el
programa residente (no cambiará de lugar en toda la sesión de trabajo). Como luego nos será de utilidad,
conviene decir aquí ahora que el Banderín de Errores Críticos del DOS está situado justo después de
InDOS en las versiones 2.x y justo antes en la 3.0 (en la 3.1 y siguientes, la función 5D06h permite obtener su
dirección en DS:SI). Por tanto, desde los programas residentes bastará, en principio, comprobar que InDOS
es igual a cero antes de llamar al DOS (y, de paso, que el Banderín de Errores Críticos es también cero). En
caso contrario, se puede inicializar una variable que indique que el programa residente tiene aún pendiente su
ejecución: desde la interrupción periódica se puede comprobar si está pendiente la activación del programa
residente y se puede verificar el estado del DOS hasta que éste esté listo para ser llamado, lo que sucederá
28 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
tarde o temprano. Además de la interrupción periódica, también se puede desviar la INT 28h: desde esta
interrupción se puede llamar al DOS, como dije antes, incluso aunque InDOS sea igual a 1 (pero no mayor)
siempre que la función del DOS a ejecutar sea superior a la 0Ch (lo más normal). Sin embargo, cuando sea
seguro llamar al DOS, habrá que hacer algunas cosas más antes de empezar a realizar la labor propia del
programa residente.
En el PSP se almacena mucha información vital para la ejecución de los programas. Una de las áreas más
importantes es el JFT (Job File Table) que contiene información referida a los ficheros del programa que se
ejecuta. No es conveniente, desde un programa residente, modificar el PSP del programa principal. Por tanto,
habrá que anotar la dirección del PSP actual y conmutar al del programa residente; al final del trabajo se
procederá a restaurar el PSP del programa principal. Si no se toma esta precaución, podría suceder de todo.
Por ejemplo: si el programa residente abre un fichero usando el PSP del programa principal, cuando éste
termine (el programa principal) ese fichero será probablemente cerrado sin que el programa residente se
entere. Para obtener la dirección del PSP activo se puede utilizar la función Get PSP (50h; ó la 62h,
totalmente equivalente) que devuelve en BX su segmento; la función Set PSP (51h) permite establecer un
nuevo PSP indicando en BX el segmento. Si se desea mantener la compatibilidad con el DOS 2.x, hay que
tener en cuenta además un error de este sistema operativo. La errata consiste en que las funciones 50h y 51h
no operan bien en el DOS 2.x a menos que el sistema use la pila de errores críticos. Por tanto, con esta
versión del sistema se puede forzar el Banderín de Errores Críticos a un valor 0FFh antes de llamar a las
funciones 50h y 51h, para volverlo a poner a cero después: así, el DOS cree que el sistema está en medio de
un error y usa la pila que queremos.
Además del PSP se debe cambiar el DTA (Disk Transfer Area) que utiliza el DOS para acceder al disco:
este área está normalmente en el offset 80h del PSP (sobrescribe el campo de parámetros de la línea de
comandos cuando el programa accede a disco) y ocupa 128 bytes. Basta con preservar el DTA del programa
principal, cuya dirección se obtiene en ES:BX con la función Get DTA (2Fh), y activar un nuevo DTA (por
ejemplo, en el offset 80h del PSP de programa residente) utilizando la función Set DTA (1Ah), pasando su
dirección en DS:DX.
La información extendida de errores es otro punto a tener en consideración. Supongamos que el programa
principal comete un error y el DOS genera la correspondiente información extendida de errores (a partir de la
versión 3.0). Si en ese momento se activa el programa residente, puede que realice alguna función del DOS
con éxito y el DOS sobrescribirá la condición de error previa. Por tanto, es deber del programa residente
preservar y restaurar la información extendida de errores antes de actuar. La función Get Extended Error
Information (59h) devuelve en AX, BX y CX la información extendida de errores. Con la función Set
Extended Error Information (5D0Ah), en DS:DX se suministra al DOS la dirección de una tabla que
contiene el AX, BX y CX con la información extendida de errores a establecer.
Como complemento, si se van a emplear las funciones de acceso a disco del DOS, también es conveniente
monitorizar la INT 13h para evitar un acceso a disco cuando no ha finalizado el anterior (aunque el DOS esté
en posición correcta). Si se van a emplear las INT 25h/26h, convendría monitorizarlas; así como la INT 10h
si se utilizan servicios de vídeo (aunque sean del DOS). Por monitorizar se entiende interceptar esa
interrupción e instalar una rutina de control que incremente y decremente una variable cada vez que empieza o
termina una de esas interrupciones, con objeto de saber cuándo se está dentro de ellas. En general, los
programas residentes que accedan demasiado intensivamente al disco (en una especie de multitarea) deberían
monitorizar no sólo INT 13h sino también INT 25h e INT 26h.
El procedimiento a seguir, por tanto, para activar un programa residente respondiendo por ejemplo a la
pulsación de una combinación de teclas, es el siguiente:
29 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
- Desde la interrupción del teclado, y una vez detectada la combinación de teclas, intentar activar el
programa residente. Será posible activarlo si: no estaba ya activo, no hay una INT 13h en curso, InDOS=0 y
el Banderín de Errores Críticos también es igual a 0.
- Por si falla, desde la interrupción del temporizador se puede comprobar si está pendiente aún la
activación del programa residente (por si no se pudo cuando se pulsaron las teclas); en ese caso, volverlo a
intentar de nuevo, con los mismos pasos que en el caso anterior.
- Desde la interrupción 28h comprobar si está pendiente aún la activación del programa residente: en ese
caso, si no estaba ya activo e InDOS<=1 y el Banderín de Errores Críticos es igual a 0 se puede proceder a
activar el programa residente.
- Como mínimo habrán de existir dos variables de control: Una que indica si el programa residente ya está
activo (y se deben rechazar o posponer nuevas activaciones, ya que éste se supone no reentrante). Otra, que
indique si el programa residente va a ser activado en breve (en cuanto el DOS nos deje). Ambas variables
son semáforos que conviene tratar con cuidado, para evitar reentradas en el programa residente: cuando
desde una interrupción son comprobadas (ej., desde una INT 28h) podría producirse otra interrupción (como
INT 8) lo que complica ligeramente la programación. Aunque no lo he dicho antes, todos los programas
residentes que usan el DOS deben definir una pila propia, ya que la del programa interrumpido puede no ser
suficientemente grande. Por el hecho de definir una pila propia, los programas residentes que usan funciones
del DOS no son reentrantes; lo cual no es, por lo general, una limitación muy importante.
- Por supuesto, antes de ejecutar su código propiamente dicho, el programa residente deberá preservar el
DTA, el PSP y la información extendida de errores, así como los vectores de INT 1Bh/23h/24h. Después
deberá desviar las INT 1Bh e INT 23h hacia un IRET (para evitar un Ctrl-Break ó Ctrl-C) y la INT 24h,
para implementar una gestión propia de los errores críticos. Al final, deberá restaurar todo de nuevo.
Toda la información vertida hasta ahora procede de la versión original del libro Undocumented DOS,
citado en la bibliografía. Sin embargo, en mi experiencia personal con los programas residentes he sacado la
conclusión de que es conveniente también desviar la INT 21h e intentar desde la misma activar el programa
residente, tal como si se tratara de una interrupción periódica más. El motivo es que desde la INT 8 ó la INT
1Ch hay que tener bastante suerte para que el DOS esté desocupado cuando se producen, ya que estas
interrupciones sólo suceden 18 veces cada segundo. Esto significa que, por ejemplo, mientras se formatea un
disco y se intenta activar el programa residente, puede que éste no responda hasta haberse formateado medio
disco o, incluso, hasta finalizar el formateo. Sin embargo, mientras se formatea el disco, se producen miles de
llamadas a la INT 21h: cuando InDOS sea cero tras acabar una sola de estas llamadas, podremos darnos
cuenta; sin embargo, utilizando sólo la interrupción periódica estaremos a merced de la suerte. Desviar la INT
21h e intentar activar el programa residente desde ella permite por ejemplo que éste actúe, en medio de un
formateo de disco, de manera casi instantánea cuando se le requiere. Otro ejemplo: con el método normal, sin
controlar la INT 21h, mientras se saca un directorio por pantalla y se intenta activar el programa residente,
cada cierto número de líneas éste responde; controlando la INT 21h, responde cada dos o tres caracteres
impresos. Es evidente que la INT 21h pone a nuestra disposición un método mucho más efectivo a menudo
que la interrupción periódica; sin embargo, tampoco es conveniente prescindir de esta última ya que la INT
21h sólo funciona cuando alguien llama al DOS (y no siempre alguien lo está llamando). En general, conviene
utilizar las dos interrupciones a la vez: si bien interceptar la INT 21h no está recomendado en ningún sitio
excepto en este libro, puedo asegurar que he tenido bastantes ocasiones de comprobar que es completamente
fiable.
Hasta ahora hemos visto el método más común para poder emplear el DOS desde un programa residente.
Sin embargo, este método depende de la molesta variable InDOS. Esto limita la efectividad de los programas
30 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
residentes, que no pueden ser activados por ejemplo cuando se ejecuta un comando TYPE. La solución
alternativa que se apuntaba al principio de este apartado consiste en salvar el contexto del DOS y restaurarlo
después, algo factible desde el DOS 3.0. Esto supone bastantes diferencias respecto al método estudiado
hasta ahora. En lugar de chequear InDOS se debe verificar que el DOS no está en una sección crítica (que
por fortuna es lo más normal) como luego veremos; y esto tanto desde la interrupción del teclado como desde
la periódica o desde la INT 28h. Al comienzo del código del programa residente, se debe salvar el estado del
DOS: esto significa que hay que pedir memoria al sistema (o tenerla reservada de antemano en cantidad
suficiente) para contener esa información. También hay que instalar las nuevas rutinas de control de INT 1Bh,
23h y 24h; no es necesario preservar el PSP activo (ya incluido en el área salvada): lo que sí es preciso es
activar el PSP propio. Tampoco es preciso preservar el DTA ni la información extendida de errores: aunque
se debe establecer un nuevo DTA, al restaurar el estado del DOS más tarde éste será también
automáticamente restablecido. Y bien, ¿en qué consiste el estado o contexto del DOS?: se basa en un área de
datos, el SDA (Swappable Data Area), cuyo tamaño oscila entre 24 bytes y 2 Kbytes. Este área almacena el
PSP activo y las tres pilas del DOS, así como la dirección del DTA...
Para manipular el SDA se puede emplear la función del sistema Get Address of DOS Swappable Data
Area (5D06h), que devuelve en DS:SI un puntero al SDA, en DX el número mínimo de bytes a preservar
cuando el DOS está libre y en CX el número de bytes a preservar cuando el DOS está ocupado (InDOS
distinto de cero). Desde la versión 4.0 del DOS se debe utilizar en su lugar la función Get DOS Swappable
Data Areas (5D0Bh), ya que este sistema no posee un único área de datos sino múltiples. El procedimiento
general consistirá, simplemente, en salvar el SDA al principio y restaurarlo al final.
Como se dijo antes, el SDA sólo puede ser accedido cuando el DOS no está en un momento crítico.
Cuando el DOS entra y sale de los momentos críticos, llama a la INT 2Ah con AX=8000h (inicio de
momento crítico) o bien AX=8100h o AX=8200h (fin de momento crítico). Se debe interceptar la INT 2Ah e
incrementar/decrementar una variable que indique las entradas/salidas del DOS en fase crítica.
Este método para gestionar los programas residentes requiere algo más de memoria: en especial, si se
quiere asegurar la compatibilidad con futuras versiones del sistema, habrá que reservar mucho más de 2Kb
para almacenar el SDA (intentar utilizar memoria convencional puede fallar, ya que el programa principal
puede tenerla toda asignada) aunque este problema es menor en máquinas con memoria expandida o
extendida. No hay que olvidar que el SDA no se puede grabar en disco (para eso hay que usar el DOS, y el
DOS no se puede emplear hasta no haber salvado el SDA). También es quizá algo más complejo. Sin
embargo, añade algo más de potencia a los programas residentes, ya que pueden ser activados casi en
cualquier momento y prácticamente en cualquier circunstancia. El autor de este libro nunca ha empleado este
método.
Hay programadores que utilizan métodos muy curiosos para emplear los servicios del DOS desde los
programas residentes. Un ejemplo, expuesto por Douglas Boling en su artículo de la revista RMP (Ed. Anaya,
Marzo-Abril de 1992) consiste en activar el Banderín de Errores Críticos antes de llamar a las funciones
ordinarias del DOS: de esta manera, se utiliza la pila de errores críticos en lugar de la de disco, con lo que no
hay conflictos. Esto, por supuesto, sin que el DOS estuviera antes en estado crítico (en caso de estarlo hay
que esperar). El inconveniente de este método es que sólo un programa residente de este tipo puede estar
activo en un momento dado en el ordenador. Evidentemente, también hay que desviar la INT 24h para
controlar un posible error crítico de verdad.
31 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
texto. El método que emplea es el clásico de comprobar la variable InDOS. Al pulsar Alt-SysReq
(combinación por defecto) comienza a actuar. Emite un sonido ascendente que precede la grabación y otro
descendente que la sucede, para confirmar que ha grabado. Los ficheros que genera tienen por nombre
SCRxx-nn.SCR, donde xx es la anchura de la pantalla en columnas (en hexadecimal) y nn el número de
fichero, entre 00 y 99. Los ficheros se crean a partir de 00 cuando se instala el programa, sobrescribiendo
otros existentes con anterioridad. Al almacenar en el nombre del fichero la anchura del modo de vídeo, es fácil
después procesar la imagen al conocer sus dimensiones. El programa no comprueba el modo de vídeo, por lo
que en pantallas gráficas se obtienen resultados desconcertantes. Sin embargo, la ventaja de ello es que de
esta manera puede salvar pantallas extrañas no estándar (como 132x60, etc.) que pueden poseer ciertas
tarjetas. El fichero es creado en el directorio activo por defecto; si se invoca la utilidad mientras se ejecuta un
DIR, el fichero podría crearse en el directorio visualizado (algunas versiones del COMMAND cambian el
directorio activo momentáneamente). Como cabía esperar, el programa se autoinstala automáticamente en
memoria superior y tiene opción de desinstalación, siendo también configurables las teclas de activación.
Entre los aspectos técnicos, decir que se desvía la INT 21h como se comentó con anterioridad. En ese
sentido, SCRCAP puede ser invocado con éxito mientras se formatea un disquete (bueno, pero tampoco
para grabar precisamente sobre ese disquete). Se define una pila interna de 0,75 Kbytes, suficiente para el
programa que graba la pantalla y para dar cabida a todas las interrupciones hardware que puedan anidarse
durante el proceso (examinando la memoria con DEBUG se puede observar qué cantidad máxima de pila es
consumida tras un rato de trabajo, ya que los caracteres 'PILA' permanecen en la zona de la misma aún no
empleada). Desde la rutina de control de INT 8 e INT 9 se llama a una subrutina, proceso_tsr, que toma la
decisión de activar el programa residente si el DOS está preparado, o lo pospone en caso contrario. Desde la
INT 28h se hace la comprobación más relajada de InDOS (basta con que sea no mayor de 1) y se toma
también la decisión de activar el programa residente o seguir esperando: en el primer caso se llama a
proceso_tsr con una variable (in28) que indica que ya no hay que hacer más comprobaciones. En
proceso_tsr se comprueba la variable activo para evitar una reentrada al programa residente: como es un
semáforo, es preciso inhibir las interrupciones con objeto de que entre su consulta y ulterior hipotética
modificación no pueda ser modificado por nadie (por otro proceso lanzado por interrupciones). Al final, la
rutina tarea_TSR es el auténtico programa residente. Simplemente modificando esta rutina se pueden crear
programas residentes que realicen cualquier función, pudiendo llamar para ella al DOS.
SCRCAP termina residente dejando en memoria todo el PSP, a diferencia de programas anteriores. Los
últimos 128 bytes del PSP se dejan residentes porque serán empleados como área de transferencia a disco
(DTA). Conviene ahora hacer un pequeño apunte importante: cuando el programa es relocalizado a la
memoria superior, hay que actualizar un campo en el PSP relocalizado (rutina reubicar_prog): se trata del
campo que apunta a la JFT (offset 36h del PSP), con objeto de que apunte correctamente al nuevo segmento
en que reside el PSP. Si no se tomara esta precaución, no se accedería al disco correctamente.
Si se compara el listado de SCRCAP con el de RCLOCK, el lector comprobará que tienen común cerca
del 50% de las líneas. Sólo cambia la ayuda, algún parámetro, alguna subrutina de la instalación y, por
supuesto, el código residente. En general, las subrutinas que componen ambos programas son lo
suficientemente generales como para acomodar múltiples soluciones informáticas: se puede considerar que
ambos programas son una especie de plantillas para crear utilidades residentes. Para hacer nuevos
programas residentes que hagan otras tareas, basta con cambiar sólo la parte residente y poco más. Esto
permite trabajar con comodidad, pese a tratarse del lenguaje ensamblador, y producir múltiples programas en
tiempo récord.
Para visualizar las pantallas capturadas puede utilizarse la utilidad SCRVER.C, que admite comodines para
poder ver cualquier conjunto de ficheros. Con SCR2TXT.C se convierten las pantallas capturadas (de
40/80/94/100/120/132 ó 160 columnas) a modo texto: se suprimen los colores, se eliminan la mayoría de los
32 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
códigos de control, se quitan los espacios en blanco al final de las líneas y se añaden retornos de carro para
separarlas. Esto último provoca, en pantallas que ocupan justo las 80 columnas, que al emplear el TYPE del
DOS las líneas queden separadas por una línea extra en blanco (si tuvieran 79 columnas o si se carga desde
un editor de texto, no habrá problemas).
La mayoría de los programas residentes prefieren operar con pantallas de texto: ocupan menos memoria,
son totalmente estándar y más rápidas. En la práctica, la dificultad asociada al proceso de preservar el
contenido de una pantalla gráfica y después restaurarla lleva a muchos programas residentes a no dejarse
activar cuando la pantalla está en modo gráfico. Sin embargo, existe una técnica sencilla que permite
simplificar este proceso, siendo operativa en todos los modos de la EGA y VGA estándar, aunque presenta
alguna dificultad en ciertos modos de la VGA.
En los modos estándar de IBM (y en general también en los no estándar) cuando se solicita a la BIOS que
establezca el modo de vídeo (véanse las funciones de la BIOS en los apéndices) si el bit más significativo del
modo se pone a 1, al cambiar de modo no se limpia la pantalla. Esta característica está disponible sólo en
máquinas con tarjeta EGA o VGA (tanto XT como AT). Se trata de una posibilidad muy interesante, que
permite a los programas residentes activar momentáneamente una pantalla de texto, preservar el fragmento de
la misma que van a emplear y, al final, restaurarlo y volver al modo gráfico como si no hubiera sucedido nada,
sin necesidad de preservar ni restaurar zonas gráficas. También habrán de preservar la posición inicial del
cursor y la página de vídeo activa inicialmente (que habrán de restaurar junto con el modo de vídeo), así como
las paletas de la EGA y VGA, tareas éstas que puede simplificar la BIOS.
Por ejemplo: si la pantalla estaba en modo 12h (VGA 640x480 con 16 colores) se puede activar el modo
83h (el 3 con el bit 7 activo) de texto de 80x25 y, cuando halla que restaurarla, activar el modo 92h (el 12h
con el bit 7 activo). Evidentemente, después habrá que engañar de alguna manera a la BIOS para que crea
que la pantalla está en modo 12h y no 92h (sutil diferencia, ¿no?) y ello se consigue borrando el bit más
significativo de la posición 40h:87h (la variable de la BIOS 40h:49h indica siempre el número de modo de
pantalla con el bit más significativo borrado: este bit se almacena separadamente en 40h:87h). Esta operación
es segura, ya que la diferencia entre el modo 12h y el 92h es sólo a nivel de software y no de hardware. Un
programa residente elegante, además, se tomará la molestia de dejar activo el bit de 40h:87h si así lo estaba al
principio, antes de restaurar el modo gráfico (poco probable, pero posible -sobre todo cuando el usuario
activa más de un programa residente de manera simultánea-).
Esta técnica presenta, sin embargo, una ligera complicación al trabajar en el modo 13h de la VGA
(320x200 con 256 colores) o en la mayoría de los modos SuperVGA. El problema consiste en que, al pasar
a modo texto, la BIOS define el juego de caracteres -que en la EGA/VGA es totalmente programable-
utilizando una cierta porción de la memoria de vídeo de la tarjeta. Por desgracia, esa porción de la memoria
de la tarjeta gráfica es parte de la pantalla en el modo 13h y en los modos SuperVGA. La solución no es muy
complicada, aunque sí un poco engorrosa. Ante todo, recordar que esto sólo es necesario en modos de
pantalla avanzados o en el 13h. Una posible solución consiste en preservar la zona que va a ser manchada (8
Kb) en un buffer, pasar a modo texto y, antes de volver al modo gráfico, redefinir el juego de caracteres de
texto de tal manera que al volver a modo gráfico ya esté restaurada la zona manchada. Este orden de
operaciones no es caprichoso y lo he elegido para reducir los accesos al hardware, como se verá. El
33 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
problema principal radica en el hecho de que la arquitectura de la pantalla en los modos gráficos y de texto
varía de manera espectacular. Por ello, no hay un algoritmo sencillo para acceder a la zona de memoria de
gráficos que hay que preservar. Para no desarrollar complicadas rutinas -por si fuera poco, una para cada
modo gráfico- es más cómodo programar el controlador de gráficos para configurar de manera cómoda la
memoria de vídeo y preservar sin problemas los 8 Kb deseados. Después, no hace falta restaurar el estado
de ningún controlador de vídeo, ya que la BIOS lo reprogramará correctamente al pasar a modo texto. Por
último, y estando aún en modo texto, se redefinirá el juego de caracteres con los 8 Kb preservados. Como
inmediatamente después se vuelve al modo gráfico, el usuario no notará la basura que aparezca en la pantalla
durante breves instantes y, de nuevo, la BIOS reprogramará adecuadamente el controlador de gráficos. El
siguiente ejemplo práctico parte de la suposición de que nos encontramos en el modo 13h:
Las rutinas preservar8k y restaurar8k son tan obvias que, evidentemente, no las comentaré. Sin
embargo, la rutina que prepara el sistema de vídeo de tal manera que se pueda redefinir el juego de caracteres
de texto, requiere conocimientos acerca de la arquitectura de las tarjetas gráficas EGA y VGA a bajo nivel.
Esta información puede obtenerse en libros especializados sobre gráficos (consúltese la bibliografía) aunque a
continuación expongo el listado de def_car_on; eso sí, sin entrar en detalles técnicos acerca de su
funcionamiento:
def_car_on PROC
MOV DX,3C4h ; puerto del secuenciador
LEA SI,car_on ; códigos a enviarle
MOV CX,4
CLD
CLI ; precauciones
def_on_1: LODSW
OUT DX,AX ; programar registro
LOOP def_on_1
STI ; no más precauciones
MOV DL,0CEh ; 3CEh = puerto del controlador de gráficos
MOV CX,3
def_on_2: LODSW
OUT DX,AX ; programarlo
LOOP def_on_2
RET
car_on DW 100h, 402h, 704h, 300h, 204h, 5, 6 ; datos
def_car_on ENDP
En la aplicación práctica de las rutinas expuestas se han detectado algunos problemas de compatibilidad
con algunas tarjetas. El más grave se produjo con una OAK SuperVGA: en algunos modos de 800 y 1024
puntos, se colgaba el ordenador al ejecutar def_car_on. La solución adoptada consistió en dar un paso
intermedio: antes de llamar a def_car_on se puede poner la pantalla en un modo no conflictivo y que sea
gráfico para evitar que la BIOS defina el juego de caracteres (como el 13h+80h=93h); en este modo sí se
puede ejecutar def_car_on, antes de pasar al modo texto.
34 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
El método propuesto es ciertamente sencillo, aunque se complique un poco más en algunos modos de la
VGA. Tiene requerimientos (como el buffer de 8 Kb) que no están quizá al alcance de los programas
residentes menos avanzados. Los más avanzados pueden grabar los 8 Kb en disco duro, si la máquina está
dotada del mismo, así como toda la memoria de pantalla CGA (unos modestos 16 Kb) en las máquinas que
no están dotadas de EGA o VGA y no pueden conmutar el modo de pantalla sin borrar la misma. Las
máquinas que no tengan disco duro aumentarán el consumo de memoria del programa residente en 8/16 Kb,
aunque ¡peor sería tener que preservar hasta 1 Mb de memoria de vídeo!. El problema está en las tarjetas no
compatibles VGA: mucho cuidado al utilizar la rutina def_car_on (hay que detectar antes la presencia de una
auténtica EGA/VGA, ¡no vale la MCGA!). En MCGA no se puede aplicar def_car_on en el modo 13h,
aunque afortunadamente esta tarjeta está poco extendida (sólo acompaña al PS/2-30, en sus primeros
modelos un compatible XT); los más perfeccionistas siempre pueden consultar bibliografía especializada en
gráficos para tratar de manera especial este adaptador de vídeo, aunque sería incluso más recomendable
ocuparse antes de la Hércules. Otro premio reservado para estos perfeccionistas será la posibilidad de
conmutar los modos de pantalla accediendo al hardware y sin apoyo de la BIOS, para que no borre la
pantalla en las CGA. Téngase en cuenta que esta operación sería mucho más delicada en las EGA y VGA (es
más difícil restaurar todos los parámetros hardware del modo gráfico activo inicialmente) en las que además
habría que definir un juego de caracteres de texto. Por cierto, el estándar VESA posee también funciones
para preservar y restaurar el estado del adaptador de vídeo; el lector podría encontrar interesante
documentarse acerca de ello.
El tema de los programas residentes de DOS funcionando bajo Windows no es demasiado importante ya
que, en teoría, desde dentro de Windows no es necesario tener instalados programas residentes, al tratarse de
un entorno multitarea que permite tener varios programas activos en pantalla a la vez. Sin embargo, puede ser
interesante en ocasiones crear programas residentes que también operen bajo Windows, de cara a no tener
que desarrollar una versión específica no residente para este entorno.
Un problema importante de los programas residentes consiste en la dificultad para leer el teclado. La razón
es que Windows reemplaza totalmente al controlador del DOS, anulando los TSR que se activan por teclado.
En los AT se puede leer el puerto del teclado en cualquier momento (fuera de la INT 9) aunque no es
recomendable porque la práctica reiterada de este método provoca anomalías en el mismo (tales como
aparición de números en los cursores, estado de Shift que se engancha, etc.) debido a las limitaciones del
hardware. Un método más recomendable, aunque menos potente, consiste en comprobar las variables de la
BIOS que indican el estado de mayúsculas, bloque numérico, shift, ... ya que estas variables son
correctamente actualizadas desde dentro de Windows. El único problema es la limitación de combinaciones
posibles que se pueden realizar con estas teclas, de cara a permitir la convivencia de varios programas
residentes (problema que se puede solventar permitiendo al usuario elegir las teclas de activación).
El otro problema está relacionado con la multitarea de Windows. Si se abren varios procesos DOS desde
este entorno y se activa el programa residente en más de uno de ellos, pueden aparecer problemas de
reentrada (la segunda ejecución estropeará los datos de la primera). La solución más sencilla consiste en no
permitir la invocación del programa residente desde más de una tarea; sin embargo, en algunos TSR (tales
como utilidades de macros de teclado, etc.) esto supone una grave e intolerable restricción. Otra solución
sencilla consiste en obligar al usuario a instalar el TSR en cada sesión de DOS abierta, con lo que todo el
entorno de operación será local a dicha sesión. Para los casos en que no sea recomendable esto último, se
puede quemar el último y más efectivo cartucho: comunicar el TSR con el conmutador de tareas de Windows
para emplear memoria instantánea. El único inconveniente es que Windows sólo facilita memoria instantánea
en el modo extendido 386, no en el modo estándar ni -en el caso de la versión 3.0- en el real. Sin embargo,
35 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
con la versión 3.1 de Windows, en el modo estándar se puede emplear el conmutador de tareas del DOS
5.0, que es el que utiliza dicho modo. No deja de ser una pena tener que utilizar un método diferente para el
modo estándar que para el extendido, aunque la recompensa para quien implemente soporte en sus TSR para
los dos métodos es que les hará compatibles también con el conmutador de tareas del MS-DOS 5.0. Se
puede interceptar el arranque de Windows y comprobar si lo hace en modo real, en cuyo caso se puede
abortar su ejecución y emitir un mensaje de error para solicitar al usuario que no desinstale el TSR antes de
entrar en ese modo de Windows.
Cuando Windows arranca, llama a la INT 2Fh con AX=1605h: un TSR puede interceptar esta llamada
(como en cualquier otra interrupción, llamando primero al controlador previo) y comprobar si el bit 0 de DX
está a cero (en ese caso se estará ejecutando en modo extendido): si se desea abortar la ejecución de
Windows bastará cargar un valor distinto de 0 en CX antes de retornar.
Si el TSR necesita áreas de datos locales a cada sesión en el modo extendido, puede indicárselo a
Windows con un puntero a un área de datos denominado SWSTARTUPINFO en ES:BX. Para ello, y
teniendo en cuenta que puede haber varios TSR que intercepten las llamadas a la INT 2Fh con AX=1605h,
este área ha sido diseñada para almacenar una cadena de referencias entre todos ellos; por ello es preciso
almacenar primero el ES:BX inicial de la rutina en dicha estructura y cargar ES:BX apuntándola antes de
retornar. El formato de SWSTARTUPINFO es el siguiente:
DW 3 ; versión de la estructura
DD ? ; puntero a la próxima estructura SWSTARTUPINFO (ES:BX inicial)
DD 0 ; puntero al nombre ASCIIZ del dispositivo virtual (ó 0)
DD 0 ; datos de referencia del dispositivo virtual (si tiene nombre)
DD ? ; puntero a la tabla de registros de datos locales (ó 0)
El formato de la tabla de registros de datos locales, que define las estructuras de datos que serán locales a
cada sesión, es el siguiente:
En los momentos críticos en que el TSR deba evitar una conmutación de tareas, puede emplear las
funciones BeginCriticalSection (llamar a INT 2Fh con AX=1681h) y EndCriticalSection (llamar a INT 2Fh
con AX=1682h); el TSR debe estar poco tiempo en fase crítica para no ralentizar Windows.
Para detectar la presencia del conmutador de tareas del MS-DOS 5.0 se debe llamar a la INT 2Fh con
AX=4B02h: si a la vuelta AX es 0, significa que está cargado y ES:DI apunta a la rutina de servicio del
mismo, que pone varias funciones a disposición de los TSR: los TSR deberán ejecutar la función AX=4
(Conectar a la cadena de Notificación) al instalarse en memoria y la función AX=5 (Desconectar de la
Cadena de Notificación) al ser desinstalados, para informar al conmutador. Una vez enganchado, el TSR
será llamado por el conmutador de tareas para ser informado de todo lo interesante que suceda (de cosas
tales como la creación y destrucción de sesiones, suspensión del conmutador, etc.) por medio de la ejecución
de la rutina de notificación del mismo, pudiendo el TSR permitir o no, por ejemplo, la suspensión de la
sesión... el aviso de inicio de sesión es fundamental para los TSR que tienen áreas de datos temporales que
inicializar al comienzo de cada sesión. El procedimiento general lo inicia el conmutador de tareas llamando a la
INT 2Fh con AX=4B01h: los TSR serán invocados unos tras otros (pasándose mutuamente el control). Para
gestionar esto existe una estructura de datos denominada SWCALLBACKINFO (apuntada por ES:BX al
llamar a INT 2Fh con AX=4B01h):
36 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
DW 10 ; longitud de la estructura
DW ? ; identificador del API (1-NETBIOS, 2-802.2, 3-TCP/IP, 4-Tuberías Lan
5-NetWare IPX)
DW ? ; número de la mayor versión del API soportada
DW ? ; número de la menor versión del API soportada
DW ? ; nivel de soporte: 1-mínimo (el TSR impide la conmutación de la tare
incluso tras finalizar sus funciones), 2-soporte a nivel API (el TS
impide la conmutación de tareas si las peticiones son importantes),
3-Compatibilidad de conmutación (se permite conmutar de tarea inclu
con peticiones importantes, aunque algunas podrían fallar), 4-Sin
compatibilidad (se permite siempre la conmutación).
Cuando el conmutador de tareas arranca, ejecuta una INT 2Fh con AX=4D05h para tomar nota de los
bloques de datos locales a cada sesión, llamada que los TSR deberán detectar del mismo modo que cuando
comprobaban la ejecución de Windows en modo extendido: la estructura de datos es además, por fortuna, la
misma en ambos casos.
Las funciones que debe soportar la rutina de notificación, apuntada por la estructura
SWCALLBACKINFO, son las siguientes:
37 de 38 12/10/00 19:13
PROGRAMAS RESIDENTES file:///C|/librosVirtuales/UniversoDigital/10.html
BX = banderines
bit 0: activo si el conmutador que llama es el único cargado
bits 1-15: reservados (0)
Devuelve: AX = 0000h
Volver al Índice
38 de 38 12/10/00 19:13
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
11.1. - INTRODUCCIÓN.
Los controladores de dispositivo (device drivers en inglés) son programas añadidos al núcleo del sistema
operativo, concebidos inicialmente para gestionar periféricos y dispositivos especiales. Los controladores de
dispositivo pueden ser de dos tipos: orientados a caracteres (tales como los dispositivos NUL, AUX, PRN,
etc. del sistema) o bien orientados a bloques, constituyendo las conocidas unidades de disco. La diferencia
fundamental entre ambos tipos de controladores es que los primeros reciben o envían la información carácter
a carácter; en cambio, los controladores de dispositivo de bloques procesan, como su propio nombre indica,
bloques de cierta longitud en bytes (sectores). Los controladores de dispositivo, aparecidos con el DOS 2.0,
permiten añadir nuevos componentes al ordenador sin necesidad de rediseñar el sistema operativo.
Los controladores de dispositivo han sido tradicionalmente programas binarios puros, similares a los
COM aunque ensamblados con un ORG 0, a los que se les colocaba una extensión SYS. Sin embargo, no
hay razón para que ello sea así ya que un controlador de dispositivo puede estar incluido dentro de un
programa EXE, con la condición de que el código del controlador sea el primer segmento de dicho programa.
El EMM386.EXE del MS-DOS 5.0 sorprendió a más de uno en su día, ya que llamaba la atención observar
cómo se podía cargar con DEVICE: lo cierto es que esto es factible incluso desde el DOS 2.0 (pese a lo que
pueda indicar algún libro), pero ha sido mantenido casi en secreto. Actualmente es relativamente frecuente
encontrar programas de este tipo. La ventaja de un controlador de dispositivo de tipo EXE es que puede ser
ejecutado desde el DOS para modificar sus condiciones de operación, sin complicar su uso por parte del
usuario con otro programa adicional. Además, un controlador de dispositivo EXE puede superar el límite de
los 64 Kb, ya que el DOS se encarga de relocalizar las referencias absolutas a segmentos como en cualquier
programa EXE ordinario. Por cierto, el RAMDRIVE.SYS de WINDOWS 3.1 (no el de MS-DOS 5.0) y el
VDISK.SYS de DR-DOS 6.0 son realmente programas EXE, aunque renombrados a SYS (aviso: no
recomiendo a nadie ponerles extensión EXE y ejecutarlos después).
Todo controlador de dispositivo de bloques comienza con una cabecera estándar, mostrada a
continuación:
+--------------------------------------------------------------------------------------+
| CABECERA DEL CONTROLADOR DE DISPOSITIVO DE BLOQUES |
+--------------------------------------------------------------------------------------+
| offset 0 DD 0FFFFFFFFh ; doble palabra de valor -1 |
| offset 4 DW 0 ; palabra de atributos (ejemplo arbitrario) |
| offset 6 DW estrategia ; desplazamiento de la rutina de estrategia |
| offset 8 DW interrupcion ; desplazamiento de la rutina de interrupción |
| offset 10 DB 1 ; número de discos definidos: 1 por ejemplo |
| offset 11 DB 7 DUP (0) ; 7 bytes no usados |
+--------------------------------------------------------------------------------------+
Al principio, una doble palabra con el valor 0FFFFFFFFh (-1 en complemento a 2) será modificada
posteriormente por el DOS para enlazar el controlador de dispositivo con los demás que haya en el sistema,
formando una cadena. No fue una ocurrencia muy feliz elegir precisamente ese valor inicial como obligatorio
para la copia en disco, dado que la instrucción de código de operación 0FFFFh es ilegal y bloquea la CPU si
es ejecutada. Esto significa que un controlador de dispositivo binario puro no puede ser renombrado a COM
y ejecutado también desde el DOS (habrá de ser necesariamente de tipo EXE). A continuación, tras esta
doble palabra viene una palabra de atributos, cuyo bit más significativo está borrado en los dispositivos de
bloques para diferenciarlos de los dispositivos de caracteres. Tras ello, aparecen los offsets a las rutinas de
1 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
estrategia e interrupción, únicas de las que consta el controlador. Por último, un byte indica cuántas nuevas
unidades de disco se definen y detrás hay 7 bytes reservados -más bien no utilizados-.
+--------------------------------------------------------------------------------------+
| PALABRA DE ATRIBUTOS DEL CONTROLADOR DE DISPOSITIVO DE BLOQUES |
+--------------------------------------------------------------------------------------+
| bit 15: borrado para indicar dispositivo de bloques |
| bit 14: activo si se soporta IOCTL |
| bit 13: activo para indicar disco de formato no-IBM |
| bit 12: reservado |
| bit 11: en DOS 3+ activo si soportadas órdenes OPEN/CLOSE y REMOVE |
| bit 10: reservados |
| bit 9: no documentado. Al parecer, el DRIVER.SYS del DOS 3.3 lo emplea para |
| indicar que no está permitida una E/S directa en las unidades «nuevas» |
| bit 8: no documentado. El DRIVER.SYS del DOS 3.3 lo pone activo para las |
| unidades «nuevas» |
| bit 7: en DOS 5+ activo si soportada orden 19h (CHECK GENERIC IOCTL SUPPORT) |
| bit 6: en DOS 3.2+ activo si soportada orden 13h (GENERIC IOCTL) |
| bits 5-2: reservados |
| bit 1: activo si el driver soporta direccionamientos de sector de 32 bits |
| (unidades de más de 65536 sectores y, por ende, más de 32 Mb). |
| bit 0: reservado |
+--------------------------------------------------------------------------------------+
+--------------------------------------------------------------------------------------+
| CABECERA DEL CONTROLADOR DE DISPOSITIVO DE CARACTERES |
+--------------------------------------------------------------------------------------+
| offset 0 DD 0FFFFFFFFh ; doble palabra de valor -1 |
| offset 4 DW 8000h ; palabra de atributos (ejemplo arbitrario) |
| offset 6 DW estrategia ; desplazamiento de la rutina de estrategia |
| offset 8 DW interrupcion ; desplazamiento de la rutina de interrupción |
| offset 10 DB "AUX " ; nombre del dispositivo (8 caracteres) |
+--------------------------------------------------------------------------------------+
Aunque en el ejemplo aparece AUX, ello es un ejemplo de lo que no se debe hacer, a no ser que sea lo
que realmente se desea hacer (se está creando un dispositivo AUX que ya existe, con lo que se sobrescribe y
anula el puerto serie original). En general, además de los nombres de los dispositivos del sistema, no deberían
utilizarse los que crean ciertos programas (como el EMMXXXX0 del controlador EMS, etc.). Conviene decir
aquí que muchos de los controladores de dispositivo de caracteres instalados en el ordenador no lo son tal
realmente, sino que se trata de simples programas residentes que se limitan a dar error a quien intenta acceder
a ellos (pruebe el lector a ejecutar la orden COPY *.* EMMXXXX0: con el controlador de memoria
expandida instalado) aunque algunos implementan ciertas funciones vía IOCTL.
+--------------------------------------------------------------------------------------+
| PALABRA DE ATRIBUTOS DEL CONTROLADOR DE DISPOSITIVO DE CARACTERES |
+--------------------------------------------------------------------------------------+
| bit 15: activo para indicar dispositivo de caracteres |
| bit 14: activo si se soporta IOCTL |
| bit 13: en DOS 3+ activo si se soporta orden 10h (OUTPUT UNTIL BUSY) |
| bit 12: reservado |
| bit 11: en DOS 3+ activo si soportadas órdenes OPEN/CLOSE y REMOVE) |
2 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
Cuando el DOS va a acceder a un dispositivo (debido a una petición de un programa de usuario) ejecuta,
de manera secuencial, las rutinas de estrategia e interrupción, que son de tipo FAR. Hay que recordar que el
paso del MS-DOS 1.0 al 2.0 supuso una emigración de la filosofía del CP/M a la del UNIX. La razón de la
existencia separada de las rutinas de estrategia e interrupción se inspira en la filosofía de diseño del UNIX y
su arquitectura multitarea, aunque para el DOS hubiera sido suficiente una sola rutina. De hecho, la rutina de
estrategia tiene como única misión recoger la dirección de la cabecera de petición de solicitud que el
DOS envía al driver, en ES:BX. Las 3 líneas de código siguientes constituyen una rutina de estrategia, ya que
son prácticamente idénticas en todos los controladores de dispositivo:
+--------------------------------------------------------------------------------------+
| RUTINA DE ESTRATEGIA |
+--------------------------------------------------------------------------------------+
| estrategia PROC FAR ; de tipo FAR |
| MOV CS:pcab_pet_desp,BX |
| MOV CS:pcab_pet_segm,ES |
| RET |
| estrategia ENDP |
| |
| pcab_peticion LABEL DWORD |
| pcab_pet_desp DW 0 |
| pcab_pet_segm DW 0 |
+--------------------------------------------------------------------------------------+
¿Para qué sirve la cabecera de petición de solicitud?: sencillamente, es un área de datos que el DOS utiliza
para comunicarse con el controlador de dispositivo. Por medio de este área se envían las órdenes y los
parámetros que el dispositivo soporta, y se recogen ciertos resultados. La rutina de interrupción del
dispositivo, además de preservar todos los registros que va a alterar para restaurarlos al final, se encarga de
consultar la dirección de la cabecera de petición de solicitud que almacenó la rutina de estrategia y comprobar
qué le está pidiendo el DOS. No es realmente una rutina de interrupción ya que retorna con RETF, en vez de
con IRET, por lo que nunca podrá ser invocada por una interrupción hardware. Aunque según la orden a
procesar el tamaño de la cabecera de petición de solicitud puede variar, los primeros 13 bytes son:
+---------------------------------------------------------------------------------------+
| CABECERA DE PETICIÓN DE SOLICITUD (13 PRIMEROS BYTES) COMÚN A TODAS LAS ÓRDENES |
+---------------------------------------------------------------------------------------+
| offset 0 DB longitud_bloque ; longitud total de la cabecera |
3 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
En general, la rutina de interrupción suele multiplicar por dos el número de la orden (almacenada en el
offset 2 de la cabecera de petición), para así acceder indexadamente a una tabla de palabras que contiene los
desplazamientos a las rutinas que procesan las diversas órdenes: aunque esto no ha de ser necesariamente así,
casi todos los controladores de dispositivo se comportan de esta manera.
+----------------------------------------------------------------------+
| 00h INIT |
| 01h MEDIA CHECK (dispositivos de bloque) |
| 02h BUILD BPB (dispositivos de bloque) |
| 03h IOCTL INPUT |
| 04h INPUT |
| 05h NONDESTRUCTIVE INPUT, NO WAIT (dispositivos de caracteres) |
| 06h INPUT STATUS (dispositivos de caracteres) |
| 07h INPUT FLUSH (dispositivos de caracteres) |
| 08h OUTPUT |
| 09h OUTPUT WITH VERIFY |
| 0Ah OUTPUT STATUS (dispositivos de caracteres) |
| 0Bh OUTPUT FLUSH (dispositivos de caracteres) |
| 0Ch IOCTL OUTPUT |
| 0Dh (DOS 3+) DEVICE OPEN |
| 0Eh (DOS 3+) DEVICE CLOSE |
| 0Fh (DOS 3+) REMOVABLE MEDIA (dispositivos de bloques) |
| 10h (DOS 3+) OUTPUT UNTIL BUSY (dispositivos de caracteres) |
| 11h-12h no usada |
| 13h (DOS 3.2+) GENERIC IOCTL |
| 14h-16h no usadas |
| 17h (DOS 3.2+) GET LOGICAL DEVICE |
| 18h (DOS 3.2+) SET LOGICAL DEVICE |
| 19h (DOS 5.0+) CHECK GENERIC IOCTL SUPPORT |
+----------------------------------------------------------------------+
La tabla anterior resume las órdenes que puede soportar un controlador de dispositivo; en general no será
preciso implementar todas: de hecho, incluso para un disco virtual basta con algunas de las primeras 16.
Todas las órdenes devuelven una palabra de estado al sistema operativo, cuyo formato puede consultarse a
continuación. En general, las ordenes no soportadas pueden originar un error o bien ser sencillamente
ignoradas (en ese sentido, crear un dispositivo NUL es tarea realmente sencilla).
+---------------------------------------------------------------------------------------+
| FORMATO DE LA PALABRA DE ESTADO |
+---------------------------------------------------------------------------------------+
| bit 15: Activo si hay error, en ese caso los bits 0-7 indican el tipo de error |
| bits 14-10: Reservados |
| bit 9: Activo si el controlador de dispositivo no está listo. En las operaciones |
| de entrada está listo si hay un carácter en el buffer de entrada o si tal |
| buffer no existe; en las de salida cuando el buffer aún no está lleno. |
| bit 8: Activo si el controlador de dispositivo ha acabado de ejecutar la orden. |
| Hasta el DOS 5.0 al menos, esto es siempre así (en un hipotético sistema |
| multitarea, una orden podría ejecutarse en varias ráfagas de CPU). |
| bits 7-0: Código de error, si el bit 15 está activo: |
4 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
La construcción de rutinas de gestión para las diversas órdenes que han de soportarse no es un proceso
muy complicado, pese a que está envuelto en una leyenda negra. Sin embargo, puede que parte de la
explicación que viene a continuación sobre dichas órdenes sea difícil de entender al lector poco iniciado. No
hay que olvidar que los controladores de dispositivo respetan unas normas de comportamiento definidas por
el fabricante del DOS, y más que de intentar comprender por qué una cosa es de una manera determinada, de
lo que se trata es de obedecer. En general, lo que no se entienda puede ser pasado por alto ya que
probablemente no es estrictamente necesario conocerlo. Además, casi ningún controlador necesita soportar
todas las órdenes, como se verá al final en los programas de ejemplo.
+--------------------------------------------------------------------------------------+
| CABECERA DE PETICIÓN DE SOLICITUD PARA LA ORDEN 0 (INIT) |
+--------------------------------------------------------------------------------------+
| offset 0 13 BYTES: Ya vistos con anterioridad. |
| offset 0Dh BYTE: A la vuelta, indicar al DOS el nº de unidades de disco |
| definidas (solo en dispositivos de bloque). |
| offset 0Eh: DWORD: A la vuelta, indica el último byte residente con un |
| puntero largo de 32 bits. Si el dispositivo no se instala |
| ante algún fallo, para no quedar residente basta indicar |
| un offset 0 (el segmento es vital inicializarlo con CS). |
| offset 12h: DWORD: A la entrada, el DOS indica dónde comienza la línea de |
| parámetros del CONFIG.SYS. A la salida se indica al DOS |
| la dirección de la tabla de apuntadores a estructuras BPB |
| (esto último sólo en los dispositivos de bloques). |
| offset 16h: BYTE: Desde el DOS 3.0, número de discos lógicos existentes |
| hasta ese momento ej. 3 para A: B: y C: (solo en los |
| dispositivos de bloque). |
+--------------------------------------------------------------------------------------+
Esta es la primera de todas las órdenes y se ejecuta siempre una vez cuando el dispositivo es cargado en
memoria, con objeto de que éste se inicialice. Aquí sí se pueden emplear libremente las funciones del DOS
(en el resto de las órdenes no: el driver es un programa residente más). En su inicialización el driver decide qué
cantidad de memoria se queda residente y puede analizar la línea de comandos del CONFIG.SYS para
comprobar los parámetros del usuario. En los dispositivos de bloque se indica también al sistema el número
de unidades definidas por el controlador y la dirección de una tabla de punteros a estructuras BPB, ya que
existe una de estas estructuras para cada unidad lógica. El BPB (BIOS Parameter Block) es una estructura
que contiene información sobre las unidades; puede consultarse en el capítulo 7. Aunque el BPB ha sido
ampliado en las últimas versiones del DOS, para construir discos de menos de 65536 sectores solo hace falta
completar los primeros campos (solo hasta los relacionados con el DOS 2.0 o, como mucho, el 3.0).
5 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
Los parámetros en la línea de comandos del CONFIG.SYS son similares a los de un programa ordinario,
aunque como se observa en el cuadro anterior su dirección se obtiene en el puntero de 32 bits ubicado en el
offset 12h de la cabecera de petición de solicitud. Por ello, si ES:BX apunta a dicha cabecera, la instrucción
LES BX,ES:[BX+12h] tiene como resultado alterar el valor de ES:BX para que ahora apunte a la zona de
parámetros. En ella, aparece todo lo que había después del '=' o el ' ' que seguía al DEVICE. Por ejemplo,
para una línea de config.sys como la siguiente:
DEVICE \DOS\VDISK.SYS 128
el contenido de la zona de parámetros sería '\DOS\VDISK.SYS 128' -sin incluir las comillas,
lógicamente-. Como se puede observar, el nombre y ruta del programa están separados de sus parámetros
por uno o más delimitadores (espacios en blanco o tabuladores -ASCII 9-); al final se encuentra el código de
retorno de carro -ASCII 13- aunque quizá en algunas versiones del DOS podría estar indicado el final de la
cadena por un salto de línea -ASCII 10- en lugar del retorno de carro. Aviso: tras el nombre/ruta del fichero,
las versiones más antiguas del DOS colocan un byte a cero. No se debe modificar la línea de parámetros:
además de improcedente puede ser peligroso, al tratarse de un área de datos del sistema. En los dispositivos
de bloque, el mismo campo donde se obtiene la dirección de los parámetros ha de ser empleado para
devolver al DOS la dirección de los punteros a los BPB: el sentido común indica que primero debe leerse la
dirección de los parámetros y después puede modificarse dicho campo.
Esta orden sólo es preciso implementarla en los dispositivos de bloques, sirve para que el sistema pregunte
al controlador si se ha producido un cambio en el soporte: por ejemplo, si se ha cambiado el disquete de la
disquetera. En general, los discos fijos y virtuales suelen responder que no, ya que es seguro que nadie puede
haberlos cambiado; en los disquetes suele responderse que sí (ante la duda). En caso de que el soporte haya
cambiado, el DOS invalida y libera todos los buffers en memoria relacionados con el mismo. Si no ha
cambiado, el DOS sacará la información de sus buffers internos evitando en lo posible un acceso al disco.
+--------------------------------------------------------------------------------------+
| CABECERA DE PETICIÓN DE SOLICITUD PARA LA ORDEN 1 (MEDIA CHECK) |
+--------------------------------------------------------------------------------------+
| offset 0 13 BYTES: Ya vistos con anterioridad. |
| offset 13 BYTE: A la entrada, el DOS indica el descriptor del soporte |
| (solo en dispositivos de bloque) |
| offset 14 BYTE: A la vuelta, el driver indica el resultado: 0FFh si se ha |
| producido un cambio, 0 si se desconoce (lo que equivale |
| al primer caso) y 1 si no ha habido cambio. |
+--------------------------------------------------------------------------------------+
Es ejecutada por el sistema si la respuesta a la orden MEDIA CHECK es afirmativa (cambio de soporte).
El DOS necesita entonces averiguar las características del nuevo soporte, para lo que pide al driver que le
suministre un BPB con información. De nuevo, esta orden solo ha de implementarse en los dispositivos de
bloques. Desde el DOS 3.0 se recomienda anotar la etiqueta de volumen del disco cuando se ejecuta esta
orden para detectar un posible cambio ilegal del mismo, aunque lo cierto es que este método es bastante
ineficiente (discos sin etiquetar, con la misma etiqueta...); desde el DOS 4.0 se mejora este asunto con los
números de serie, pero pocos drivers se molestan en comprobarlos. Las versiones más antiguas del DOS
(2.x) necesitan que cambie el byte descriptor de soporte para detectar el cambio de disco. Las versiones
actuales, habida cuenta del caos de bytes de identificación comunes para disquetes diferentes, no requieren
que el byte descriptor cambie para aceptar el cambio y confían en la información que suministra MEDIA
CHECK.
En los discos de tipo IBM, los más comunes, el DOS intenta cooperar con el controlador de dispositivo
6 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
en los cambios de disco. Por ello, se las apaña para leer el primer sector de la FAT y se lo pasa al driver, que
así tiene más fácil la tarea de detectar el tipo de disco y suministrar al DOS el BPB adecuado, ya que el
primer byte de la FAT contiene el tipo de disco (byte descriptor de medio). En los discos que no son de tipo
IBM es el driver quien, por sus propios medios, ha de apañárselas para detectar el tipo de disco introducido
en la unidad correspondiente: por ejemplo, leyendo el sector de arranque. En algunos casos puede resultar útil
indicar que el disco es de tipo no IBM; por ejemplo en un controlador para un soporte físico que necesite
detectar el medio introducido para poder acceder al mismo. Por ejemplo en una disquetera: al introducir un
nuevo disco de densidad diferente al anterior, el intento por parte del DOS de leer la FAT en los discos tipo
IBM provocaría un fallo (si esto no sucede con el controlador del propio sistema para las disqueteras es
porque la BIOS suplanta al DOS, realizando quizá algunas tareas más de las que debería tener estrictamente
encomendadas al detectar un cambio de disco).
+--------------------------------------------------------------------------------------+
| CABECERA DE PETICIÓN DE SOLICITUD PARA LA ORDEN 2 (BUILD BPB) |
+--------------------------------------------------------------------------------------+
| offset 0 13 BYTES: Ya vistos con anterioridad. |
| offset 13 BYTE: A la entrada, el DOS indica el descriptor del soporte. |
| (solo en dispositivos de bloque) |
| offset 14 DWORD: A la entrada, el DOS apunta a un buffer que contiene el |
| primer sector de la FAT (cuyo 1º byte es el descriptor de |
| soporte) si el disco es de tipo IBM; de lo contrario el |
| buffer está vacío y puede emplearse para otro propósito. |
| offset 18 DWORD: A la vuelta, el driver devuelve aquí la dirección del BPB |
| del nuevo disco (no la de ninguna tabla de punteros). |
+--------------------------------------------------------------------------------------+
Puede ser soportada tanto por los dispositivos de caracteres como por los de bloque, el sistema solo la
utiliza si así se le indicó en la palabra de atributos del dispositivo (bit 14). El IOCTL es un mecanismo
genérico de comunicación de las aplicaciones con el controlador de dispositivo; por medio de esta función, los
programas de usuario solicitan información al controlador (subfunciones 2 y 4 de la función 44h del DOS) sin
tener que emplear el canal normal por el que se envían los datos. Es frecuente que no esté soportada en los
dispositivos más simples. La cabecera de petición de solicitud de esta orden y de varias de las que veremos a
continuación es la siguiente:
+--------------------------------------------------------------------------------------+
| CABECERA DE PETICIÓN DE SOLICITUD PARA LAS ÓRDENES: |
| 3 (IOCTL INPUT) |
| 4 (INPUT) |
| 8 (OUTPUT) |
| 9 (OUTPUT VERIFY) |
| 10h (OUTPUT UNTIL BUSY) |
+--------------------------------------------------------------------------------------+
| offset 0 13 BYTES: Ya vistos con anterioridad. |
| offset 13 BYTE: A la entrada, el DOS indica el descriptor del soporte. |
| (solo en dispositivos de bloque) |
| offset 14 DWORD: En entrada, dirección del área de transferencia a memoria |
| offset 18 WORD: En entrada, número de sectores (dispositivos de bloques) |
| o bytes (dispositivos de caracteres) a transferir. |
| A la salida, sectores/bytes realmente transferidos. |
| offset 20 WORD: Número de sector de comienzo (solo en los dispositivos de |
| bloques y de menos de 32 Mb) |
| offset 22 DWORD: En las órdenes 4 y 8 y desde el DOS 3.0 se devuelve al |
| DOS un puntero a la etiqueta de volumen del disco en el |
| caso de un error 0Fh. |
| offset 26 DWORD: Número de sector de comienzo en discos de más de 32Mb |
7 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
Esta orden es una de las más importantes. Sirve para que el sistema lea los datos almacenados en el
dispositivo. Si el dispositivo es de caracteres, los almacenará en un buffer de entrada a medida que le van
llegando del periférico y los enviará en respuesta a esta orden (si no los tiene, espera un tiempo razonable a
que le lleguen antes de "fallar"). Si el dispositivo es de bloque, no se envían bytes sino sectores completos. En
los dispositivos de caracteres, lo más normal es que el DOS solicite transferir sólo 1 en cada vez, aunque en
teoría podría solicitar cualquier cantidad. En el caso de los dispositivos de bloque esta orden es ejecutada por
el DOS cuando se accede a disco vía INT 25h/26h.
Solo debe ser soportada por los dispositivos de caracteres. Es análoga a INPUT, con la diferencia de que
no se avanza el puntero interno al buffer de entrada de datos tras leer el carácter. Por ello, tras utilizar esta
orden será preciso emplear después la 4 para leer realmente el carácter. La principal utilidad de esto es que el
sistema puede saber si el dispositivo tiene ya un nuevo carácter disponible antes de llamarle, para evitar que
éste se quede parado hasta que le llegue. El bit 9 de la palabra de estado devuelta indica, si está activo, que el
dispositivo está ocupado (sin caracteres).
Solo disponible en dispositivos de caracteres, vacía el buffer del dispositivo. Lo que éste suele hacer es
sencillamente igualar los punteros al buffer de entrada interno (el puntero al último dato recibido del periférico
y el puntero al próximo carácter a enviar al sistema cuando se lo pida).
Es otra de las órdenes más importantes, análoga a INPUT pero actuando al revés. Permite al sistema
enviar datos al dispositivo, bien sean caracteres o sectores completos, según el tipo de dispositivo.
Es análoga a OUTPUT, con la salvedad de que el dispositivo efectúa, tras escribir, una lectura inmediata
hacia un buffer auxiliar, con la correspondiente comprobación de que lo escrito es correcto al comparar
ambos buffers. Resulta totalmente absurdo implementarla en un disco virtual (el 11% de la memoria del
sistema podría estar ya destinada a detectar un fallo en cualquier byte de la misma, y además es igual de
probable el error durante la escritura que durante la verificación) por lo que en este caso debe comportarse
igual que la orden anterior. En los discos físicos de verdad, sin embargo, conviene tomarla en serio.
8 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
Es similar a INPUT STATUS y, como ésta, propia de los dispositivos de caracteres. Su misión es
análoga, pero relacionada con el buffer de salida en vez del buffer de entrada.
Solo implementada desde el DOS 3.0 y superior, indica que el dispositivo o un fichero almacenado en él
ha sido abierto. El controlador se limita a incrementar un contador. Esta orden y las dos siguientes no han de
estar necesariamente soportadas.
Solo implementada desde el DOS 3.0 y superior, indica que el dispositivo o un fichero almacenado en él
ha sido cerrado. El controlador se limita a decrementar un contador: si éste llega a cero, se reinicializan los
buffers internos, si los hay, para permitir por ejemplo un posible cambio de disco.
Solo implementada también desde el DOS 3.0 y superior, indica al sistema si el dispositivo es removible o
no, apoyándose en los resultados de las dos órdenes anteriores.
Solo es admitida en dispositivos de caracteres y a partir del DOS 3.0; sirve para enviar más de un carácter
al periférico. En concreto, se envían todos los que sean posibles (de la cantidad solicitada) hasta que el
periférico esté ocupado: entonces se retorna. Aquí no se considera un error no haber podido transferir todo.
Esta función es útil para acelerar el proceso de salida.
Las órdenes 11h, 12h, 14h, 15h y 16h no han sido aún definidas, ni siquiera en el DOS 5.0. La orden
13h o GENERIC IOCTL, disponible desde el DOS 3.2 permite un mecanismo más sofisticado de
comunicación IOCTL. También en el DOS 3.2 han sido definidas las órdenes 17h (GET LOGICAL
DEVICE) y 18h (SET LOGICAL DEVICE). El DOS 5.0 añade una nueva: la 19h (CHECK GENERIC
IOCTL SUPPORT). Por cierto, las ordenes 80h y superiores están destinadas a la comunicación con los
dispositivos CD-ROM...
9 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
Los controladores de dispositivo forman una cadena en la memoria, una lista conectada por los 4 primeros
bytes de la cabecera utilizados a modo de puntero. A medida que se van instalando en memoria, quedan de
tal manera que los últimos cargados apuntan a los predecesores. Al final, el sistema operativo apunta el
dispositivo NUL al último dispositivo instalado, colocándose NUL al final de la cadena. Por tanto,
averiguando la dirección del dispositivo NUL y siguiendo la cadena de apuntadores obtenida en los primeros
4 bytes de cada uno (en la forma segmento:offset) se puede recorrer la lista de dispositivos (ya sean de
caracteres o de bloque) en orden inverso al que fueron instalados en memoria. El último de ellos estará
apuntando a XXXX:FFFF. La lista de controladores de dispositivo puede pasar por la memoria convencional
o por la superior, saltando de una a la otra múltiples veces. Algunos gestores de memoria, como QEMM
cuando se utiliza LOADHI.SYS (en lugar del DEVICEHIGH del DOS) colocan la cadena de dispositivos en
memoria convencional, aunque luego instalen el mismo en memoria superior. Esto quiere decir que para
acceder al código o datos internos del dispositivo conviene tomar precauciones, de cara a averiguar la
dirección donde realmente reside. El programa TURBODSK que veremos más adelante utiliza la cadena de
controladores de dispositivo para buscarse a sí mismo en memoria e identificar todas las posibles unidades
que controla. Por desgracia, la manera de obtener la dirección del dispositivo NUL varía de unas versiones
del DOS a otras, aunque solo ligeramente. Hay que utilizar la función indocumentada Get List of Lists
(servicio 52h del DOS) e interpretar la información que devuelve: En ES:BX más un cierto offset comienza la
cabecera del dispositivo NUL (el propio dispositivo, no un puntero al mismo). Ese offset es 17h para las
versiones 2.X del DOS, 28h para la 3.0X y 22h para todas las demás, habidas y por haber. La utilidad
DRV.C listada más abajo recorre los dispositivos instalados, informando de ellos. Adicionalmente, excepto
en las versiones más antiguas del DOS, DRV.C accede a los bloques de control de memoria que preceden a
los dispositivos que están ubicados en un offset 0 respecto al segmento, con objeto de indicar el consumo de
memoria de los mismos y el nombre del fichero ejecutable. Con DR-DOS 5.0 no se informa correctamente
del nombre, ni tampoco del tamaño (excepto si el dispositivo está instalado en memoria superior); no hay
problemas sin embargo con DR-DOS 6.0 ni, por supuesto, con MS-DOS 4.0 ó posterior. A continuación,
antes del listado del programa, se muestra un ejemplo de salida del mismo bajo MS-DOS 5.0 (por supuesto,
no recomiendo a nadie instalar tantos discos virtuales).
+==== DRV 1.0 === LISTA DE DISPOSITIVOS DEL SISTEMA === (c) 1992 CiriSOFT ====+
| Dirección Tipo Nombre Estrat. Interr. Atributo Programa Tamaño |
| --------- -------- ------------- -------- -------- -------- -------- ------ |
| 0116:0048 Carácter NUL 0DC6 0DCC 8004 |
| E279:0000 Bloque Unidad I: 00CB 00D6 0800 RAMDRIVE 1184 |
| E22B:0000 Bloque Unidad H: 00CB 00D6 0800 RAMDRIVE 1232 |
| E1A7:0000 Bloque Unidad G: 0086 0091 0800 VDISK 2096 |
| E103:0000 Bloque Unidad F: 0086 0091 0800 VDISK 2608 |
| E0E6:0000 Bloque Unidad E: 005A 0065 0800 TDSK 448 |
| E0BE:0000 Bloque Unidad D: 005A 0065 0800 TDSK 624 |
| E013:0000 Carácter CON 0078 0083 8013 ZANSI 2720 |
| E003:0000 Carácter ALTDUP$ 00C2 00CD 8000 ALTDUP 240 |
| DFD8:0000 Carácter KEYBSP50 0012 0018 8000 KEYBSP 672 |
| DD90:0000 Carácter gmouse 0012 0021 8000 GMOUSE 9328 |
| DD85:0000 Carácter ACCESOS$ 0013 001A 8000 ACCESOS 160 |
| DD7C:0000 Carácter &FDREAD2 0012 0012 8000 FDREAD 128 |
| 0316:0000 Carácter KEYBUF21 0012 0018 8000 KEYBUFF 160 |
| D803:0000 Carácter SMARTAAR 00A2 00AD C800 SMARTDRV 22400 |
| 0255:003F Carácter QEMM386$ 0051 007D C000 |
| 0255:0000 Carácter EMMXXXX0 0051 0064 C000 QEMM386 3072 |
| 0070:0023 Carácter CON 06F5 0700 8013 |
| 0070:0035 Carácter AUX 06F5 0721 8000 |
| 0070:0047 Carácter PRN 06F5 0705 A0C0 |
| 0070:0059 Carácter CLOCK$ 06F5 0739 8008 |
| 0070:006B Bloque Unidades A:-C: 06F5 073E 08C2 |
| 0070:007B Carácter COM1 06F5 0721 8000 |
| 0070:008D Carácter LPT1 06F5 070C A0C0 |
10 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
// DRV 1.0
// Utilidad para listar los controladores de dispositivo instalados.
#include <dos.h>
#include <stdio.h>
struct REGPACK r;
unsigned long huge *siguiente;
unsigned char huge *disp;
int i, disco, dosver;
void main()
{
r.r_ax=0x3000; intr (0x21, &r); /* obtener versión del DOS */
dosver=(r.r_ax << 8) | (r.r_ax >> 8);
if ((dosver & 0xFF00)==0x200) i=0x17; /* DOS 2.XX */
else if ((dosver>0x2FF) && (dosver<0x30A)) i=0x28; /* DOS 3.0X */
else i=0x22; /* otra versión */
siguiente=MK_FP(r.r_es, r.r_bx+i);
printf("\n+==== DRV 1.0 === LISTA DE DISPOSITIVOS DEL SISTEMA ===
(c) 1992 CiriSOFT ====+\n");
printf("| Dirección Tipo Nombre Estrat. Interr.
Atributo Programa Tamaño |\n");
printf("| --------- -------- ------------- -------- --------
-------- -------- ------ ");
while (FP_OFF(siguiente)!=0xffff) {
disp = (unsigned char huge *) siguiente;
printf("|\n| %04X:%04X ", FP_SEG(disp), FP_OFF(disp));
if (disp[5] & 0x80) {
printf("Carácter ");
for (i=10; i<18; i++) printf("%c",disp[i]); printf(" ");
}
else {
printf("Bloque ");
if (disp[10]==1)
printf("Unidad %c: ", disco--);
else {
printf("Unidades %c:-%c:",disco-disp[10]+1, disco);
disco-=disp[10];
}
}
printf(" %04X %04X %04X ", disp[6] | (disp[7]<<8),
disp[8] | (disp[9]<<8), disp[4] | (disp[5]<<8));
11 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
El controlador propuesto de ejemplo crea un dispositivo HEX$ que imprime en pantalla y en hexadecimal
todo lo que recibe. Por supuesto, el programa se instala en el CONFIG.SYS con una orden del tipo
DEVICE=HEX.SYS. En principio, sería un programa mucho más simple si se limitara a imprimir los
caracteres que recibe, aunque ello no tendría utilidad alguna. De hecho, la mayor parte de la complejidad del
listado no se debe al controlador de dispositivo, sino al resto. Para empezar, las órdenes Open, Close o
Remove, en un hipotético dispositivo que simplemente sacara por pantalla lo que recibe están de más.
Además, la rutina que procesa los caracteres (procesa_AL) se limitaría a imprimirles; también se eliminarían
todas las demás subrutinas de apoyo. Sin embargo, el hecho de realizar un volcado hexadecimal complica
bastante el asunto. El listado hexadecimal que se obtiene es similar al siguiente:
Es preciso implementar la orden Open para detectar el inicio de la transferencia, inicializando a cero el
contador de offset relativo de la izquierda. Los caracteres se imprimen unos tras otros en hexadecimal (con un
guión separador tras el octavo) y se van almacenando en un buffer hasta completar 16: entonces, se imprimen
de nuevo pero en ASCII (sustituyendo por puntos los códigos de control). La orden Close sirve para detectar
el final de la operación: ante ella se escriben los espacios necesarios y se vuelcan los códigos ASCII
acumulados hasta el momento (entre 0 y 15) que restasen por ser imprimidos. Por emplear Open y Close este
controlador de dispositivo necesita DOS 3.0 o superior.
Utilizando COPY en vez de TYPE, al enviar varios ficheros con los comodines el COMMAND suele
encadenarles en uno solo y el offset es relativo al primero enviado (esto depende de la versión del intérprete
de comandos). Aunque se supone que el DOS va a enviar los caracteres de uno en uno, el dispositivo se toma
la molestia de prever que esto pueda no ser así, procesando en un bucle todos los que se le indiquen. Para
imprimir se utiliza la INT 29h del DOS (fast console OUTPUT), más recomendable que llamar a un servicio
del sistema operativo (que a fin de cuentas va a parar a esta interrupción). No hay que olvidar que los
controladores de dispositivo son también programas residentes a todos los efectos, con las mismas
limitaciones. Sin embargo, desde los programas normales no es recomendable utilizar la INT 29h, entre otras
razones porque esos programas, además de imprimir a poca velocidad, no soportarían redireccionamiento en
la salida (la INT 29h no es precisamente rápida, aunque sí algo más que llamar al DOS).
El dispositivo HEX$ sólo actúa en salida, imprimiendo en pantalla lo que recibe. Si se intenta leer desde él
devuelve una condición de error (por ejemplo, al realizar COPY HEX$ FICH.TXT). Para visualizar ficheros
12 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
binarios que puedan contener la marca de fin de fichero (^Z) no basta hacer TYPE o COPY a secas: en estos
casos se debe emplear COPY /B FICHERO.EXT HEX$, la opción /B sirve para que la salida no se detenga
ante el ^Z. La operación de impresión en pantalla se supone siempre exitosa; por ello el dispositivo no
modifica la variable que indica el número de caracteres a procesar: al devolverla precisamente como estaba al
principio indica que se han procesado sin problemas todos los solicitados. En la instalación se comprueba la
versión del DOS, para cerciorarse de la presencia de un 3.0 o superior. Este driver de ejemplo sólo consume
464 bytes de memoria bajo MS-DOS 5.0. Tras ensamblarlo y linkarlo hay que aplicar EXE2BIN para
pasarlo de EXE a SYS (TLINK /t sólo opera cuando hay un ORG 100h).
Como se puede verificar observando el listado, las únicas órdenes realmente soportadas por el dispositivo
son, aparte de OPEN, CLOSE y REMOVE, las órdenes WRITE y WRITE VERIFY. Todas las demás, en
este controlador que no depende del hardware típico de entrada/salida, son innecesarias. Como el proceso de
escritura en pantalla se supone siempre con éxito, WRITE VERIFY es idéntica a WRITE, sin realizar
verificación alguna. Las órdenes no soportadas pueden ser ignoradas o bien desembocar en un error, según
sea el caso.
Otra ventaja es que es mucho más flexible que los discos virtuales que acompañan al sistema operativo,
permitiendo definir con mayor libertad los parámetros e incluyendo uno nuevo (el tamaño de cluster). Los
usuarios avanzados nunca estuvieron contentos con los discos del sistema que abusaban demasiado del
ajuste de parámetros. Aunque una elección torpe de parámetros de TURBODSK puede crear un disco
prácticamente inútil, e incluso incompatible con algunas versiones del DOS, también es cierto que los usuarios
con menos conocimientos pueden dejar a éste que elija los parámetros por ellos, con excepción del tamaño
del disco. Los usuarios más informados, en cambio, no tendrán ahora trabas.
Sin embargo, la pretensión inicial de hacer TURBODSK más rápido que los discos del sistema, de la que
hereda su peculiar nombre, ha tenido que enfrentarse a la elevada eficiencia de RAMDRIVE. Las últimas
versiones de este disco ya apuran bastante el rendimiento del sistema, por lo que superarle sólo ha sido
posible con un truco en la memoria expandida/convencional y en máquinas 386DX y superiores:
TURBODSK detecta estas CPU y aprovechar su bus de 32 bits para realizar las transferencias de bloques
de memoria. La velocidad es sin duda el factor más importante de un disco virtual, con mucho, por lo que no
se deben ahorrar esfuerzos para conseguirla.
A continuación se resumen las características de TURBODSK, comparándolo con los discos virtuales del
sistema: RAMDRIVE en representación del MS-DOS 5.0 (aunque se incluye una versión más reciente que
viene con WINDOWS 3.1) y el VDISK de DR-DOS 6.0. Como puede observarse, la única característica
que TURBODSK no presenta es el soporte de memoria extendida vía INT 15h de VDISK, tampoco
13 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
+-----------------+
| CARACTERÍSTICAS |
+-----------------+---------------------------------------------------------------+
| RAMDRIVE VDISK TURBODSK |
| (WINDOWS 3.1) (DR-DOS 6.0) v2.3 |
+---------------------------------------------------------------------------------+
| Capacidad máxima: 32 Mb 32 Mb 64 Mb |
| Soporte de memoria convencional: Sí Sí Sí |
| Soporte de memoria EMS: Sí Sí Sí |
| Soporte de memoria extendida INT 15h: No Sí No |
| Soporte de memoria extendida XMS: Sí No Sí |
| Tamaño de sector soportado: 128-1024 128-512 32-2048 |
| Ficheros en directorio raíz: 4-1024 4-512 1-65534 |
| Asignación dinámica de la memoria: No No Sí |
| Tamaño de cluster definible: No No Sí |
| Memoria convencional consumida (MS-DOS 5.0): 1184-1232 2096-2608 448-624 |
+---------------------------------------------------------------------------------+
Para calcular la velocidad de los discos virtuales se ha utilizado el programa KBSEC.C listado más abajo.
Los resultados de KBSEC pueden variar espectacularmente en función del fabricante del controlador de
memoria o del sistema operativo. Este programa de test es útil para analizar el rendimiento de un disco virtual
en fase de desarrollo o para que el usuario elija la memoria más rápida según la configuración de su equipo.
Dicho programa bloquea todas las interrupciones excepto IRQ 0 (INT 8), la cual a su vez desvía con objeto
de aumentar la precisión del cálculo; por ello es exclusivo para la comprobación de discos virtuales y no
flexibles. Debe ser ejecutado sin tener instalado ningún caché. KBSEC fuerza el buffer de transferencia a una
dirección de memoria determinada, con objeto de no depender aleatoriamente de la velocidad dispar de la
memoria y los controladores XMS/EMS en función del segmento que sea utilizado. La fiabilidad de KBSEC
está avalada por el hecho de que siempre da exactamente el mismo resultado al ser ejecutado en las mismas
condiciones. Para hacerse una idea de la potencia de los discos virtuales, conviene tener en cuenta que un
disco fijo con 19 ms de tiempo de acceso e interface IDE, en un 386-25 puede alcanzar una velocidad de
transferencia de casi un megabyte, 17 veces menos que la mejor configuración de disco virtual -que además
posee un tiempo de acceso prácticamente nulo- en esa misma máquina.
+--------------------------------------------------------------------------------------+
| Velocidad del disco bajo MS-DOS 5.0, calculada por KBSEC, con los buffers que |
| establece el DOS por defecto (aunque esto no influye en KBSEC) y con sólo KEYB y |
| DOSKEY instalados. Para evaluar la memoria convencional no estaba instalado ningún |
| controlador de memoria; para la memoria XMS estaba instalado sólo HIMEM.SYS y para |
| la EMS, tanto HIMEM.SYS como EMM386.EXE a la vez (los resultados varían bastante |
| en función de la gestión de memoria del sistema). Datos en Kb/segundo. |
+--------------------------------------------------------------------------------------+
| VDISK RAMDRIVE TURBODSK |
| 8088-8 MHz: |
| - Memoria convencional: 563 573 573 |
| 286-12 Mhz (sin estados de espera): |
| - Memoria extendida/XMS: 1980 4253 4253 |
| - Memoria convencional: 4169 4368 4368 |
| 386-25 MHz (sin caché): |
| - Memoria extendida/XMS: 6838 17105 17095 |
| - Memoria expandida EMS: 1261 8308 14937 |
| - Memoria convencional: 7297 6525 14843 |
| 486-25 MHz sin caché externa: |
| - Memoria extendida/XMS: 7370 10278 10278 |
| - Memoria expandida EMS: 2533 7484 9631 |
14 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
/*********************************************************************
* *
* KBSEC 1.2 - Utility to calc with high precision the data transfer *
* rate (the read data transfer read) in a ramdisk. *
* *
* (C) 1992-1995 Ciriaco García de Celis *
* *
* - Do not run this program with a cache program loaded; compile *
* it in LARGE memory model with «Test stack overflow» option *
* disabled. Use Borland C. This program has english messages. *
* *
*********************************************************************/
#include <stdio.h>
#include <dos.h>
#include <conio.h>
#include <stdlib.h>
15 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
prep_hw(); ti=tiempo=vueltas=0;
while (ti==tiempo); /* esperar pulso del reloj */ ti+=TIEMPO;
rest_hw(TIEMPO); clrscr();
El listado fuente de TURBODSK consta de un único fichero que ha de ser ensamblado sin demasiados
parámetros especiales. Este programa puede ser perfectamente ensamblado de manera indistinta por MASM
6.X (con el parámetro de compatibilidad con versiones anteriores) o por TASM, aunque preferiblemente por
el segundo. Versiones de MASM anteriores a la citada no tienen potencia suficiente, básicamente porque no
permiten emplear la directiva .386 dentro de los segmentos. Con TASM conviene emplear la opción /m5 para
que el ensamblador ejecute todas las pasadas necesarias para optimizar el código al máximo (como mínimo
habría que solicitar 2, en cualquier caso, para que no emita errores).
Se describirán paso a paso todas las peculiaridades del programa, por lo que el listado debería ser
comprensible prácticamente al 100%. A lo largo de la explicación aparecen numerosas alusiones al
comportamiento de RAMDRIVE y VDISK. Por supuesto, los detalles referidos a RAMDRIVE o VDISK se
refieren exclusivamente a la versión de los mismos que acompaña a Windows 3.1 y a DR-DOS 6.0,
respectivamente, no siendo necesariamente aplicable a otras anteriores o futuras de dichos programas.
Evidentemente, la información sobre ambos no ha sido obtenida escribiendo al fabricante para solicitarle el
listado fuente, por lo que es un tanto difusa e incompleta, aunque sí suficiente para complementar la
explicación de TURBODSK y dar una perspectiva más amplia.
LA CABECERA DE TURBODSK
16 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
A continuación vienen las variables de TURBODSK, la mayoría de las cuales son intuitivas. Sin embargo,
las dos primeras son algo especiales. La primera (cs_tdsk) está destinada a almacenar el valor del registro
CS, que indica dónde reside el disco virtual. Aunque en principio puede parecer redundante, esta operación
es necesaria para lograr la compatibilidad con algunos gestores de memoria, como QEMM, que pueden
cargar la cabecera del dispositivo en memoria convencional y el resto del mismo en la superior: a nosotros nos
interesa conocer la dirección donde reside todo el dispositivo, con objeto de acceder a él para ulteriores
modificaciones de sus condiciones de operación. Cuando se utiliza el LOADHI de QEMM, el dispositivo es
cargado en memoria superior, pero después QEMM se encarga de copiar la cabecera en memoria
convencional, pasando la cadena de controladores de dispositivo del DOS por dicha memoria. Como
nosotros buscaremos a un posible TURBODSK residente siguiendo esa cadena, gracias a la variable
cs_tdsk podemos saber la dirección real del disco virtual. QEMM crea además unas falsas rutinas de
estrategia e interrupción en memoria convencional que luego llaman a las de la memoria superior. Sin
embargo, esto no es relevante para nosotros. Por fortuna, QEMM 6.0 también soporta el DEVICEHIGH del
DOS, en cuyo caso la totalidad del dispositivo es cargado en memoria superior; sin embargo, no está de más
tomar precauciones para los casos en que no sea así.
La segunda variable es id_tdsk y su utilidad es fundamental: sirve para certificar que el controlador de
dispositivo es TURBODSK, indicando además la versión. Esta variable está ubicada en los primeros 18 bytes
de la cabecera, que son los que QEMM copia en memoria convencional. Si algún gestor de memoria extraño
realizara la misma maniobra de QEMM y copiase menos de 18 bytes en memoria convencional, no pasaría
nada: TURBODSK sería incapaz de hallarse a sí mismo residente en la memoria superior, por lo que no
habría riesgo alguno de provocar un desastre. Por fortuna, estas complicadas argucias de los controladores de
memoria tienden a desaparecer desde la aparición del DOS 5.0 que, de alguna manera, ha normalizado el uso
de la memoria superior.
Existe otra variable importante, tipo_soporte, que indica en todo momento el estado del disco. En general,
las variables más importantes de TURBODSK han sido agrupadas al principio y el autor del programa se ha
comprometido a no moverlas en futuras versiones. Esto significa que otros programas podrán detectar la
presencia de TURBODSK e influir en sus condiciones de operación.
Más adelante hay otras variables internas al programa: por un lado, la tabla de saltos para las rutinas que
controlan el dispositivo; por otro, un BPB con información válida (si no fuera correcto, el DOS se podría
estrellar al cargar el dispositivo desde el CONFIG). Este BPB será modificado cuando se defina el disco, se
defina éste desde el CONFIG o no (esto último es lo más normal y recomendable). En el BPB solo se han
completado los campos correspondientes al DOS 2.x; la razón es que los demás no son necesarios ni siquiera
para el DOS 5.0: la información adicional de las últimas versiones de los BPB es empleada por las rutinas de
más bajo nivel del sistema operativo, aquellas que se relacionan con la BIOS y el hardware; sin embargo,
estas nuevas variables no son relevantes para la interfaz del DOS con el controlador de dispositivo.
Veremos ahora las principales rutinas de TURBODSK. Para empezar, la rutina de estrategia de
TURBODSK no merece ningún comentario, pero sí la de interrupción. Es bastante parecida a la de los discos
17 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
del sistema, pero con una diferencia: si el disco no está aún preparado y no se ha reservado memoria para él
(esto sucede con la variable tipo_soporte igual a cero) hay que rechazar todos los accesos al disco
devolviendo un código de unidad no preparada, algo así como decir que no hay disquete dentro de la
disquetera virtual. En cualquier otro caso, y valiéndose de la tabla de saltos, llamamos a la subrutina
adecuada que gestiona cada orden. Estas subrutinas devuelven en AX la palabra de estado que hay que
devolver al sistema, por lo que al final se realiza esta operación. En el caso de un error de transferencia
(debido al fallo de algún controlador de memoria o a un intento de acceso fuera de los límites del disco), se
indica al DOS que se han transferido 0 sectores; de lo contrario, esta variable de la cabecera de petición
queda como estaba al principio, indicando que se han transferido tantos sectores como fueron solicitados.
Las órdenes READ NOWAIT, INPUT STATUS, INPUT FLUSH, OUTPUT STATUS, OUTPUT
FLUSH, IOCTL OUTPUT, OPEN y CLOSE no están realmente soportadas. Sin embargo, si el DOS las
invoca, TURBODSK se limita a terminar como si nada hubiera sucedido, devolviendo una palabra de estado
100h que indica función terminada. A la orden IOCTL INPUT, en cambio, se responde con un error (orden
no soportada) ya que TURBODSK no está preparado para enviar cadenas IOCTL a nadie (una cosa es no
hacer caso de las que envían, ¡pero cuando además las solicitan!); en general, el comportamiento hasta el
momento es 100% idéntico al de RAMDRIVE.
Sin embargo, la orden MEDIA CHECK es totalmente diferente de la de los discos virtuales del DOS. A la
pregunta de ¿ha habido cambio de disco?, tanto VDISK como RAMDRIVE responden siempre que no. En
cambio, TURBODSK puede haber sido modificado por el usuario, debido a la asignación dinámica de
memoria que soporta. En estos casos, el programa que formatea el disco virtual (el propio TURBODSK
cuando el usuario define un disco) colocará la variable cambiado a un valor 0FFh. Este valor es el que se
devolverá la primera vez al DOS, indicando que se ha producido un cambio de disco. Las siguientes veces,
TURBODSK no volverá a cambiar (no hasta otro formateo), motivo por el cual la variable se redefine a 1.
En el momento en que el disco es cambiado, el DOS ejecuta la orden BUILD BPB, con la que se le
suministra la dirección del nuevo BPB (la misma de siempre, pero con un BPB actualizado).
La orden REMOVE se limita a devolver una condición de controlador ocupado. No estaba muy claro qué
había que hacer con ella, por lo que se optó por imitar el funcionamiento de RAMDRIVE. Lo cierto es que
hay órdenes que casi nunca serán empleadas, o que no tiene sentido que sean utilizadas, pero conviene
considerarlas en todo caso.
Las últimas órdenes que implementa TURBODSK son las de lectura y escritura o escritura con
verificación. En estas órdenes simplemente se inicializa un flag (el registro BP) que indica si se trata de leer o
escribir: si BP es 0 es una escritura, si es 1 una lectura. Finalmente, se salta a la rutina Init_io que se encarga
de preparar los registros para la lectura o escritura, consultando el encabezamiento de petición de solicitud
para estas órdenes.
Más o menos mezclada con estas órdenes está la rutina que gestiona la interrupción 19h. Esta interrupción
es necesario desviarla para mejorar la convivencia con algunos entornos multitarea basados en el modo virtual
del 386. En principio, cuando una tarea virtual es cancelada (debido a un CTRL-ALT-DEL o a un cuelgue de
la misma) el sistema operativo debería desasignar todos los recursos ligados a ella, incluida la memoria
expandida o extendida que tuviera a su disposición. Sin embargo, parece que existen entornos no muy
eficientes en los que al anular una tarea no se recupera la memoria que ocupaba. Por tanto, es deber de la
propia tarea, antes de morir, el devolver la memoria a los correspondientes controladores. La interrupción
19h se ejecuta en estos momentos críticos, por lo que TURBODSK aprovecha para liberar la memoria
EMS/XMS ocupada y, tras restaurar el vector previo de INT 19h (para mejorar la compatibilidad) continúa
el flujo normal de la INT 19h. La mayoría de los discos virtuales no desvían la INT 19h; sin embargo,
RAMDRIVE sí y TURBODSK no quería ser menos... aunque, en el caso de utilizar memoria convencional no
se realiza ninguna tarea (RAMDRIVE ejecuta una misteriosa y complicada rutina).
18 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
La rutina Init_io se ejecuta inmediatamente antes de una lectura o escritura en el disco, preparando los
registros. Se controla aquí que el primer y último sector a ser accedido estén dentro del disco: en caso
contrario se devuelve un error de sector no encontrado. En realidad, TURBODSK no comprueba si el
primer sector está en el disco, para ahorrar memoria; al contrario que la mayoría de los discos virtuales. La
razón es que si el último sector está dentro del disco ¡como no lo va a estar también el primero!. También hay
que tener en cuenta la histórica leyenda de los 64 Kb. En concreto, el problema reside en la dirección donde
depositar o leer los datos. Pongamos por ejemplo que un programa pretende leer del disco virtual 48 Kb de
datos en la dirección DS:A000h. En principio, el manual de referencia para programadores de Microsoft dice
que el dispositivo solo está obligado a transferir cuanto pueda sin cambiar de segmento. Sin embargo, el
RAMDRIVE de Microsoft no considera esta circunstancia, por lo que si un programa intenta hacer un acceso
ilegal de este tipo se corromperá también una parte indeseada del segmento de datos, ya que al llegar al final
de un segmento se comienza por el principio del mismo otra vez (esto no es así en el caso de emplear
memoria extendida, pero sí en la convencional y expandida). En TURBODSK se prefirió limitar la
transferencia al máximo posible antes de que se desborde el segmento: hay que tener en cuenta que un
desbordamiento en el segmento de datos puede llegar a afectar al de código, con todo lo que ello implica.
Cierto es que un acceso incorrecto a disco es una circunstancia crítica de la que no se puede responsabilizar
al mismo, pero a mi juicio es mejor no poner las cosas todavía peor.
Otro asunto es controlar el tamaño absoluto del área a transferir: en ningún caso debe rebasar los 64 Kb,
aunque no está muy claro si los puede alcanzar o no. RAMDRIVE opera con palabras de 16 bits,
permitiendo un máximo de 8000h (exactamente 64 Kb), excepto en el caso de trabajar con memoria
extendida: al pasar el nº de palabras a bytes, unidad de medida del controlador XMS, el 8000h se convierte
en 0 (se desborda el registro de 16 bits al multiplicar por 2): con este tipo de memoria RAMDRIVE no
soporta transferencias de 64 Kb exactos (por ello, KBSEC.C emplea un buffer de 63 y no de 64 Kb). En
TURBODSK se decidió transferir 64 Kb inclusive como límite máximo, en todos los casos. En memoria
expandida y convencional, por otro lado, existe el riesgo de que el offset del buffer sea impar y, debido al
tamaño del mismo, se produzca un acceso de 16 bits en la dirección 0FFFFh, ilegal en 286 y superiores. Esto
provoca un mensaje fatal del controlador de memoria, preguntando si se desea seguir adelante o reinicializar
el sistema (QEMM386), o simplemente se cuelga el ordenador (con el EMM386 del MS-DOS 5.0 o en
máquinas 286). Por ejemplo, pruebe el lector a leer justo 32 Kb en un buffer que comience en 8001h con
RAMDRIVE en memoria EMS: RAMDRIVE no pierde el tiempo comprobando estas circunstancias críticas,
aunque VDISK parece que sí. En TURBODSK se optó también por ser tolerante a los fallos del programa
que accede al disco: además de limitar el acceso máximo a 64 Kbytes, y de transferir sólo lo que se pueda
antes del desbordamiento del segmento, puede que todavía se transfiera entre uno y tres bytes menos, ya que
se redondea por truncamiento la cuenta de palabras que faltan para el final del segmento para evitar un
direccionamiento ilegal en el offset 0FFFFh (estas circunstancias críticas deben evaluarse utilizando las
interrupciones 25h/26h, ya que al abrir ficheros ordinarios el DOS es siempre suficientemente cauto para no
poner a prueba la tolerancia a fallos de las unidades de disco).
Inmediatamente después de la rutina Init_io de TURBODSK está colocada la que gestiona el disco en
memoria expandida. No existe ningún nexo de unión y ambas se ejecutan secuencialmente. Al final de Init_io
hay una instrucción para borrar el acarreo. Esto es así porque la rutina que gestiona el disco puede ser
accedida, además de desde Init_io, desde el gestor de la interrupción 19h. El acarreo sirve aquí para
discernir si estamos ante una operación normal de disco o ante una inicialización del sistema. En el caso de una
operación de disco, BP indica además si es lectura o escritura. TURBODSK soporta también memoria
extendida XMS y convencional: cuando se utilizan estas memorias, la rutina correspondiente sustituye a la de
memoria EMS por el simple y efectivo procedimiento de copiarla encima. Esta técnica, que horrorizará a más
de un programador, es frecuente en la programación de sistemas bajo MS-DOS. De esta manera,
TURBODSK y RAMDRIVE (que también comete esta inmoralidad) economizan memoria, ya que solo
queda residente el código necesario. El hecho de que por defecto esté colocada la rutina de memoria
expandida es debido a que es, con diferencia, la más larga de todas y así siempre queda hueco para copiar
19 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
encima las otras. A la hora de terminar residente, si la máquina tiene memoria extendida y no se indica /A, no
se dejará espacio más que para las rutinas de memoria extendida y convencional, para economizar más
memoria.
Las rutinas que gestionan los diversos tipos de memoria tienen los mismos parámetros de entrada
(obtenidos de Init_io) y sirven para leer/escribir en el disco según lo que indique BP, así como para liberar la
memoria asignada en respuesta a una interrupción 19h. Retornan devolviendo en AX el resultado de la
operación, que será normalmente exitoso. En caso de fallo de algún controlador de memoria, devolverían un
código de error de anomalía general.
La rutina más compleja es la que gestiona la memoria expandida EMS. Además, un disco virtual que se
precie debe soportar transferencias incluso en el caso de que el buffer donde leer/escribir los datos esté
también en la memoria expandida y se solape con el propio disco. Este aspecto no es tenido en cuenta por
ningún disco virtual de dominio público con soporte de memoria EMS que yo conozca, aunque sí por los del
DOS; a esto se debe que algunas aplicaciones que trabajan con memoria expandida adviertan que pueden
operar mal con ciertos discos virtuales.
En el caso de VDISK, el algoritmo es muy poco eficiente: este disco virtual realiza un bucle, con una vuelta
para cada sector, donde hace todas estas tareas: preservar el contexto del mapa de páginas, calcular las
direcciones, transferir a un buffer auxiliar, recuperar el contexto del mapa de páginas y transferir del buffer
auxiliar hacia donde solicita el DOS. Ello significa que, para transferir 32 Kb en sectores de 0,5 Kb, se salva
y restaura ¡64 veces! el contexto del mapa de páginas. No digamos si los sectores son más pequeños,
además del hecho (mucho más grave) de que transfiere dos veces y de la cantidad de veces que calcula las
direcciones. Cierto es que salvar el contexto del mapa de páginas y volverlo a restaurar es necesario, de cara
a que el disco virtual (un programa residente a todos los efectos) no afecte al programa de usuario que se está
ejecutando, por si éste utiliza también memoria expandida. La pregunta es, ¿por qué no sacaron los autores
de VDISK esas operaciones fuera del bucle?, y ¿por qué utilizar un buffer auxiliar?. Lógicamente hay una
respuesta. Piense el lector qué sucederá si el buffer donde leer o escribir que suministra el programa principal,
está en memoria expandida: ¡se solapa con el disco virtual!. Para solucionar este posible solapamiento,
VDISK se ve obligado a realizar esas operaciones con objeto de permitir una transferencia de la memoria
expandida a la propia memoria expandida, a través de un buffer auxiliar. Este algoritmo provoca que VDISK
sea prácticamente tan lento como un buen disco duro cuando trabaja con memoria expandida y sectores de
512 bytes, ¡y bastante más lento si se utilizan los sectores de 128 bytes que suele establecer por defecto!.
Además, el buffer del tamaño de un sector incrementa el consumo de memoria en 512 bytes.
+-------------------------------------------------------------------------------------------
| ESQUEMA DE FUNCIONAMIENTO DE LA RUTINA DE GESTIÓN DE MEMORIA EMS DE TURBODSK
+-------------------------------------------------------------------------------------------
| Analizaremos el caso más conflictivo:
| Cuando el área a transferir ocupa los 16 Kbytes máximos.
|
|
| | | | |
| - - - - -+---------------+- - - - - - - - - - - - - -+---------------+- - - - -
| | | M | |
| | Página 3 | E | Página 3 |
| +---------------+ M +---------------+-- ^
| | | O | | 16
| | Página 2 | R | Página 2 | Kb
| +---------------+ I +---------------+-- v
| | | A | |
20 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
| | Página 1 | | Página 1 |
| +---------------+ E +---------------+ <----- cas
| | | M | |
| | Página 0 | S | Página 0 |
| - - - - -+---------------+- - - - - - - - - - - - - -+---------------+- - - - -
| | | | |
| | | | |
| +---------------+ <----- caso A +---------------+
|
|
| Resulta evidente, en el caso A, que si el buffer donde leer/escribir los datos comien
| debajo de la dirección marcada por la flecha (o justo en esa dirección) no colisionará c
| página 0, ya que no excede de 16 Kb de longitud. Como al convertir la dirección segmen
| párrafos se pierde precisión, TURBODSK se asegura que la dirección esté 401h párrafos
| más 1 párrafo) por debajo del inicio de la página 0.
|
| En el caso B, el buffer está en memoria expandida pero comienza justo detrás de la pá
| y, por lo que no hay colisión con esta página. Una vez más, por razones de redondeo, TU
| comprueba que el buffer comience al menos 401h párrafos por encima del inicio de la pág
| En realidad, bastaría con comprobar si dista al menos 400h bytes, ya que el redon
| convertir la dirección segmentada se hace truncando.
|
| Conclusión: para que no haya colisión, el buffer ha de estar a 401h párrafos de dis
| (expresada en valor absoluto) del inicio de la página 0. ¿Qué sucede si hay colisión?. Pu
| no se puede emplear la página 0, que se solapa con el buffer. En ese caso, bastaría con
| la página 2 ya que si el buffer empieza justo donde apunta la flecha del caso B, como su
| es de no más de 16 Kb, no puede invadir... sí, ¡sí puede invadir la página 2, aunque s
| párrafo! (no olvidar que si empieza por encima de la flecha no colisiona con la página 0)
| tanto, tenemos que utilizar la página 3. En general, en un sistema con memoria EMS 4.0
| las páginas pueden ser definidas por el usuario en la dirección que desee (parámetros /P
| EMM386 del MS-DOS 5.0), basta con asegurarse que la página alternativa a la 0, para los
| en que hay colisión, está alejada al menos 48 Kb de la página 0 (esto es, que entre
| páginas hay una distancia absoluta de 32 Kb).
|
| Se comprende ahora la necesidad de restaurar el contexto del mapa de páginas antes de
| utilizar una nueva página para las transferencias: el hecho de necesitar una nueva página
| determinado porque la hasta entonces utilizada se solapa con el buffer ¡y es preciso res
| el contenido del buffer!. Además, hay que volver a salvar el contexto de manera inmediat
| que quede salvado para otra ocasión (o para cuando se acabe el acceso al disco y haya
| restaurado).
+-------------------------------------------------------------------------------------------
21 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
Tras la transferencia, si había habido colisión se vuelve de nuevo a restaurar y preservar el contexto, para
volver al estado previo a la entrada en el bucle. Estas operaciones hacen que TURBODSK sea ligeramente
más lento cuando el buffer de lectura/escritura está en memoria expandida, pero probablemente la diferencia
no llegue al 1% al caso en que no hay solapamientos. El funcionamiento general consiste en ir mapeando las
páginas de memoria expandida una a una, considerando las tres posibilidades: al principio, puede ser
necesario transferir un fragmento del final de la primera página mapeada; después, puede ser preciso transferir
algunas páginas enteras y, por último, una parte inicial de la última página. Esto significa que TURBODSK
sólo mapea (y una sola vez) las páginas estrictamente necesarias para la transferencia; además, no transfiere
sector a sector sino el mayor número posible que pueda ser transferido de una sola vez y se evita la necesidad
de hacer doble transferencia (con el consiguiente ahorro, además, del buffer de 512 bytes). Este algoritmo
permite que TURBODSK sea tan rápido como cabría esperar de un disco virtual, incluso al trabajar con
memoria EMS. De hecho, al transferir 32 bits en los 386 y superiores, la velocidad que desarrolla en memoria
EMS no se queda muy por detrás de la que consigue el controlador de memoria XMS en estas máquinas. El
inconveniente de la rutina de gestión de memoria EMS en TURBODSK es, como se dijo antes, la
complejidad: está optimizada para reducir en lo posible el tamaño, por lo que puede resultar de difícil
comprensión. Por ejemplo, posee una subrutina encargada de acceder al controlador de memoria que, en
caso de fallo, altera la pila para retornar directamente al programa principal y no al procedimiento que la
llamó. Estas maniobras que aumentan la complejidad y dificultan posteriores modificaciones del código, están
bastante documentadas en el listado, por lo que no habrá más referencias a ellas. Hay que reconocer que por
30 ó 40 bytes más la rutina podría haber sido todo un ejemplo de programación estructurada, pero cuando se
escribió TURBODSK, entre los principales objetivos estaba reducir el consumo de memoria. Esta rutina es
además la misma para leer que para escribir: en el caso de la escritura, se limita simplemente a intercambiar la
pareja DS:SI con la ES:DI antes y después de realizar la transferencia.
RAMDRIVE, por su parte, cuenta con un algoritmo con un rendimiento similar al de TURBODSK, pero
totalmente distinto. La principal diferencia es que RAMDRIVE mapea varias páginas consecutivas, lo que le
permitiría en ocasiones ser levemente más rápido que TURBODSK; sin embargo, como no transfiere con 32
bits, en los 386 y superiores es notablemente más lento que TURBODSK. RAMDRIVE necesita que las
páginas de memoria expandida sean contiguas (podrían no serlo en EMS 4.0), emitiendo un error de
instalación en caso contrario; el método de TURBODSK es algo más tolerante: no necesita que sean
estrictamente contiguas, basta solo con que entre las 4 primeras haya alguna que diste de la primera al menos
32 Kb, la cual asigna dinámicamente.
Para terminar con el análisis de la gestión de este tipo de memoria, hablaremos algo acerca de la manera
de comunicarse con el controlador de memoria. En principio, lo más normal es cargar los registros e invocar la
INT 67h, analizando el valor en AH para determinar si ha habido error. Sin embargo, se ha constatado que
RAMDRIVE, ante un código de error 82h (EMM ocupado) vuelve a reintentar de manera indefinida la
operación, excepto en el caso de la función 40h (obtener el estado del gestor) utilizada en la instalación, en
la que hay sólo 32768 intentos. Este comportamiento parece estar destinado a mejorar la convivencia con
entornos multitarea, en los que en un momento dado el controlador de memoria puede estar ocupado pero
algo más tarde puede responder. Por tanto, también se incorporó esta técnica a TURBODSK.
Un último aspecto a considerar está relacionado con el uso de instrucciones de 32 bits en las rutinas de
TURBODSK: en principio han sido cuidadosamente elegidas con el objetivo de economizar memoria. Por
ello, la instrucción PUSHAD (equivalente a PUSHA, pero con los registros de 32 bits) venía muy bien para
apilar de una sola vez todos los registros de propósito general. Sin embargo, la correspondiente instrucción
POPAD no opera correctamente, por desgracia, en la mayoría de los 386, aunque el fallo fue corregido en las
últimas versiones de este procesador (los 386 de AMD también lo tienen, ¡qué curioso!). Se trata de un fallo
conocido por los fabricantes de software de sistemas, pero poco divulgado, aunque tampoco es muy grave:
básicamente, el problema reside en que EAX no se restaura correctamente. El fallo de esta instrucción, al
parecer descubierto por Jeff Prothero está ligado a las instrucciones que vienen inmediatamente a
continuación, y está demostrado que poniendo un NOP detrás -entre otros- nunca falla. En las rutinas de
22 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
TURBODSK se observa también que los registros de 32 bits empleados en la transferencia son
enmascarados para que no excedan de 0FFFFh, ya que podrían tener la parte alta distinta de 0 y ello
provocaría una trágica excepción del controlador de memoria al intentar un acceso -por otra parte, de manera
incorrecta- fuera de los segmentos de 64Kb.
La memoria extendida vía XMS, implementada por HIMEM.SYS y algún controlador de memoria
expandida, es notablemente más sencilla de manejar que la expandida. En el caso de VDISK, se emplea el
tradicional método de la INT 15h de la BIOS para transferir bloques en memoria extendida. Pese a ello, el
VDISK de DR-DOS 6.0 es una versión moderna del legendario controlador, y puede convivir
satisfactoriamente con WINDOWS y con los programas que soportan la especificación XMS debido a que
toma las precauciones necesarias. En TURBODSK se prefirió emigrar a los servicios del controlador XMS
(rutina Procesa_xms, al final del listado), al igual que RAMDRIVE, ya que casi todas las máquinas que
poseen memoria extendida en la actualidad tienen instalado el controlador XMS. Las que no lo tienen
instalado, se les puede añadir fácilmente (solo requiere al menos DOS 3.0). Las ventajas del controlador
XMS son múltiples. Por un lado, la velocidad es bastante elevada, ya que en los 386 y superiores utiliza
automáticamente instrucciones de transferencia de 32 bits. Por otro, es extraordinariamente sencillo el
proceso: basta crear una estructura con la información del bloque a mover de la memoria convencional
hacia/desde la extendida e invocar la función 0Bh. La diferencia entre TURBODSK y RAMDRIVE es que el
primero crea la estructura sobre la pila (solo son 8 palabras). La ventaja de ello es que las instrucciones
PUSH consumen mucha menos memoria que las MOV; por otro lado así no hace falta reservar el buffer para
la estructura. Hablando de pila: todos los programas residentes que utilizan servicios XMS suelen definir una
pila interna, ya que la llamada al controlador XMS puede crear una trama de pila de hasta ¡256 bytes!. Sin
embargo, RAMDRIVE no define una pila propia, y no es difícil deducir por qué: el DOS, antes de acceder a
los controladores de dispositivo, conmuta a una de sus pilas internas, que se supone suficientemente grande
para estos eventos. Por el mismo motivo, se decidió no incorporar una pila a TURBODSK, aunque hay
discos virtuales de dominio público que sí lo hacen. Es fácil comprobar la pila que el DOS pone a disposición
de los drivers: basta hacer un pequeño programa en DEBUG que acceda al disco virtual (por ejemplo, vía
INT 25h) y, sabiendo dónde reside éste, poner un punto de ruptura en algún lugar del mismo con una INT 3.
Al ejecutar el programa en DEBUG, el control volverá al DEBUG al llegar al punto de ruptura del disco
virtual, mostrando los registros. En MS-DOS 5.0, donde se hizo la prueba, todavía quedaban más de 2 Kb
de pila en el momento del acceso al disco virtual (el tamaño de la pila es el valor de SP). Finalmente, decir que
debido a que utilizan la misma memoria de la misma manera, TURBODSK y RAMDRIVE desarrollan
velocidades prácticamente idénticas al operar en memoria extendida.
Hay sin embargo un detalle curioso que comentar: RAMDRIVE instala una rutina que intercepta las
llamadas al controlador XMS. Hacer esto es realmente complicado, teniendo en cuenta que el controlador
XMS no se invoca por medio de una interrupción, como los demás controladores, sino con un CALL
inter-segmento. Por ello, es preciso modificar parte del código ejecutable del propio controlador de memoria.
Esto es posible porque el controlador XMS siempre empieza también por una instrucción de salto lejana de
cinco bytes (o una corta de dos o tres, seguida de NOP's, considerando RAMDRIVE todas estas diferentes
posibilidades). RAMDRIVE intercepta la función 1 (asignar el HMA), pero comprobando también si AL
vale 40h: esto significa que está intentando detectar la llamada de algún programa en concreto, ya que el valor
de AL es irrelevante para el controlador XMS. En ese caso, en lugar de continuar el flujo normal, determina la
memoria extendida libre y hace unas comprobaciones, pudiendo a consecuencia de ello retornar con un error
91h (el HMA ya está asignado). Todo parece destinado a mejorar la compatibilidad con algún programa,
probablemente también de Microsoft, aunque ningún otro disco virtual -TURBODSK entre ellos- realiza estas
extrañas maniobras. Esta forma de trabajar es lo que podríamos denominar programación a nivel de cloacas,
usando código basura para tapar la suciedad de otros programas previos.
23 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
En memoria convencional hay pocas diferencias entre todos los discos virtuales. Como no hay
controladores de memoria por el medio, la operación del disco siempre resultará exitosa. La diferencia de
TURBODSK frente a RAMDRIVE y VDISK es que en los 386 y superiores utiliza de nuevo transferencias
de 32 bits. Sin embargo, esto no es demasiado importante, ya que estas máquinas suelen tener la memoria
convencional destinada a cosas más útiles que un disco. En los PC/XT el rendimiento de todos los discos
virtuales suele ser muy similar, excepto algún despistado de dominio público que mueve palabras de 8 bits. La
rutina Procesa_con ubicada al final de TURBODSK se encarga de gestionar esta memoria.
LA SINTAXIS DE TURBODSK.
TURBODSK sólo necesita que se indique el tamaño del disco, ajustando los demás parámetros de la
manera más aconsejable. De lo expuesto anteriormente se deduce que es sencillo crear discos que no operen
correctamente, si no se tienen en cuenta las limitaciones de los diversos sistemas operativos, aunque esto es
responsabilidad del usuario y el programa no limita su libertad. Con /E se fuerza la utilización de memoria
extendida, aunque es un parámetro un tanto redundante (TURBODSK utiliza por defecto esta memoria). /A y
/X sirven, indistintamente, para utilizar memoria expandida.
24 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
Casi el 80% del listado de TURBODSK está destinado a instalar y mantener el disco virtual en memoria.
TURBODSK puede ser ejecutado desde la línea de comandos y desde el CONFIG.SYS; los
procedimientos Main e Init, respectivamente, constituyen el programa principal en ambos casos. El
funcionamiento del programa es muy similar en los dos casos, aunque hay ciertas diferencias lógicas. Al
principio de ambas rutinas se inicializa una variable que indica si estamos en el CONFIG o en el AUTOEXEC
(más en general, en la línea de comandos). Algunas subrutinas concretas actuarán de manera diferente según
desde donde sea ejecutado el programa.
El procedimiento Init se corresponde exactamente con la orden INIT del controlador de dispositivo,
realizando todas las tareas que cabría esperar de la misma: inicializar el puntero a la tabla de BPB's (solo uno,
ya que cada TURBODSK instalado controla un solo disco), el número de unidades (una), así como la
memoria que ocupa el programa: al final de Init, si no se va utilizar memoria expandida se reserva espacio
sólo para las rutinas de memoria convencional y extendida. Se puede definir el disco desde el CONFIG o, sin
indicar capacidad o indicando un tamaño 0, instalar el driver sin reservar memoria: para definir el disco se
puede ejecutar TURBODSK después desde el DOS. En cualquier caso, desde el CONFIG no se permite
definir el disco en memoria convencional, ya que si así fuera no se podría desasignar en el futuro. Tampoco es
muy recomendable reservar memoria extendida o expandida, para evitar una posible fragmentación de la
misma (esto depende de la eficacia de los controladores de memoria) aunque sí se permite definir un disco de
estos desde el CONFIG. También es vital considerar el parámetro de tamaño de sector que el usuario pueda
definir, incluso aunque no se cree el disco al indicar un tamaño 0. La razón es que el DOS asigna el tamaño de
sus buffers de disco para poder soportar el sector más grande que defina algún controlador de dispositivo de
bloques. El MS-DOS 5.0 no soporta sectores de más de 512 bytes, pero DR-DOS opera satisfactoriamente
con sectores de uno o dos Kbytes, e incluso más. Sin embargo, no es recomendable utilizar sectores de más
de 512 bytes, ya que el tamaño de los buffers aumenta y se consume más memoria. Empero, TURBODSK,
gracias a los sectores de más de 512 bytes permitiría operar con discos de más de 32 Mb sin rebasar el límite
máximo de 65535 sectores. Otro pequeño detalle: si la versión del DOS es anterior a la 3.0, se ajusta la
palabra de atributos, para indicar que no se soportan las órdenes Open/Close/Remove, con objeto de
parecerse lo más posible a un controlador del DOS 2.X (RAMDRIVE también se toma esta molestia).
También desde el CONFIG se desvía la INT 19h.
El procedimiento Main es muy similar al Init, la principal diferencia radica en que en el caso de utilizar
memoria convencional hay que terminar residente, para que el DOS respete el bloque de memoria creado
para contener el disco. Sin embargo, se dejan residentes sólo los primeros 96 bytes del PSP. También desde
Main puede ser necesario desalojar la memoria de un disco previo, si se indica uno nuevo. Es preciso, así
mismo, considerar ciertas circunstancias nuevas que no podían darse desde el CONFIG: una versión del
DOS anterior a la 2.0, que el driver no haya sido instalado antes desde el CONFIG, que se indique una letra
de unidad que no se corresponda con un driver TURBODSK, que el tamaño de sector exceda el máximo que
permite la configuración del DOS, que se solicite memoria expandida y no se halla reservado espacio para la
rutina que la soporta o que se intente redefinir el disco desde WINDOWS. Este último aspecto se consideró
a raiz de los riesgos que conlleva. Supongamos, por ejemplo, que el usuario abre una sesión DOS desde
WINDOWS y define un disco de media mega en memoria convencional, volviendo después a WINDOWS:
WINDOWS recupera toda la memoria convencional que había asignado para su propio uso, pero
TURBODSK no puede darse cuenta de esta circunstancia y, si el usuario intenta grabar algo en el disco
virtual, el sistema se estrellará. La memoria virtual de WINDOWS también da problemas al crear discos en
25 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
memoria expandida o extendida. Por tanto, las definiciones del disco han de hacerse antes de entrar en
WINDOWS. Tampoco conviene definir el disco desde DESQVIEW, aunque si se anula de nuevo antes de
abandonar DESQVIEW no habrá problemas, por lo que TURBODSK sí permite modificar el disco desde el
interior de este entorno.
Tanto Init como Main leen la línea de parámetros indicados por el usuario y ejecutan ordenadamente los
procedimientos necesarios para definir el disco, si ésto es preciso.
Veremos ahora con detalle algunas rutinas importantes ejecutadas durante la instalación del disco virtual.
La rutina Gestionar_ram, ejecutada sólo desde la línea de comandos del DOS, rebaja la memoria
asignada al TDSK.EXE en ejecución a 96 bytes. Esto se hace así para poder utilizar después las funciones
estándar del sistema para asignar memoria. Esta acrobacia provoca la creación de un bloque de control de
memoria (MCB) en el offset 96 del PSP, lo cual es inocuo; también se libera el espacio de entorno por si
acaso se fuera a terminar residente.
Los procedimientos Errores_Dos y Errores_config comprueban algunos errores que pueden producirse
al ejecutar el programa desde la línea de comandos del DOS o desde el CONFIG. En el procedimiento
Max_sector invocado desde Errores_Dos se comprueba si el tamaño de sector indicado excede el máximo
que soporta el DOS, para lo que se utiliza la función 52h (Get List of Lists); si es así se indica al usuario que
ese tamaño de sector debe definirse previamente desde el CONFIG.
En la rutina TestWin se comprueba si Windows está activo, para evitar en ese caso una modificación del
disco por parte del usuario. Por desgracia, hay que chequear en dos interrupciones distintas las presencia de
Windows. Antes de llamar a la INT 2Fh se comprueba que esta interrupción esté apuntando a algún sitio: en
el sistema DOS 2.11 en que se probó TURBODSK esa interrupción estaba apuntando a 0000:0000 y el
ordenador se colgaba si no se tomaba esta precaución.
También desde el DOS, el procedimiento Reside_tdsk? busca la primera unidad TURBODSK residente
de todas las que puede haber en la memoria. Para ello crea una tabla con todos los dispositivos de bloque del
sistema (rutina Lista_discos) y empieza a buscar desde el final hacia atrás (se trata de encontrar la primera
unidad TURBODSK y no la última). Alternativamente, si se había indicado una letra de unidad, el
procedimiento Obtener_segm recorre la tabla de discos para asegurarse de que esa letra de unidad es un
dispositivo TURBODSK, así como para anotar la dirección donde reside.
La rutina Inic_letra, ejecutada desde el CONFIG, calcula la letra que el sistema asignará a la unidad, con
objeto de informar en el futuro al usuario. Desde el DOS 3.0, el encabezamiento de petición de solicitud de la
orden INIT almacena este dato. Dado que DR-DOS 6.0 no inicializa correctamente el tamaño del
encabezamiento de solicitud de esta orden, es más seguro verificar la versión del DOS que comprobar si este
dato está definido o no, en función de las longitudes, que sería lo normal. En el caso del DOS 2.X, no hay
más remedio que crear una tabla con los dispositivos de bloque del sistema y contarlos (¿a que ya sabe por
qué RAMDRIVE y VDISK no informan o informan incorrectamente de la letra de unidad al instalarse en
estas versiones del DOS?).
El procedimiento Lista_discos, como dije con anterioridad, crea una tabla con todos los dispositivos de
bloque del sistema. Para ello utiliza la valiosa función indocumentada 52h (Get List of Lists) del DOS. Por
desgracia, la manera de acceder a la cadena de controladores de dispositivo varía según la versión del DOS,
por lo que TURBODSK tiene en cuenta los tres casos posibles (DOS 2.X, 3.0 y versiones posteriores). En la
tabla creada, con cuatro bytes por dispositivo: los dos primeros indican el segmento donde reside, el segundo
el número de unidades que controla y el tercero puede valer 1 ó 0 para indicar si se trata de una unidad
26 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
TURBODSK o no. El final de la tabla se delimita con un valor de segmento igual a cero. En el caso de un
dispositivo TURBODSK no se anota el segmento donde reside sino la variable cs_tdsk del mismo, que
indica la dirección real incluso en el caso de que el dispositivo haya sido relocalizado por QEMM a la
memoria superior.
La rutina Desinstala libera la memoria que ocupa un disco residente con anterioridad, inhabilitando el
driver. En el caso de la memoria convencional hay que liberar tanto el segmento que ocupaba el disco como
el del PSP previamente residente.
El procedimiento Mem_info evalúa la memoria disponible en el sistema y toma la decisión de qué tipo y
cantidad de la misma va a ser empleada. En principio se procura utilizar la memoria que el usuario indica. De
lo contrario, por defecto se intenta emplear, en este orden, memoria extendida, expandida o convencional. En
el caso de que no haya suficiente memoria se rebaja la cantidad solicitada, generándose un mensaje de
advertencia. Si no se indica el tipo de memoria, en el caso de no haber la suficiente extendida (aunque haya
algo) se utiliza la expandida, pero el recurso a la memoria convencional se evita siempre. A la memoria
expandida se le asigna menos prioridad que a la extendida debido a que, en equipos 386 y superiores,
normalmente es memoria extendida que emula por software la expandida: suele ser más rápido dejar
directamente al controlador XMS la tarea de realizar las transferencias de bloques de memoria. El
procedimiento Mem_info se apoya en tres subrutinas que calculan la cantidad disponible de cada tipo de
memoria, despreciando longitudes inferiores a 8 Kb que es el tamaño mínimo del disco. La subrutina
Eval_xms chequea la presencia de un controlador de memoria extendida; sin embargo, antes de llamar a
INT 2Fh se toma una vez más la precaución de comprobar que esta interrupción está apuntado a algo. La
subrutina Eval_ems detecta la presencia del controlador de memoria expandida buscando un dispositivo
"EMMXXXX0". El método ordinario suele ser intentar abrir ese dispositivo y después comprobar por
IOCTL que no se trata de un fichero con ese nombre; sin embargo, los controladores de dispositivo
invocados desde el CONFIG.SYS no deben acceder a las funciones IOCTL, por lo que se utiliza el algoritmo
alternativo de comprobar si esa cadena está en el offset 10 del vector 67h. En esta subrutina se comprueba
además la versión del controlador: en la 4.0 y posterior hay que buscar, recuérdese, dos páginas de memoria
expandida (una de ellas la 0) que disten entre sí 32 Kb. Finalmente, la subrutina Eval_con determina la
memoria convencional disponible. Al principio le solicita casi 1 Mb al DOS, con objeto de que éste falle e
indique cual es la cantidad máxima de memoria disponible. Seguidamente se procede a pedir justo esa
memoria, para que el DOS devuelva el segmento en que está disponible, volviéndose a liberarla
inmediatamente a continuación. Al final, al tamaño de ese bloque de memoria se le restan 128 Kb ya que, con
memoria convencional, hay que tener la precaución de no ocuparla toda y dejar algo libre. Además, en esos
128 Kb que se perdonan será preciso que TDSK.EXE se autoreubique antes de formatear el disco, como
veremos después. Con MS-DOS 5.0 se puede crear un disco virtual en memoria superior, cargando
TDSK.EXE con el comando LOADHIGH: sin embargo, hay que pedir sólo exactamente la cantidad de
memoria superior disponible en la máquina (o algo menos); de lo contrario el DOS asignará memoria
convencional para satisfacer la demanda: dado que normalmente hay más memoria convencional libre que
superior, no será preciso solicitar en estos casos, afortunadamente, 128 Kb de menos para lograr que sea
asignada memoria superior (TDSK.EXE se autorelocalizará hacia la memoria convencional y permitirá
emplear toda la memoria superior libre que quede).
La subrutina Adaptar_param es una pieza clave dentro del programa: aquí se decide qué parte del disco
27 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
va a ocupar el directorio, la FAT, el tipo de FAT, etc. Se toman valores por defecto o, en caso contrario, los
que el usuario haya indicado, considerando todas las posibilidades de error. TURBODSK permite un elevado
grado de libertad. Por ejemplo, es factible definir un directorio raíz que consuma la mitad de la capacidad del
disco, clusters de hasta 31 Kbytes... evidentemente, los valores que TURBODSK asigna por defecto suelen
ser bastante más operativos; pero en principio hay, como se dijo, libertad total para las decisiones del usuario.
En el caso de versiones 2.X del sistema se establece un tamaño de cluster por defecto tal que nunca sea
necesaria una FAT de 16 bits (no soportada por estas versiones). El algoritmo para determinar el tipo de
FAT del disco consiste en considerar el número de sectores libres que quedan después de descontar el sector
de arranque y el directorio raíz. Teniendo en cuenta el tamaño de cluster en bytes y que la FAT de 12 bits
añade 1,5 bytes adicionales para cada cluster, se aplica esta fórmula:
que devuelve el número de cluster más alto del disco (se añade uno ya que los clusters se numeran desde
dos; por ejemplo, 100 clusters se numerarían entre 2 y 101 inclusive). Si el resultado es mayor o igual que
4086, la FAT no puede ser de 12 bits, por lo que se debe recalcular la fórmula sustituyendo el 1,5 por 2 y
definiendo una FAT de 16 bits. Hay casos críticos en que una FAT de 12 bits no alcanza, pero al definirla de
16 el tamaño adicional que ella misma ocupa hace que el número de cluster más alto baje de 4086: en estos
casos se reserva espacio para una FAT de 16 bits que luego será realmente de 12; sin embargo, se trata de
una circunstancia muy puntual y poco probable. En principio, con los tamaños de cluster y sector que
TURBODSK asigna por defecto, la FAT será de 12 bits a menos que el disco exceda los 8 Mb.
Conviene hacer hincapié en que los discos con 4085 clusters o más (con número de cluster más alto 4086
o superior) tienen una FAT de 16 bits. Por desgracia, casi todos los libros consultados (y ya es mala suerte)
tienen esta información incorrecta: para unos, la FAT16 empieza a partir de 4078 clusters; para otros, a partir
de 4086; otros, no distinguen entre nº de clusters y nº más alto de cluster... hay un auténtico caos ya que las
fuentes de información se contradicen. Al final, lo más sencillo es crear discos virtuales con 4084/4085
clusters y espiar qué hace el DOS. Es muy fácil: se graban algunos ficheros y se mira la FAT con algún
programa de utilidad (PCTOOLS, DISKEDIT). A simple vista se deduce si el DOS asigna una FAT de 12 o
de 16 bits. Tanto el MS-DOS 3.1 como el 3.3, 4.0 y 5.0; así como el DR-DOS 3.41, 5.0 y 6.0 asignan
FAT's de 16 bits a partir de 4085 clusters inclusive. Por fortuna, todas las versiones del DOS parecen
comportarse igual. Asignar el tipo de FAT correcto es vital por muchos motivos; entre otros por que si fuera
excesivamente pequeña el disco funcionaría mal. Sin embargo, los CHKDSK de casi todas las versiones del
DOS (excepto el del MS-DOS 3.30 y el de DR-DOS 6.0), incluido el de MS-DOS 5.0, poseen una errata
por la que suponen que los discos de 4085 a 4087 clusters tienen una FAT de 12 bits, con lo que pueden
estropear el disco si el usuario ejecuta un CHKDSK/F. Esto es un fallo exclusivo de CHKDSK que debería
ser corregido en el futuro, por lo que no se ha evitado estos tamaños de disco (casi nadie ejecuta CHKDSK
sobre un disco virtual, y en ese caso no va a tener tan mala suerte). Resulta curioso este fallo de CHKDSK,
teniendo en cuenta que es un programa que accede a la FAT y que 4087 (0FF7h) es precisamente la marca
de cluster defectuoso en una FAT de 12 bits, ¡nunca un número de cluster cualquiera!. Por ejemplo, con un
comando del tipo TDSK 527 128 0 1 /E (no vale la memoria expandida, ya que redondearía a 528 Kb), se
puede crear un disco de 4087 clusters en el que los CHKDSK de las versiones del DOS señaladas informen
incorrectamente de la presencia de errores (si decide hacer pruebas, retoque el número de entradas del
directorio para variar ligeramente el número de clusters).
Una vez definidos los parámetros básicos de la estructura del disco, el procedimiento Preparar_bpb
inicializa el BPB, actualizándolo al nuevo disco; también se indica que ha habido cambio de disco. El
procedimiento Prep_driver se encarga de copiar el BPB recién creado sobre el del driver residente en
memoria, así como de actualizar las variables de la copia residente en memoria, copiando simplemente las del
TDSK.EXE en ejecución. También se instala la rutina necesaria para gestionar el disco, según el tipo de
memoria a emplear por el mismo: esta rutina se instala por partida doble, tanto en la copia residente como en
28 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
el propio código del TDSK.EXE que se ejecuta (la rutina de gestión de memoria será accedida directamente
al formatear el disco virtual).
En el caso de emplear memoria convencional, antes de formatear el disco hay que tomar precauciones. El
motivo radica en el hecho de que el disco probablemente comience en el offset 96 del PSP. Por tanto, si se
inicializa sin más el sector de arranque, la FAT y el directorio raíz (en eso consiste simplemente el formateo) el
propio TDSK.EXE se autodestruirá. Para evitarlo, TDSK.EXE se copia a sí mismo en esos 128 Kb libres
que siempre hay, incluso en el peor de los casos, pasando a ejecutarse en ese nuevo destino por medio de una
instrucción RETF que carga CS al retornar (procedimiento Relocalizar). Se copia todo, pila incluida (se
actualiza también SS). No habrá problemas, ya que TDSK.EXE es realmente un programa COM disfrazado
de EXE, que carece de referencias absolutas a segmentos. Se toma la precaución de relocalizar TDSK.EXE
(que no ocupa más de 12 Kb) justo a la mitad de ese área de 128 Kb, para evitar solapamientos consigo
mismo en casos críticos. Se puede llegar a sobreescribir parte de la zona transitoria del COMMAND.COM,
lo cual provoca simplemente su recarga desde disco. Ciertamente, no es muy ortodoxo que un programa en
ejecución vaya dando paseos por la memoria del PC, pero estas cosas se pueden hacer en MS-DOS y nadie
puede cuestionar la efectividad del método. Los programadores más conservadores han tenido suerte de que
el adaptador de vídeo monocromo cuente con sólo 4 Kb.
+-------------------------------------------------------------------------------------------
| ESQUEMA DE LA AUTORELOCALIZACIÓN DE TDSK.EXE (UN CASO CONCRETO)
+-------------------------------------------------------------------------------------------
|
| Casi todas las cifras son arbitrarias, a modo de ejemplo práctico.
|
|
| 1 Mb +-------------------------+ 1 Mb +-------------------------+
| | | | |
| | | | |
| | | | |
| | | | |
| 640 Kb +-------------------------+ 640 Kb +-------------------------+<-+
| | | | | |
| | | | | |
| | | aprox. 588 Kb +->+-------------------------+ |
| | | | | nueva pila de TDSK.EXE | |
| | | | + - - - - - - - - - - - - + |
| | | +--------> | | | |
| | | | | | TDSK.EXE | |
| | | | | | | |
| | | | | +-------------------------+ |
| | | | | | PSP TDSK.EXE (256 bytes)| |
| | | | 576 Kb +->+-------------------------+ |
| | | | | 64 Kb libres (área de | |
| | | | | seguridad) | |
| | | | 512 Kb +-------------------------+<-+
| | . . . | | | . . . |
| . . . | . . .
| . . . | . . .
| | | | | |
| +-------------------------+<-+ | | Futuros programas |
| | pila de TDSK.EXE | | | +-------------------------+
| + - - - - - - - - - - - - + | | | |
| | | | ----+ | Área de almacenamiento |
| | TDSK.EXE | | | del disco virtual |
| | | | | |
| +-------------------------+ | +-------------------------+
| | PSP TDSK.EXE (256 bytes)| | | PSP TDSK.EXE (96 bytes) |
| +-------------------------+<-+ +-------------------------+
| | DOS/BIOS | | DOS/BIOS |
29 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
| 0 Kb +-------------------------+ 0 Kb +-------------------------+
| Antes Después
|
|
| En este esquema se muestra la autorelocalización de TDSK.EXE en memoria en el c
| definirse el disco en memoria convencional. No están reflejados los bloques de cont
| memoria ni otros detalles. Si la memoria está suficientemente fragmentada (por haber ins
| programas residentes tras definir algún disco) puede que no fuera estrictamente nec
| respetar 128 Kb al final del bloque que nos asigna el DOS ni tampoco quizá relocalizar TD
| sin embargo, el programa no está optimizado hasta ese extremo. El hecho de relocaliza
| hacia la frontera de los 576 Kb en lugar de los 512 se debe a evitar problemas de colisio
| casos críticos de cantidad de memoria libre y tamaño de disco solicitado por el usuario.
+-------------------------------------------------------------------------------------------
Hablando de acceso directo al disco, otra ventaja de no utilizar INT 25h/INT 26h es que Windows 95 no
permite un uso directo de estas funciones. Los programas que acceden a estas interrupciones son
considerados inadecuados. TURBODSK puede funcionar bajo Windows 95, sin obligar al usuario a
reconfigurar nada, gracias entre otros motivos a que no utiliza INT 26h.
Con MS-DOS 2.11 y 3.1 hubo bastantes problemas, ya que estos sistemas no detectan muy bien el
cambio de disco aunque la rutina MEDIA CHECK del controlador de dispositivo se lo indique: son versiones
del DOS muy desconfiadas que además comprueban el byte descriptor de medio. Es de suponer que cuando
el disco informa que ha habido cambio, estas versiones invalidarán los buffers asociados a él; sin embargo, si
creen que se trata de un disco del mismo tipo no se molestan en actualizar el BPB. Por ello, con estas
versiones, tras el formateo TURBODSK hace dos cambios de disco consecutivos, con modificación del byte
descriptor de medio entre ambos. El hecho de hacer un segundo cambio se debe al interés de restaurar el
byte descriptor de medio inicial. Además, el DOS 2.11 probado necesitaba dos cambios en cualquier caso: si
no, no se tomaba en serio el cambio de disco. Entre cambio y cambio, se pregunta al sistema el espacio libre
en disco para forzar un acceso al mismo.
AMPLIACIONES DE TURBODSK
Después de esta completa exposición sobre las rutinas que componen TURBODSK, espero que el lector
esté suficientemente preparado para entender en conjunto el funcionamiento del programa y para crear
unidades de disco por su cuenta. Una posible mejora de TURBODSK sería evitar la pérdida de datos al
redefinir el disco, tratándose por ejemplo de aumentar su capacidad. Es complejo añadir esta optimización, ya
que la arquitectura del nuevo disco puede cambiar demasiado (nuevo tamaño de FAT e incluso tipo de la
misma). Además, el usuario iba a tener muchos problemas siempre, ya que sería muy frecuente que cuando
30 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/11.html
tratase de reducir el tamaño del disco éste estuviera demasiado lleno. En general, los discos virtuales
redimensionables que soportan una redefinición sin pérdida de datos, suelen permitir esto de manera limitada
y bajo circunstancias concretas. Lo que sí sería más interesante es crear un disco virtual con asignación de
memoria en tiempo real: cuando el usuario pretende crear un fichero, habilitar el espacio suficiente. Sin
embargo, esto significa unir las complicaciones anteriores a otras nuevas, complicaciones que restarían
velocidad al disco virtual, además de la dificultad de implementarlas que desanima al programador más audaz.
Por otra parte, no está muy claro que el MS-DOS sea un sistema adecuado para soportar tal disco: al final, el
proyecto podría quedar descartado en la fase de análisis (si es que alguien acepta el reto).
Una vez instalado el controlador de dispositivo, puede ser necesario para los programas del usuario
interaccionar con él. Para ello se ha definido oficialmente un mecanismo de comunicación: el control IOCTL.
En principio, un controlador de dispositivo puede ser hallado recorriendo la cadena de controladores de
dispositivo para localizarlo y acceder directamente a su código y datos. Sin embargo, en los controladores
más evolucionados, el método IOCTL es el más recomendable.
El control IOCTL (que permite separar el flujo de datos con el dispositivo de la información de control) se
ejerce por medio de la función 44h del DOS, siendo posible lo siguiente:
- Averiguar los atributos de un controlador de dispositivo, a partir del nombre. Esto permite, entre otras
cosas, distinguir entre un dispositivo real y un fichero con el mismo nombre. Seguro que el lector ha construido
alguna vez un programa que abre un fichero de salida de datos con el nombre que indica el usuario: hay
usuarios muy pillines que en lugar del clásico PEPE.TXT prefieren indicar, por ejemplo, CON, estropeando la
bonita pantalla que tanto trabajo había costado pintar. Una solución consiste, antes de abrir el fichero de
salida, en asegurarse de que es realmente un fichero.
- Leer del controlador o enviarle una tira de caracteres de control. Esto sólo es posible si el controlador
soporta IOCTL. Por ejemplo, un driver encargado de gestionar un puerto serie especial podría admitir
cadenas del tipo "9600,n,8,1" para fijar la velocidad de transmisión, paridad, etc. El trabajo que requiere
codificar la rutina IOCTL OUTPUT, encargada de recibir estos datos, puede en muchos casos merecer la
pena.
- Averiguar el estado del controlador: saber si tiene caracteres disponibles, o si ya ha transmitido el último
enviado. Esta característica, entre otras, es implementada por la orden IOCTL INPUT del controlador.
Para obtener información detallada acerca de la función 44h del DOS hay que consultar, lógicamente, la
bibliografía al respecto (recomendable el INTERRUP.LST).
31 de 31 12/10/00 19:14
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/1201.html
En este capítulo se mostrará detenidamente el funcionamiento de todos los chips importantes que lleva el
ordenador en la placa base y alguno de los colocados en las tarjetas de expansión.
Nota: Por limitaciones técnicas, al describir los circuitos integrados las señales que son activas
a nivel bajo no tendrán la tradicional barra negadora encima; en su lugar aparecerán precedidas
del signo menos: -CS, -WR, -MEMR, ...
En algunos casos, acceder directamente a los chips no es necesario: en general, es mejor dejar el trabajo
al DOS, o en su defecto a la BIOS. Sin embargo, hay casos en que es estrictamente necesario hacerlo: por
ejemplo, para programar temporizaciones, hacer sonidos, comunicaciones serie por interrupciones, acceso a
discos de formato no estándar, etc. Algunas veces bastará con la información que aparece en el apartado
donde se describe la relación del chip con los PC; sin embargo, a menudo será necesario consultar la
información técnica del apartado ubicado inmediatamente antes, para lo que bastan unos conocimientos
razonables de los sistemas digitales. Los ordenadores modernos normalmente no llevan los integrados
explicados en este capítulo; sin embargo, poseen circuitos equivalentes que los emulan por completo.
Resulta interesante tener una idea global de las conexiones del 8086 con el exterior de cara a entender
mejor la manera en que interacciona con el resto de los elementos del ordenador. Se ha elegido el 8088 por
ser el primer procesador que tuvo el PC; a efectos de entender el resto del capítulo es suficiente con el 8088.
El 8088 puede trabajar en dos modos: mínimo (pequeñas aplicaciones) y máximo (sistemas
multiprocesador). Los requerimientos de conexión con el exterior cambian en función del modo que se
decida emplear, aunque una parte de las señales es común en ambos.
1 de 3 12/10/00 19:15
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/1201.html
Request/Grant. Estas patillas bidireccionales permiten a los demás procesadores conectados al bus forzar al
-RQ/-GT0..1:
8088 a que libere el bus al final del ciclo en curso.
Lock. Línea que sirve al 8088 para prohibir el acceso al bus a otros procesadores (se activa tras la
instrucción máquina LOCK y dura mientras se ejecuta la siguiente instrucción -la que sigue a LOCK, que es
-LOCK:
realmente un prefijo-). También se activa automáticamente en los momentos críticos de un ciclo de
interrupción.
QS1/QS0: Queue Status. Permite determinar el estado de la cola de instrucciones del 8088.
El 8086 cambia el patillaje sensiblemente, aunque la mayoría de las señales son similares. En lugar de 8
líneas de datos y direcciones multiplexadas (AD0..7) el 8086 posee 16, ya que el bus de datos es de 16 bits.
2 de 3 12/10/00 19:15
CONTROLADORES DE DISPOSITIVO file:///C|/librosVirtuales/UniversoDigital/1201.html
Existe una línea especialmente importante en el 8086, -BHE/S7 (Bus High Enables/Status), que normalmente
indica si se accede a la parte alta del bus de datos o no (operaciones 8/16 bits). El 8086 posee una cola de
instrucciones de 6 bytes, en lugar de 4.
Resulta absurdo estudiar la composición binaria de las instrucciones máquina de ningún procesador; en los
casos en que sea necesario se pueden ver los códigos con alguna utilidad de depuración. Sin embargo, a título
de curiosidad, se expone a continuación el formato general de las instrucciones (aunque hay algunas
excepciones y casos especiales).
El código de operación ocupa 6 bits; el bit D indica si es el operando fuente (=0) el que está en el
campo registro (REG) o si lo es el operando destino (=1): la razón es que el 8086 sólo admite un operando a
memoria, como mucho (o el fuente, o el destino, no los dos a la vez). El bit W indica el tamaño de la
operación (byte/palabra). MOD indica el modo de direccionamiento: 00-sin desplazamiento (no existe
campo de desplazamiento), 01-desplazamiento de 8 bits, 10-desplazamiento de 16 bits y 11-registro (tanto
fuente como destino están en registro). El campo REG indica el registro involucrado en la instrucción, que
puede ser de 8 ó 16 bits (según indique W): 0-AX/AL, 1-CX/CL, 2-DX/DL, 3-BX/BL, 4-SP/AH, 5-BP/CH,
6-SI/DH, 7-DI/BH; en el caso de registros de segmento sólo son significativos los dos bits de menor peso:
00-ES, 01-CS, 10-SS, 11-DS. El campo R/M, en el caso de modo registro (MOD=11) se codifica igual
que el campo REG; en caso contrario se indica la forma en que se direcciona la memoria: 0: [BX+SI+desp],
1: [BX+DI+desp], 2: [BP+SI+desp], 3: [BP+DI+desp], 4: [SI+desp], 5: [DI+desp], 6: [BP+desp], 7:
[BX+desp].
3 de 3 12/10/00 19:15
EL INTERFAZ DE PERIFÉRICOS 8255 file:///C|/librosVirtuales/UniversoDigital/1202.html
El PPI 8255 es un dispositivo de E/S general, programable, capaz de controlar 24 líneas con diferentes
configuraciones (entrada/salida) y en hasta 3 modos de operación.
DESCRIPCIÓN FUNCIONAL
Las dos líneas de direcciones definen cuatro puertos de E/S en el ordenador: los tres
primeros permiten acceder a los puertos A, B y C; el cuarto sirve para leer o escribir la
palabra de control. El 8255 está dividido en dos grupos internos: el grupo A, formado por
el puerto A y los 4 bits más significativos del puerto C; y el grupo B, constituido por el
puerto B junto a los 4 bits menos significativos del puerto C. El puerto C está
especialmente diseñado para ser dividido en dos mitades y servir de apoyo a los puertos A y B en algunos sistemas.
El 8255 soporta 3 modos de operación: el modo 0 (entrada y salida básica), el modo 1 (entrada y salida con señales de
control) y el modo 2 (bus bidireccional de comunicaciones). Tras un Reset, los 3 puertos quedan configurados en modo
entrada, con las 24 líneas puestas a "1" gracias a la circuitería interna. Esta configuración por defecto puede no obstante ser
alterada con facilidad. El modo para el puerto A y B se puede seleccionar por separado; el puerto C está dividido en dos
mitades relacionadas con el puerto A y el B. Todos los registros de salida son reseteados ante un cambio de modo,
incluyendo los biestables de estado. Las configuraciones de modos son muy flexibles y se acomodan a casi todas las
necesidades posibles. Los tres puertos pueden ser accedidos en cualquier momento a través de la dirección E/S que les
corresponde, como se vio en el apartado anterior. La palabra de control a enviar a la 4ª dirección es:
Si el bit más significativo de la palabra de control está borrado, es tratada entonces como un comando especial que
permite activar o inhibir selectivamente los bits del puerto C:
1 de 3 12/10/00 19:15
EL INTERFAZ DE PERIFÉRICOS 8255 file:///C|/librosVirtuales/UniversoDigital/1202.html
Esto es particularmente útil para los modos 1 y 2, donde las interrupciones generadas por las líneas del puerto C pueden
ser activadas o inhibidas simplemente poniendo a 1 ó 0, respectivamente, el flip-flop interno INTE correspondiente a la
interrupción que se trate. Todos son puestos a cero tras establecer el modo.
El 8255 es exclusivo de los PC/XT; ha sido eliminado de la placa base de los AT y PS/2, en los que
ciertos registros realizan algunas funciones que en los PC/XT realiza el 8255; por ello, en estas máquinas NO
se puede programar el 8255 (ha sido eliminado y no existe nada equivalente). El 8255 de los PC/XT está
conectado a la dirección base E/S 60h; por ello, los puertos A, B y C se acceden, respectivamente, a través
de los puertos de E/S 60h, 61h y 62h; la palabra de control se envía por el puerto 63h: la BIOS del PC y XT
programa el 8255 con una palabra de control 10011001b, que configura todos los puertos en el modo 0, con
el A y C de entrada y el B de salida. El 8255 es empleado, básicamente, para almacenar los datos que llegan
del teclado (puerto A), para leer la configuración del ordenador en los conmutadores de la placa base (puerto
C) y para controlar el altavoz y la velocidad en los XT-Turbo (puerto B).
Aviso: los PC tienen un byte de identificación 0FFh; los XT 0FEh (este byte está en la posición de
memoria 0FFFF:0Eh); por otro lado, parte de esta información es accesible también por medio de la variable
BIOS ubicada en 40h:10h, método mucho más recomendable.
Puerto A (60h): tiene una doble función: cuando el bit 7 del puerto B está a 1, el puerto A recibe el
código de rastreo de la tecla pulsada, que luego puede ser leído desde la interrupción del teclado. Si el bit 7
del puerto B está a 0, entonces el puerto A devuelve información sobre la configuración del sistema en los PC
(no en los XT): en el bit 0 (a 1 si hay disqueteras), bits 2..3 (número de bloques de 16 kb de memoria ¡que
obsoleto e inútil!), bits 4..5 (tipo de pantalla: 11 MDA, 10 Color 80x25, 01 Color 40x25) y bits 6..7 (número
2 de 3 12/10/00 19:15
EL INTERFAZ DE PERIFÉRICOS 8255 file:///C|/librosVirtuales/UniversoDigital/1202.html
Puerto B (61h): bit 0 (PC/XT: conectado a la línea GATE del contador 2 del 8253), bit 1 (PC/XT:
conectado al altavoz), bit 2 (sólo PC: selecciona el contenido del puerto C), bit 3 (en XT: selecciona
contenido del puerto C; en PC: a 0 para activar el motor del casete), bit 4 (PC/XT: a 0 para activar la RAM),
bit 5 (PC/XT: a 0 para activar señales de error en el slot de expansión), bit 6 (PC/XT: a 1 activa la señal de
reloj del teclado), bit 7 (en PC: empleado para seleccionar la función del puerto A; tanto en PC como en XT
sirve además para enviar una señal de reconocimiento al teclado).
Puerto C (62h):
Si el bit 2 del puerto B (PC) o el bit 3 del puerto B (XT) están a 1:
- En los PC: los bits 0..3: mitad inferior del 2º banco de conmutadores de la placa base (RAM en slots de
expansión); bit 4 (entrada de casete).
- En los XT: bit 1 (activo si coprocesador instalado), bits 2..3 (bancos de RAM en placa base).
- En PC/XT: bit 5 (OUT del contador 2 del 8253), bit 6 (a 1 si comprobar errores en slots de expansión), bit
7 (1 si comprobar error de paridad).
Si el bit 2 del puerto B (PC) o el bit 3 del puerto B (XT) están a 1:
- En los PC: bits 0..3 parte alta del segundo banco de conmutadores de configuración (no usada).
- En los XT: bits 0..1 tipo de pantalla (11 MDA, 10 color 80x25, 01 color 40x25), bits 2..3 (nº de
disqueteras menos 1).
- En PC/XT: los bits 4..7 están igual que en el caso anterior (no dependen del bit 2 ó 3 del puerto B).
3 de 3 12/10/00 19:15
EL TEMPORIZADOR 8253 U 8254 file:///C|/librosVirtuales/UniversoDigital/1203.html
El 8253/4 es un chip temporizador que puede ser empleado como reloj de tiempo real, contador de
sucesos, generador de ritmo programable, generador de onda cuadrada, etc. En este capítulo, la información
vertida estará relacionada con el 8254 que equipa a los AT, algo más potente que el 8253 de los PC/XT; sin
embargo, las pocas diferencias serán comentadas cuando llegue el caso.
Este circuito integrado posee 3 contadores totalmente independientes, que pueden ser programados de 6
formas diferentes.
D7..D0: BUS de datos bidireccional de 3 estados.
CLK 0: CLOCK 0, entrada de reloj al contador 0.
OUT 0: Salida del contador 0.
GATE 0: Puerta de entrada al contador 0.
CLK 1: CLOCK 1, entrada de reloj al contador 1.
OUT 1: Salida del contador 1.
GATE 1: Puerta de entrada al contador 1.
CLK 2: CLOCK 2, entrada de reloj al contador 2.
OUT 2: Salida del contador 2.
GATE 2: Puerta de entrada al contador 2.
Líneas de dirección para seleccionar uno de los tres contadores o el
A0..A1:
registro de la palabra de control.
-CS: Habilita la comunicación con la CPU.
-WR: Permite al 8254 aceptar datos de la CPU.
-RD: Permite al 8254 enviar datos a la CPU.
DESCRIPCIÓN FUNCIONAL
El diagrama funcional del 8254, con la estructura interna de las diversas partes que lo componen, se muestra a la izquierda.
A la derecha, diagrama de los bloques internos de un contador:
1 de 14 12/10/00 19:16
EL TEMPORIZADOR 8253 U 8254 file:///C|/librosVirtuales/UniversoDigital/1203.html
lectura continuarán siguiendo a CE. La lógica de control del contador se encarga de que un sólo latch esté activo a un
tiempo, ya que el bus interno del 8254 es de 8 bits. CE no puede ser nunca leído directamente (lo que se lee es OL). De
manera análoga, existen un par de registros CRM y CRL (CR significa Count Register) que almacenan la cuenta del contador
y se la transmiten convenientemente a CE. Los valores de cuenta se escriben siempre sobre CR (y no directamente sobre
CE). La lógica de control gestiona la conexión con el exterior a través de las líneas CLK, GATE y OUT.
DESCRIPCIÓN OPERACIONAL
Tras el encendido del ordenador, el 8254 está en un estado indefinido; con un modo, valor de cuenta y estado de salida
aleatorios. Es entonces cuando hay que programar los contadores que se vayan a emplear; el resto, no importa dejarlos de
cualquier manera.
Para programar un contador del 8254 hay que enviar primero una palabra de control y, después, un valor de cuenta inicial.
Los contadores se seleccionan con las líneas A0 y A1; el valor A0=A1=1 selecciona la escritura de la palabra de control (en
la que se identifica el contador implicado). Por tanto, el 8254 ocupa normalmente 4 direcciones de E/S consecutivas ligadas a
los contadores 0, 1, 2 y al registro de la palabra de control. Para enviar la cuenta inicial se utiliza simplemente el puerto E/S
ligado al contador que se trate. El formato de la palabra de control es:
Operaciones de escritura.
El 8254 es muy flexible a la hora de ser programado. Basta con tener en cuenta dos cosas: por un lado, escribir siempre
primero la palabra de control, antes de enviar la cuenta inicial al contador. Por otro, dicha cuenta inicial debe seguir
exactamente el formato seleccionado en la palabra de control (enviar sólo byte bajo, enviar sólo byte alto, o bien enviar
ambos consecutivamente). Teniendo en cuenta que cada contador tiene su propio puerto y que la palabra de control indica
el contador al que está asociada, no hay que seguir un orden especial a la hora de programar los contadores. Esto significa
que, por ejemplo, se puede enviar la palabra de control de cada contador seguida de su cuenta inicial, o bien enviar todas las
palabras de control para los 3 contadores y después las 3 cuentas iniciales; también es válida cualquier combinación
intermedia de estas secuencias (por ejemplo: enviar la palabra de control para el contador 0, después la palabra de control
para el contador 1, después la parte baja de la cuenta para el contador 0, luego la parte baja de la cuenta para el contador 1, la
parte alta de la cuenta para el contador 0, etc...).
Un nuevo valor de cuenta inicial puede ser almacenado en un contador en cualquier momento, sin que ello afecte al modo
en que ha sido programado (el resultado de esta operación dependerá del modo, como se verá más adelante). Si se programa
el contador para leer/escribir la cuenta como dos bytes consecutivos (bajo y alto), el sentido común indica que entre ambos
envíos/recepciones no conviene transferir el control a una subrutina que utilice ese mismo contador para evitar un resultado
incorrecto.
Operaciones de lectura.
Existen tres posibles métodos para leer el valor de un contador en el 8254. El primero es el comando Read-Back, sólo
disponible en el 8254 (y no en el 8253), como luego veremos. El segundo consiste en leer simplemente el contador
accediendo a su puerto correspondiente: este método requiere inhibir la entrada CLK al contador (por ejemplo, a través de la
línea GATE o utilizando circuitería exterior de apoyo) con objeto de evitar leer la cuenta en medio de un proceso de
actualización de la misma, lo que daría un resultado incorrecto. El tercer método consiste en el comando de enclavamiento.
2 de 14 12/10/00 19:16
EL TEMPORIZADOR 8253 U 8254 file:///C|/librosVirtuales/UniversoDigital/1203.html
Este comando se envía cual si de una palabra de control se tratara (A1=A0=1): para diferenciarlo de ellas los bits 5 y 4
están a cero. En los bits 7 y 6 se indica el contador afectado. Los demás bits deben estar a cero para compatibilizar con
futuras versiones del chip. Cuando se envía el comando, el OL del contador seleccionado queda congelado hasta que la
CPU lo lee, momento en el que se descongela y pasa de nuevo a seguir a CE. Esto permite leer los contadores al vuelo sin
afectar la cuenta en curso. Se pueden enviar varios de estos comandos a los diversos contadores, cuyos OL's quedarán
enclavados hasta ser leídos. Si se envían varios comandos de enclavamiento al mismo contador, separados por un cierto
intervalo de tiempo, sólo se considerará el primero (por tanto, la cuenta leída corresponderá al valor del contador cuando fue
enclavado por vez primera).
Por supuesto, el contador debe ser leído utilizando el formato que se definió al enviar la palabra de control; aunque en el
caso de leer 16 bits, las dos operaciones no han de ser necesariamente consecutivas (se pueden insertar en el medio otras
acciones relacionadas con otros contadores).
Otra característica interesante (¿disponible tal vez sólo en el 8254?) consiste en la posibilidad de mezclar lecturas y
escrituras del mismo contador. Por ejemplo, si ha sido programado para cuentas de 16 bits, es válido hacer lo siguiente: 1)
leer el byte menos significativo, 2) escribir el nuevo byte menos significativo, 3) leer el byte más significativo, 4) escribir el
nuevo byte más significativo.
Comando Read-Back.
Sólo está disponible en el 8254, no en el 8253. Este comando permite leer el valor actual de la cuenta, así como averiguar
también el modo programado para un contador y el estado actual de la patilla OUT, además de verificar el banderín de cuenta
nula (Null Count) de los contadores que se indiquen. El formato del comando Read-Back es el siguiente:
El comando Read-Back permite enclavar la cuenta en varios OL's de múltiples contadores de una sola vez, sin requerir
múltiples comandos de enclavamiento, poniendo el bit 5 a cero. Todo funciona a partir de aquí como cabría esperar (los
contadores permanecen enclavados hasta ser leídos, los que no son leídos permanecen enclavados, si el comando se reitera
sólo actúa la primera vez reteniendo la primera cuenta...). También es posible enviar información de estado al latch OL,
enclavándola para que puede ser leída con comodidad por el puerto que corresponda a ese contador. La palabra de estado
tiene el siguiente formato:
En D0..D5 se devuelve justo la misma información que se envió en la última palabra de control; en el bit D7 se entrega el
estado actual de la patilla OUT del 8254, lo que permite monitorizar por software las salidas del temporizador economizando
hardware en ciertas aplicaciones. El bit NULL COUNT (D6) indica cuándo la última cuenta escrita en CR ha sido transferida a
CE: el momento exacto depende del modo de funcionamiento del contador. Desde que se programa un nuevo valor de
cuenta, pasa un cierto tiempo hasta que éste valor pasa de CR a CE: leer el contador antes de que se haya producido dicha
transferencia implica leer un valor no relacionado con la nueva cuenta. Por ello, según las aplicaciones, puede llegar a ser
necesario esperar a que NULL COUNT alcance el valor 0 antes de leer. El funcionamiento es el siguiente:
3 de 14 12/10/00 19:16
EL TEMPORIZADOR 8253 U 8254 file:///C|/librosVirtuales/UniversoDigital/1203.html
Operación Consecuencias
A -Escribir al registro de la palabra de control (1) NULL COUNT = 1
B -Escribir al registro contador (CR) (2) NULL COUNT = 1
C -Nueva cuenta cargada en CE (CR ->CE) NULL COUNT = 0
Notas:
(1) Sólo el contador especificado por la palabra de control tiene su NULL COUNT a 1; los
demás contadores, lógicamente, no ven afectado su correspondiente bit NULL COUNT.
(2) Si el contador es programado para cuentas de 16 bits, NULL COUNT pasa a valer 1
inmediatamente después de enviar el segundo byte.
Si se enclava varias veces seguidas la palabra de estado, todas serán ignoradas menos la primera, por lo
que el estado leído será el correspondiente al contador en el momento en que se enclavó por vez primera la
palabra de estado.
Se pueden enclavar simultáneamente la cuenta y la palabra de estado (en un comando Read-Back con
D5=D4=0), lo que equivale a enviar dos Read-Back consecutivos. En este caso, y con independencia de
quién de los dos hubiera sido enclavado primero, la primera lectura realizada devolverá la palabra de estado y
la segunda la cuenta enclavada (que automáticamente quedará de nuevo desenclavada).
Si se escribe una nueva cuenta mientras GATE=0, ésta será cargada en cualquier caso en el siguiente pulso
del reloj: cuando GATE suba, OUT se pondrá en alto tras N pulsos del reloj (y no N+1 en este caso).
4 de 14 12/10/00 19:16
EL TEMPORIZADOR 8253 U 8254 file:///C|/librosVirtuales/UniversoDigital/1203.html
GATE provoca la carga del contador (CR -< CE) y que OUT baje en el próximo pulso del reloj,
comenzando el pulso One-Shot de N ciclos de reloj de duración; el contador vuelve a ser recargado si se
produce un nuevo flanco de subida de GATE, de ahí que OUT permanezca en bajo durante N pulsos de reloj
tras la última vez que suceda esto. El pulso One-Shot puede repetirse sin necesidad de recargar el contador
con el mismo valor. GATE no influye directamente en OUT.
Si se escribe una nueva cuenta durante un pulso One-Shot, el One-Shot en curso no resulta afectado, a
menos, lógicamente, que se produzca un nuevo flanco de subida de GATE: en ese caso, el contador sería
recargado con el nuevo valor.
5 de 14 12/10/00 19:16
EL TEMPORIZADOR 8253 U 8254 file:///C|/librosVirtuales/UniversoDigital/1203.html
con ese nuevo valor de cuenta inicial tras el próximo pulso del reloj y volverá a comenzar, en caso contrario
se recargará con el nuevo valor tras finalizar con normalidad el medio-ciclo en curso.
Para valores de cuenta impares, la duración a nivel alto de OUT será un período de reloj mayor que la
duración a nivel bajo.
6 de 14 12/10/00 19:16
EL TEMPORIZADOR 8253 U 8254 file:///C|/librosVirtuales/UniversoDigital/1203.html
Todos los AT y PS/2 llevan instalado un 8254 o algo equivalente; los PC/XT van equipados con un 8253,
algo menos versátil; los PS/2 más avanzados tienen un temporizador con un cuarto contador ligado a la
interrupción no enmascarable, si bien no lo consideraremos aquí. Todos los contadores van conectados a un
reloj que oscila a una frecuencia de 1.193.180 ciclos por segundo (casi 1,2 Mhz). La dirección base en el
espacio de E/S del ordenador elegida por IBM cuando diseñó el PC es la 40h. Por tanto, los tres contadores
son accedidos, respectivamente, a través de los puertos 40h, 41h y 42h; la palabra de control se envía al
puerto 43h.
La salida del contador 0 está conectada a IRQ 0 (ligado a la INT 8, que a su vez invoca a INT 1Ch); este
contador está programado por defecto con el valor cero (equivalente a 65536), por lo que la cadencia de los
pulsos es de 1.193.180/65.536 = 18,2 veces por segundo, valor que determina la precisión del reloj del
sistema, ciertamente demasiado baja. Se puede modificar el valor de recarga de este contador en un
programa, llamando a la vieja INT 8 cada 1/18,2 segundos para no alterar el funcionamiento normal del
ordenador, si bien no es conveniente instalar programas residentes que cambien permanentemente esta
especificación: los programas del usuario esperan encontrarse el temporizador a la habitual y poco útil
frecuencia de 18,2 interrupciones/segundo.
La salida del contador 1 controla el refresco de memoria en todas las máquinas, su valor normal para el
divisor es 18; aumentándolo se puede acelerar el funcionamiento del ordenador, con el riesgo -eso sí- de un
fallo en la memoria, detectado por los chips de paridad -si los hay-, que provoca generalmente el bloqueo del
equipo. De todas maneras, en los PC/XT se puede aumentar entre 19 y 1000 sin demasiados riesgos,
acelerándose en ocasiones hasta casi un 10% la velocidad de proceso del equipo. En los AT la ganancia de
velocidad es mucho menor y además este es un punto demasiado sensible que conviene no tocar para no
correr riesgos, aunque se podría bajar hasta un valor 2-17 para ralentizar el sistema. Sin embargo, no es
conveniente alterar esta especificación porque, como se verá más adelante, hay un método para realizar
retardos (empleado por la BIOS y algunas aplicaciones) que se vería afectado.
El contador 2 puede estar conectado al altavoz del ordenador para producir sonido; alternativamente
puede emplearse para temporizar. Es el único contador que queda realmente libre para el usuario, lo que suele
dar quebraderos de cabeza a la hora de producir sonido.
12.3.3 - TEMPORIZACIÓN.
Los contadores 0 y 1, especialmente este último, ya están ocupados por el sistema; en la práctica el único
disponible es el 2. Este contador ha sido conectado con el doble propósito de temporizar y de generar
sonido. Para emplearlo en las temporizaciones, es preciso habilitar la puerta GATE activando el bit 0 del
puerto 61h; también hay que asegurarse de que la salida del contador no está conectada al altavoz (a menos
que se desee música mientras se cronometra) poniendo a 0 el bit 1 del mismo puerto (61h):
IN AL,61h
AND AL,11111101b ; borrar bit 1 (conexión contador 2 con el altavoz)
OR AL,00000001b ; activar bit 0 (línea GATE del contador 2)
JMP SHORT $+2 ; estado de espera para E/S
7 de 14 12/10/00 19:16
EL TEMPORIZADOR 8253 U 8254 file:///C|/librosVirtuales/UniversoDigital/1203.html
OUT 61h,AL
El siguiente programa de ejemplo, CRONOS.ASM, incluye dos subrutinas para hacer retardos de alta
precisión. La primera de ellas, inic_retardo, hay que llamarla al principio para que programe el contador 2
del temporizador; la rutina retardo se encarga de hacer el retardo que se indique en AX (en unidades de
1/1193180 segundos).
; ********************************************************************
; * *
; * CRONOS.ASM - Subrutinas para hacer retardos de precisión. *
; * *
; * INIT_RETARDO: llamarla al principio del todo. *
; * RETARDO: Entregar en AX el nº de 1193180-avos de *
; * segundo que dura el retardo (máximo 65400). *
; * *
; ********************************************************************
programa SEGMENT
ASSUME CS:programa, DS:programa
ORG 100h
inicio:
CALL inic_retardo
MOV CX,20 ; 20 retardos
MOV AX,59659 ; de 50 milisegundos
retard: CALL retardo
LOOP retard
INT 20h
inic_retardo PROC
PUSH AX
IN AL,61h
AND AL,11111101b
OR AL,1
JMP SHORT $+2
OUT 61h,AL
MOV AL,10110100b ; contador 2, modo 2, binario
JMP SHORT $+2
OUT 43h,AL
POP AX
RET
inic_retardo ENDP
retardo PROC
PUSH AX
PUSH BX
CLI
OUT 42h,AL ; parte baja de la cuenta
MOV AL,AH
JMP SHORT $+2
OUT 42h,AL ; parte alta
JMP SHORT $+2
IN AL,61h
XOR AL,1 ; bajar GATE
JMP SHORT $+2
OUT 61h,AL
XOR AL,1 ; subir GATE
8 de 14 12/10/00 19:16
EL TEMPORIZADOR 8253 U 8254 file:///C|/librosVirtuales/UniversoDigital/1203.html
programa ENDS
END inicio
A la hora de emplear las rutinas anteriores hay que tener en cuenta dos consideraciones. Por un lado,
están diseñadas para hacer pequeños retardos: llamándolas repetidamente, el bucle que hay que hacer (y las
interrupciones que se producen durante el proceso) provoca que retarden más de la cuenta. Por ejemplo, en
el programa principal, poniendo 1200 en CX en lugar de 20, el retardo debería ser de 60 segundos; sin
embargo, comparando este dato con el contador de hora de la BIOS (en una versión ligeramente modificada
del programa) resulta ser de casi 60,2 segundos. La segunda consideración está relacionada con las
interrupciones: de la manera que está el listado, se puede producir una interrupción en la que algún programa
residente utilice el contador 2 del temporizador, alterando el funcionamiento de las rutinas de retardo (por
ejemplo, una utilidad de click en el teclado) o incluso provocando un fallo en la misma (si a ésta no le da
tiempo a comprobar que ya es la hora): este es un aspecto a tener en cuenta en un caso serio. Se puede, por
ejemplo, inhibir todas las interrupciones (o enmascar sólo las más molestas), aunque anular la interrupción del
temporizador, la más peligrosa, provocaría un retraso de la hora del ordenador.
9 de 14 12/10/00 19:16
EL TEMPORIZADOR 8253 U 8254 file:///C|/librosVirtuales/UniversoDigital/1203.html
18,2 veces cada segundo y de volver a ponerlo a cero cada 24 horas. No es conveniente mirar el valor del
contador de hora de la BIOS, sumarle una cantidad y esperar a que alcance dicha cantidad fija: la experiencia
demuestra que eso produce a veces cuelgues del ordenador, no solo debido a que suele fallar cuando son las
23:59:59 sino también porque cuando se alcanza el valor esperado, por cualquier motivo (tal como un
alargamiento excepcional de la rutina que controla INT 8 ó INT 1Ch debido a algún programa residente)
puede que el programa principal no llegue a tiempo para comprobar que ya es la hora... y haya que esperar
otras 24 horas a probar suerte. Lo ideal es contar las veces que cambia el contador de hora de la BIOS.
Por último, como ejemplo ameno, el siguiente fragmento de programa hace que la hora del ordenador vaya
diez veces más rápida -poco recomendable, aunque muy divertido- programando el contador 0 con un valor
de cuenta 6553 (frente al 0=65536 habitual), de la siguiente manera:
Aunque ausente en todos los manuales de referencia técnica y en todos los libros relacionados con la
programación de PC, existe un método muy fácil y eficiente para temporizar disponible en todos los
ordenadores AT. Pese a no estar documentado, un programa muy usual como es el KEYB del MS-DOS (a
partir de la versión 5.0 del sistema) lo utiliza en todos los AT, sin importar el modelo. Por ello, cabe suponer
que seguramente los futuros equipos mantendrán la compatibilidad en este aspecto. Sucede que la salida del
contador 1 del 8254, encargada del refresco de la memoria, controla de alguna manera desconocida (tal vez a
través de un flip-flop) la generación de una onda cuadrada de unos 33 KHz que puede leerse a través del bit
4 del puerto 61h (no se trata de la salida OUT del contador 1: éste está programado en modo 2 y no genera
precisamente una onda cuadrada). El contador 1 es programado por la BIOS en todos los PC con una cuenta
18, conmutando el nivel de la salida cada segundo 1193180/18 = 66287,77 veces. Para hacer un
determinado retardo basta con contar las veces que el bit cambia de nivel: la función en ensamblador
retardo_asm() del programa de ejemplo lo ilustra. Este método es especialmente interesante en los
programas residentes que precisen retardos de precisión, para sonido u otras tareas, tales como limitar la
duración máxima de una comprobación en un bit de estado a unos milisegundos o microsegundos (control de
timeouts); la principal ventaja es que no se modifica en absoluto la configuración de ningún chip que pueda
estar empleando el programa principal, empezando por el 8254. Además, no requiere preparación previa
alguna. Para los más curiosos, decir que el bit 5 del puerto 61h es la salida OUT del contador 2 del 8254 (la
línea OUT del contador 2 del 8253 de los PC/XT también puede consultarse a través del bit 5, pero del
puerto 62h).
El único inconveniente del método es la alta frecuencia con que cambia el bit: esta misma rutina escrita en
C podría no ser suficientemente ágil para detectar todas las transiciones en las máquinas AT más lentas a 6
MHz. A partir de 8 MHz sí puede ser factible, como evidencian las pruebas realizadas, aunque hay que
extremar las precauciones para que el código compilado sea lo bastante rápido: utilizar las dos variables
registro que realmente soportan los compiladores y huir de la aritmética de 32 bits, como puede observarse
en la función retardo_c() del programa de ejemplo. Una mala codificación o compilador podrían hacer
inservible el método incluso en una máquina a 16 ó 20 MHz. Para no tener problemas, es mejor emplear la
versión en ensamblador, escrita en un C no mucho menos estándar. La macro MICRO() ayuda a seleccionar
con más comodidad el retardo, indicándolo en mus, aunque implica una operación en coma flotante que por sí
sola añade unos 100 mus de retardo adicionales en un 386-25 sin coprocesador y con las librerías de
10 de 14 12/10/00 19:16
EL TEMPORIZADOR 8253 U 8254 file:///C|/librosVirtuales/UniversoDigital/1203.html
Borland.
Anécdota: Para los más curiosos, decir que los programadores de Microsoft emplean este método en el KEYB
en dos ocasiones: para limitar a un tiempo razonable la espera hasta que el registro de entrada del 8042 se llene (15
ms) y, en otra ligera variante, para controlar la duración del pitido de error. Los aficionados al ensamblador pueden
comprobarlo personalmente aplicando el comando U del DEBUG sobre el KEYB para desensamblar a partir de los
offsets 0E39 y 0D60, respectivamente: en el primer caso, la subrutina sólo es ejecutada en AT; en el segundo, veréis
como el KEYB se asegura de que el equipo es un AT comprobando el valor de BP antes de saltar a 0D70 (ejecuta un
bucle vacío en las demás máquinas). Esta nueva técnica ha permitido eliminar respecto a anteriores versiones del
programa algunos test sobre tipos de ordenadores, cuya finalidad más común era ajustar las constantes de retardo.
Son válidos tanto el KEYB del MS-DOS 5.0 castellano como el del MS-DOS 6.0 en inglés o castellano indistintamente
(¡las direcciones indicadas coinciden!). También en las BIOS modernas suele haber ejemplos de esta técnica, aunque
las direcciones ya no coinciden...
/********************************************************************/
/* */
/* Programa de demostración del método de retardo basado en la */
/* monitorización de los ciclos de refresco de memoria del AT. */
/* */
/********************************************************************/
#include <dos.h>
void main()
{
/* cuatro formas de hacer un mismo retardo de precisión */
11 de 14 12/10/00 19:16
EL TEMPORIZADOR 8253 U 8254 file:///C|/librosVirtuales/UniversoDigital/1203.html
do
do {
while (a==(b=inportb(0x61) & 0x10));
a=b;
} while (cuenta_l--);
while (cuenta_h--);
}
La producción de sonido es uno de los puntos más débiles de los ordenadores compatibles, que sólo
superan por muy escaso margen a alguno de los micros legendarios de los 80, si bien las tarjetas de sonido
han solventado el problema. Pero aquí nos conformaremos con describir la programación del altavoz. En
todos los PCs existen dos métodos diferentes para generar sonido, con la utilización del 8254 o sin él, que
veremos por separado.
El altavoz del ordenador está ligado en todas las máquinas al bit 1 del puerto E/S 61h. Si se hace cambiar
este bit (manteniéndolo durante cierto tiempo alto y durante cierto tiempo bajo, repitiendo el proceso a gran
velocidad) se puede generar una onda cuadrada de sonido. Cuanto más deprisa se realice el proceso, mayor
será la frecuencia del sonido. Por fortuna, la baja calidad del altavoz del PC redondea la onda cuadrada y
produce un sonido algo más musical de forma involuntaria. No existe, en cualquier caso, control sobre el
volumen, que dada la calidad del altavoz también está en función de la frecuencia. Este método de producción
de sonido tiene varios inconvenientes. Por un lado, la frecuencia con que se hace vibrar al bit que lo produce,
si no se tiene mucho cuidado, está a menudo más o menos ligada a la capacidad de proceso del ordenador:
esto significa que el sonido es más grave en máquinas lentas y más agudo en las rápidas. Esto es
particularmente grave y evidente cuando las temporizaciones se hacen con bucles de retardo con registros de
la CPU: la frecuencia del sonido está totalmente a merced de la velocidad de la máquina en que se produce.
Es por ello que el pitido de error que produce el teclado es a menudo distinto de unos ordenadores a otros,
aunque tengan el mismo KEYB instalado. Otro gran inconveniente de este método es que las interrupciones,
fundamentalmente la del temporizador, producen fuertes interferencias sobre el sonido. Por ello, es normal
tenerlas inhibidas, con el consiguiente retraso de la hora. Por último, un tercer gran inconveniente es que la
CPU está completamente dedicada a la producción de sonido, sin poder realizar otras tareas mientras tanto.
Antes de comenzar a producir el sonido con este método hay que bajar la línea GATE del 8254, ya que
cuando está en alto y se activa también el bit 1 del puerto E/S 61h, el temporizador es el encargado de
producir el sonido (este es el segundo método, como veremos). Por tanto, es preciso poner primero a cero el
bit 0 del mismo puerto (61h):
12 de 14 12/10/00 19:16
EL TEMPORIZADOR 8253 U 8254 file:///C|/librosVirtuales/UniversoDigital/1203.html
El otro método posible consiste en emplear el contador 2 del temporizador conectado al altavoz; así,
enviando el período del sonido (1.193.180/frecuencia_en_Hz) a dicho contador (programado en modo 3),
éste se encarga de generar el sonido. Esto permite obtener sonidos idénticos en todos los ordenadores. Existe
el pequeño problema de que la duración del sonido ha de ser múltiplo de 1/18,2 segundos si se desea utilizar
el reloj del sistema para determinarla (un bucle de retardo sería, una vez más, dependiente de la máquina) ya
que el contador 2 está ahora ocupado en la producción de sonido y no se puede usar para temporizar (al
menos, no sin hacer malabarismos). Alternativamente, se podría evaluar la velocidad de la CPU para ajustar
las constantes de retardo o aumentar la velocidad de la interrupción periódica.
Para emplear este sistema, primero se prepara el contador 2 para temporizar (poniendo a 1 el bit 0 del
puerto 61h) y luego se conecta su salida al altavoz (poniendo a 1 el bit 1 del puerto 61h). Al final, conviene
borrar ambos bits de nuevo. Ahora no es preciso inhibir las interrupciones para garantizar la calidad del
sonido:
Las frecuencias en Hz de las distintas notas musicales están oficialmente definidas y los músicos suelen
tenerlas en cuenta a la hora de afinar los instrumentos. La escala cromática temperada, adoptada por la
American Standards Asociation en 1936, establece el LA4 como nota de referencia en 440 Hz. En general,
una vez conocidas las frecuencias de las notas de una octava, las de la octava siguiente o anterior se obtienen
multiplicando y dividiendo por dos, respectivamente. La fórmula de abajo permite obtener las frecuencias de
las notas asignándolas un número (a partir de 6 y hasta 88; el LA de 440 Hz es la nota 49) con una precisión
razonable, máxime teniendo en cuenta que van a ir a parar al altavoz del PC. Tal curiosa relación se verifica
debido a que la respuesta del oído humano es logarítmica, lo que ha permitido reducir a simples matemáticas
el viejo saber milenario de los músicos.
13 de 14 12/10/00 19:16
EL TEMPORIZADOR 8253 U 8254 file:///C|/librosVirtuales/UniversoDigital/1203.html
14 de 14 12/10/00 19:16
EL CONTROLADOR DE INTERRUPCIONES 8259 file:///C|/librosVirtuales/UniversoDigital/1204.html
Los ordenadores se comunican con el exterior por medio de los dispositivos de entrada y salida. Estos
dispositivos son normalmente lentos en comparación con la elevada velocidad de la unidad central. Un
ejemplo típico puede ser el teclado: entre las pulsaciones de cada tecla hay un espacio de tiempo impredecible
y dependiente del usuario. Una manera simple de gestionar los dispositivos de E/S consiste en comprobar
continuamente si alguno de ellos tiene un dato disponible o lo está solicitando. Sin embargo, esto supone una
importante pérdida de tiempo para el microprocesador, que mientras tanto podría estar haciendo otras cosas.
En una máquina multitarea y/o multiusuario, resulta más interesante que los periféricos puedan interrumpir al
microprocesador para solicitarle una operación de entrada o salida en el momento necesario, estando la CPU
liberada de la misión de comprobar cuándo llega ese momento. Cuando se produce la interrupción, el
microprocesador ejecuta la correspondiente rutina de servicio y después continúa con su tarea normal. Los
compatibles PC poseen un hardware orientado por completo a la multitarea (otra cosa es que el 8086 y el
DOS no la aprovechen) y la entrada/salida se gestiona casi por completo mediante interrupciones en todas las
máquinas. Por ejemplo, en las operaciones de disco, cuando acaba la transferencia de datos se produce una
interrupción de aviso y una rutina de la BIOS activa una variable que lo indica, en el segmento de memoria
40h. Las propias funciones de la BIOS para acceder al disco se limitan a chequear continuamente esa variable
hasta que cambie, lo que significa un evidente desaprovechamiento de las posibilidades que la gestión por
interrupciones pone a nuestra disposición.
Las interrupciones añaden cierta complejidad al diseño del hardware: en principio, es necesario
jerarquizarlas de alguna manera para decidir cuál se atiende en el caso de que se produzcan dos
simultáneamente. También es importante el control de prioridad para el caso de que se produzca una
interrupción mientras se está procesando otra: sólo se la atenderá si es de mayor prioridad. En este capítulo
sólo consideraremos las interrupciones hardware, no las de software ni las excepciones del procesador.
Este circuito integrado está especialmente diseñado para controlar las interrupciones en sistemas basados
en el 8080/8085 y en el 8086. Puede controlar hasta 8 interrupciones vectorizadas. Además, a un 8259 se le
pueden conectar en cascada un máximo de 8 chips 8259 adicionales, lo que permite gestionar sistemas con
hasta 64 interrupciones, como veremos.
1 de 12 12/10/00 19:17
EL CONTROLADOR DE INTERRUPCIONES 8259 file:///C|/librosVirtuales/UniversoDigital/1204.html
El diagrama funcional del 8259, con la estructura interna de las diversas partes que lo componen, es el siguiente:
Los principales registros internos del 8259 son el IRR (Interrupt Request Register) y el ISR (In Service Register). El IRR
almacena todas las peticiones de interrupción pendientes; el ISR almacena todas las interrupciones que están siendo
atendidas en un momento dado. La lógica de gestión de prioridad determina qué interrupción, de las solicitadas en el IRR,
debe ser atendida primero: cuando lleguen las señales INTA dicha interrupción será la primera procesada y su bit
correspondiente se activará en el ISR. El buffer del bus de datos conecta el 8259 con el bus de datos de la placa principal del
ordenador: su diseño en 3 estados permite desconectarlo cuando sea necesario; a través de este bus circulan las palabras de
control y la información de estado. La lógica de lectura y escritura acepta los comandos que envía la CPU: aquí hay
registros para almacenar las palabras de inicialización y operación que envía el procesador; también sirve para transferir el
estado del 8259 hacia el bus de datos. El buffer de cascada/comparador almacena y compara las identificaciones de todos los
8259 que posea el sistema: el 8259 maestro envía la identificación del 8259 esclavo en las líneas CAS, los 8259 esclavos la
leen y el implicado en la operación coloca en el bus de datos la dirección (vector) de la rutina que atenderá la interrupción en
los 2 próximos (o el próximo) ciclos INTA.
2 de 12 12/10/00 19:17
EL CONTROLADOR DE INTERRUPCIONES 8259 file:///C|/librosVirtuales/UniversoDigital/1204.html
El funcionamiento del 8259 varía ligeramente en función del sistema en que esté instalado, según sea este un 8086 o un
8080/8085. Veremos primero el caso del 8086:
1) Una o más líneas IR son activadas por los periféricos, lo que pone a 1 el correspondiente bit del IRR.
El 8259 evalúa la prioridad de estas interrupciones y solicita la interrupción a la CPU (línea INT) si es
2)
necesario.
3) Cuando la CPU reconoce la interrupción, envía la señal -INTA.
Nada más recibida la señal -INTA de la CPU, el 8259 activa el bit correspondiente a la interrupción de
4) mayor prioridad (la que va a ser procesada) en el ISR y lo borra en el IRR. En este ciclo, el 8259 aún no
controla el bus de datos.
Cuando la CPU envía un segundo ciclo -INTA, el 8259 deposita en el bus de datos un valor de 8 bits
5)
que indica el número de vector de interrupción del 8086, para que la CPU lo pueda leer.
En el modo AEOI del 8259, el bit de la interrupción en el ISR es borrado nada más acabar el segundo
6) pulso -INTA; en caso contrario, ese bit permanece activo hasta que la CPU envíe el comando EOI al
final de la rutina que trata la interrupción (caso más normal).
En el caso de sistemas basados en el 8080/8085, el funcionamiento es idéntico hasta el punto (3), pero a
continuación sucede lo siguiente:
Nada más recibida la señal -INTA de la CPU, el 8259 activa el bit correspondiente a la interrupción de
mayor prioridad (la que va a ser procesada) en el ISR y lo borra en el IRR. En este ciclo, el 8259
4)
deposita en el bus de datos el valor 11001101b, correspondiente al código de operación de la
instrucción CALL del 8080/85.
5) Esta instrucción CALL provoca que la CPU envíe dos pulsos -INTA.
El 8259 utiliza estos dos pulsos -INTA para depositar en el bus de datos, sucesivamente, la parte baja y
6)
alta de la dirección de memoria del ordenador de la rutina de servicio de la interrupción (16 bits).
Esto completa la instrucción CALL de 3 bytes. En el modo AEOI del 8259, el bit de la interrupción en
7) el ISR es borrado nada más acabar el tercer pulso -INTA; en caso contrario, ese bit permanece activo
hasta que la CPU envíe el comando EOI al final de la rutina que trata la interrupción.
Si en el paso (4), con ambos tipos de microprocesador, no está presente la petición de interrupción (por
ejemplo, porque ha sido excesivamente corta) el 8259 envía una interrupción de nivel 7 (si hubiera un 8259
conectado en IR7, las líneas CAS permanecerían inactivas y la dirección de la rutina de servicio de
interrupción sería suministrada por el 8259 maestro).
El 8259 acepta dos tipos de comandos generados por la CPU: los ICW (Inicialization Command Word)
que inicializan el 8259, y los OCW (Operation Command Word) que permiten programar la modalidad de
funcionamiento. Antes de que los 8259 de un sistema comiencen a trabajar deben recibir una secuencia de
ICW que los inicialice. Los ICW y OCW constan de secuencias de 2 a 4 comandos consecutivos que el
8259 espera recibir secuencialmente, unos tras otros, a través del bus de datos, según sea necesario (el
propio 8259 se encarga de contarlos midiendo los pulsos de la línea -WR). Los OCW pueden ser enviados
en cualquier momento, una vez realizada la inicialización.
La comunicación con el 8259 emplea las líneas -WR y -RW, así como A0. El hecho de que exista una
sola línea de direcciones implica que el 8259 sólo ocupa dos direcciones de puerto de E/S en el espacio de
entrada y salida del ordenador.
3 de 12 12/10/00 19:17
EL CONTROLADOR DE INTERRUPCIONES 8259 file:///C|/librosVirtuales/UniversoDigital/1204.html
ICW1: Cuando un comando es enviado con A0=0 y D4=1, el 8259 lo interpreta como la primera palabra
de la inicialización (ICW1) e inicia dicha secuencia de inicialización, lo que implica lo siguiente:
- Se resetea el circuito sensible a los niveles, lo que quiere decir que hasta nueva orden las líneas IR serán
sensibles por flancos de transición bajo-alto.
- Se limpia el IMR.
- A la línea IR7 se le asigna un nivel de prioridad 7.
- Se desactiva el Special Mask Mode. Se queda listo para devolver IRR en la próxima lectura OCW3.
- Si IC4 (bit D0) es 0, todas las funciones seleccionadas en ICW4 serán puestas a 0 (non buffered
mode, no AEOI, sistema 8080/85) e ICW4 no será necesaria.
Notas: Si SNGL es 1 significa que el 8259 es único en el sistema y no será enviada ICW3. Si
IC4 es 0, tampoco será enviada ICW4. En el 8080/85, las diversas interrupciones generan CALL's a
8 direcciones adyacentes separadas 4 u 8 bytes (según indique ADI): para componer la dirección, el
8259 inserta A0..A4 (o A0..A5) convenientemente, según la interrupción que se trate. En el 8086,
A7..A5 y ADI son ignoradas.
ICW2: Se envía con A0=1, para diferenciarlo de ICW0 (hacer OUT a la siguiente dirección de puerto).
ICW3: Se envía sólo en el caso de que haya más de un 8259 en el sistema (bit SNGL de ICW1 a cero),
en caso contrario en su lugar se enviaría ICW4 (si procede).
Formato de ICW3 a enviar a un 8259 esclavo para que memorice de qué línea IR del maestro cuelga:
4 de 12 12/10/00 19:17
EL CONTROLADOR DE INTERRUPCIONES 8259 file:///C|/librosVirtuales/UniversoDigital/1204.html
ICW4: Se envía sólo si IC4=1 en ICW1, con objeto de colocar el 8259 en un modo de operación distinto
del establecido por defecto (que equivale a poner a cero todos los bits de ICW4).
Notas: El Special Fully Nested Mode, el buffered mode y la modalidad AEOI serán explicadas
más tarde. Nótese que con el 8086 es obligatorio enviar ICW4 para seleccionar esta CPU.
Una vez inicializado, el 8259 está listo para procesar las interrupciones que se produzcan. Sin embargo,
durante su funcionamiento normal está capacitado para recibir comandos de control por parte de la CPU.
OCW1:
Este comando activa y borra bits en el IMR (Interrupt Mask Register). Los bits M0..M7 de
OCW1 se corresponden con sus correspondientes bits del IMR. Un bit a 1 significa interrupción
enmascarada (inhibida) y a 0, interrupción habilitada.
OCW2:
OCW3:
5 de 12 12/10/00 19:17
EL CONTROLADOR DE INTERRUPCIONES 8259 file:///C|/librosVirtuales/UniversoDigital/1204.html
En las ICW y, sobre todo, en las OCW, se han introducido un aluvión de elementos nuevos que serán
explicados a continuación.
Modos de EOI.
El EOI (End Of Interrupt) sirve para bajar el bit del ISR que representa la interrupción que está siendo
procesada. El EOI puede producirse automáticamente (AEOI) al final de la última señal INTA que envía la
CPU al 8259 para una interrupción dada (tercer ciclo INTA en el 8080/85 y segundo en el 8086); sin
embargo, la mayoría de los sistemas requieren una gestión de prioridades en las interrupciones, lo que significa
que es más conveniente que EOI lo envíe el propio procesador al 8259, a través de OCW2, cuando acabe la
6 de 12 12/10/00 19:17
EL CONTROLADOR DE INTERRUPCIONES 8259 file:///C|/librosVirtuales/UniversoDigital/1204.html
rutina de gestión de interrupción, para evitar que mientras se gestiona esa interrupción se produzcan otras de
igual o menor prioridad. En un sistema con varios 8259, el EOI debe ser enviado no sólo al 8259 esclavo
implicado sino también al maestro. Hay dos modalidades de EOI: la específica y la no-específica. En el EOI
no específico, el 8259 limpia el bit más significativo que esté activo en el ISR, que se supone que es el
correspondiente a la última interrupción producida (la de mayor prioridad y que está siendo procesada). Esto
es suficiente para un sistema donde se respeta el Fully Nested Mode. En el caso en que no fuera así, el 8259
es incapaz de determinar cuál fue el último nivel de interrupción procesado, por lo que la rutina que gestiona la
interrupción debe enviar un EOI específico al 8259 indicándole qué bit hay que borrar en el ISR.
Rotación de prioridades.
Hay sistemas en que varios periféricos tienen el mismo nivel de prioridad, en los que no interesa mantener
un orden de prioridades en las líneas IR. En condiciones normales, nada más atender una interrupción de un
periférico, podría venir otra que también se atendería, mientras los demás periféricos se cruzarían de brazos.
La solución consiste en asignar el menor nivel de prioridad a la interrupción recién atendida para permitir que
las demás pendientes se procesen también. Para ello se envía un EOI que rote las prioridades: si, por ejemplo,
se había procesado una IR3, IR3 pasará al menor nivel de prioridad e IR4 al mayor, quedando las
prioridades ordenadas (de mayor a menor): IR4, IR5, IR6, IR7, IR0, IR1, IR2, IR3. Existe también una
rotación específica de prioridades, a través de OCW2, que puede realizarse en un comando EOI o
independientemente del mismo (comando para asignar prioridad).
Comando POLL.
En esta modalidad poco habitual, habilitada a través de OCW3, no se emplea la salida INT del 8259 o
bien el microprocesador trabaja con las interrupciones inhibidas. El servicio a los periféricos es realizado por
software utilizando el comando POLL. Una vez enviado el comando POLL, el 8259 interpreta la próxima
lectura que se realice como un reconocimiento de interrupción, actualizando el ISR y consultando el nivel de
prioridad. Durante esa lectura, la CPU obtiene en el bus de datos la palabra POLL que indica (en el bit 7) si
hay alguna interrupción pendiente y, en ese caso, cuál es la de mayor prioridad (bits 0-2).
Buffered Mode.
Al emplear el 8259 en grandes sistemas, donde se requieren buffers en los buses de datos, si se va a
emplear el modo cascada existe el problema de la habilitación de los buffers. Cuando se programa el modo
buffer, la patilla -SP/-EN del 8259 actúa automáticamente como señal de habilitación del los buffers cada vez
que se deposita algo en el bus de datos. Si se programa de esta manera el 8259 (bit BUF de ICW4) será
7 de 12 12/10/00 19:17
EL CONTROLADOR DE INTERRUPCIONES 8259 file:///C|/librosVirtuales/UniversoDigital/1204.html
preciso distinguir por software si se trata de un 8259 maestro o esclavo (bit M/S de ICW4).
Los PC/XT vienen equipados con un 8259 conectado a la dirección base E/S 20h; este controlador de
interrupciones es accedido, por tanto, por los puertos 20h (A0=0) y 21h (A0=1). En los AT y máquinas
superiores, adicionalmente, existe un segundo 8259 conectado en cascada a la línea IR2 del primero. Este
segundo controlador es accedido a través de los puertos 0A0h y 0A1h. La BIOS del ordenador, al arrancar
la máquina, coloca la base de interrupciones del primer controlador en 8, lo que significa que las respectivas
IR0..IR7 están ligadas a los vectores de interrupción 8..15; el segundo 8259 de los AT genera las
interrupciones comprendidas entre 70h y 77h. La asignación de líneas IR para los diversos periféricos del
ordenador es la siguiente (por orden de prioridad):
En los AT, la línea IR2 del 8259 maestro es empleada para colgar de ella el segundo 8259 esclavo. Como
la línea IR2 está en el slot de expansión de 8 bits, por razones de compatibilidad los AT tienen conectado en
su lugar la IR9 que simula la IR2 original. Cuando se produce una IR9 debido a un periférico de XT que
pretendía generar una IR2, el AT ejecuta una rutina de servicio en INT 71h que salta simplemente a la INT
0Ah (tras enviar un EOI al 8259 esclavo).
La colocación de IRQ0-IRQ7 en el rango INT 8-INT 15 fue bastante torpe por parte de IBM, al saltarse
la especificación de Intel que reserva las primeras 32 interrupciones para el procesador. En modo protegido,
algunas de esas excepciones es estrictamente necesario controlarlas. Por ello, los sistemas operativos que
trabajan en modo extendido y ciertos extensores del DOS (como las versiones 3.x de WINDOWS) se ven
obligados a mover de sitio estas interrupciones. En concreto, WINDOWS 3.x las coloca en INT 50h-INT
57h (por software, las máquinas virtuales 8086 emulan las correspondientes INT 8-INT 15). Además, en el
modo protegido del 286/386 (o el virtual-86 del 386) la tradicional tabla de vectores de interrupción es
sustituida por otra de descriptores, aunque el funcionamiento global es similar.
La interrupción no enmascarable del 80x86 no está controlada por el 8259: es generada por la circuitería
que controla la memoria si se detecta un error de paridad. La interrupción no enmascarable puede ser
enmascarada en los ordenadores compatibles gracias a la circuitería de apoyo al procesador, aunque no es
frecuente; en los AT el bit 7 del puerto 70h controla su habilitación (si es cero, la NMI está habilitada) sin
embargo también se podría inhibir el control de paridad directamente (activando los bits 2 y 3 de la dirección
E/S 61h, respetando el resto de los bits de ese puerto por medio de una lectura previa). En los PC/XT, es el
puerto 0A0h el que controla la habilitación de la NMI, también con el bit 7 (con la diferencia de que debe
estar a cero para inhibirla).
Durante la inicialización del ordenador, la BIOS envía sucesivamente al 8259 las palabras ICW1 a ICW4
8 de 12 12/10/00 19:17
EL CONTROLADOR DE INTERRUPCIONES 8259 file:///C|/librosVirtuales/UniversoDigital/1204.html
Como se puede observar, la rutina de arriba enmascara todas las interrupciones a través del IMR. El
objetivo de esta medida es evitar que se produzcan interrupciones antes de desviar los correspondientes
vectores, pudiendo incluso mientras tanto estar habilitadas las interrupciones con STI.
Cuando se produce una interrupción de la CPU (bien por software o por hardware), el indicador de
interrupciones del registro de estado del 8086 se activa para inhibir otra posible interrupción mientras se
procesa esa (la instrucción IRET recuperará los flags del programa principal devolviendo las interrupciones a
su estado previo). Lo normal suele ser que las rutinas que gestionan una interrupción comiencen por un STI
con objeto de permitir la generación de otras interrupciones; las interrupciones sólo deben estar inhibidas en
brevísimos momentos críticos. Sin embargo, cuando se procesa una interrupción hardware, el registro de
interrupciones activas (ISR) indica qué interrupción en concreto está siendo procesada; si en ese momento
llega otra interrupción hardware de menor o igual prioridad le será denegada la petición, si es de mayor
prioridad le será concedida (si la rutina comenzaba por STI). Cuando acaba de procesarse la interrupción
hardware, la instrucción IRET no le dice nada al 8259, por lo que el programador debe preocuparse de
borrar el ISR antes de acabar. Si, por ejemplo, se gestiona la interrupción del temporizador sin limpiar al final
el ISR, a partir de ese momento quedarán bloqueados el teclado, los discos ... Conviene aquí señalar que una
rutina puede apoyarse en una interrupción hardware sin necesidad de reprogramarla por completo. Ejemplo:
STI
PUSH AX
IN AL,puerto_teclado
CALL anterior_int9
;
; procesar tecla
9 de 12 12/10/00 19:17
EL CONTROLADOR DE INTERRUPCIONES 8259 file:///C|/librosVirtuales/UniversoDigital/1204.html
;
POP AX
IRET
Al producirse la INT 9 se lee el código de rastreo de la tecla y luego se llama a la rutina que gestionaba
con anterioridad a ésta la INT 9: ella se encargará de limpiar el ISR que, por tanto, no es tarea de nuestra
rutina. Si hubiera que limpiar el ISR, bastaría con un EOI no específico (OCW2: enviar un valor 20h al puerto
20h para el 8259-1 y al puerto 0A0h para el 8259-2; en las IRQ8-IRQ15 hay que enviar el EOI a ambos
controladores de interrupción).
Aviso: Aunque el funcionamiento del 8259 es suficientemente lógico como para pasar casi
inadvertido, hay veces en que hay que tenerlo en cuenta. Por ejemplo, al utilizar el servicio 86h de la
INT 15h del AT (con objeto de hacer retardos) desde una interrupción hardware comprendida entre
IRQ 0 e IRQ 7, conviene limpiar el ISR antes de llamar: no basta con hacerlo al final de la rutina. La
causa es que la BIOS utiliza las interrupciones asociadas al reloj de tiempo real para hacer el retardo, y
en algunas máquinas es poco precavido y no limpia el ISR al principio, lo que deja totalmente
bloqueado el ordenador.
La siguiente utilidad reprograma el 8259 maestro para desviar las INT 8-INT 15 a los nuevos vectores
INT 50h-INT 57h (que invocan a los originales, para que el sistema siga funcionando con normalidad). Esta
nueva ubicación no ha sido elegida por capricho, y es la misma que emplea WINDOWS 3.x. La razón es que
el 386 trabaja normalmente en modo virtual-86 bajo MS-DOS 5.0; cuando se produce una interrupción se
ejecuta una rutina en modo protegido. El EMM386 del MS-DOS 5.0 no está preparado para soportar las
IRQ0-IRQ7 en otra localización que no sea la tradicional INT 8-INT 15 ó en su defecto INT 50h-INT 57h
(por compatibilidad con WINDOWS). Con el QEMM386 o, simplemente, sin controlador de memoria
expandida instalado, no habría problemas y se podría elegir otro lugar distinto. Por cierto: si se entra y se sale
de WINDOWS, la nueva localización establecida, ya sea en 50h o en otro sitio, deja de estar vigente: esto
significa que WINDOWS reprograma la interrupción base al volver al DOS. Personalmente he comprobado
que aunque IRQDEMO fuera más elegante (empleando funciones de la especificación VCPI), nuestro
querido WINDOWS no lo sería: ¡para qué molestarse!. Sin embargo, IRQDEMO sí se toma la molestia de
comprobar si la máquina es un XT o un AT para enviar correctamente la ICW3 del 8259.
; ********************************************************************
; * IRQDEMO.ASM - Utilidad residente de demostración, que desvía *
; * las interrupciones hardware INT 8-INT 15 hacia *
; * los vectores INT 50h a INT 57h. *
; ********************************************************************
irqdemo SEGMENT
ASSUME CS:irqdemo, DS:irqdemo
ORG 100h
inicio:
JMP main
10 de 12 12/10/00 19:17
EL CONTROLADOR DE INTERRUPCIONES 8259 file:///C|/librosVirtuales/UniversoDigital/1204.html
irq3: INT 11
IRET
irq4: INT 12
IRET
irq5: INT 13
IRET
irq6: INT 14
IRET
irq7: INT 15
IRET
main PROC
LEA BX,tabla_ints
MOV AL,50h ; nueva base para IRQ's 0-7
otra_int: PUSH AX
PUSH BX
MOV AH,25h
MOV DX,[BX]
INT 21h ; desviar INT 50h-57h
POP BX
ADD BX,2
POP AX
INC AL
CMP AL,58h
JB otra_int
CALL es_AT?
MOV BL,4
MUL BL
MOV BL,AL ; BL = 4 en AT y 0 en PC/XT
CALL inic_8259
LEA DX,texto_txt
MOV AH,9
INT 21h ; mensaje de instalación
MOV ES,ES:[2Ch]
MOV AH,49h
INT 21h ; liberar entorno
MOV AH,31h
MOV DX,tam_resid
INT 21h ; terminar residente
main ENDP
11 de 12 12/10/00 19:17
EL CONTROLADOR DE INTERRUPCIONES 8259 file:///C|/librosVirtuales/UniversoDigital/1204.html
irqdemo ENDS
END inicio
12 de 12 12/10/00 19:17
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
El acceso directo a memoria es una técnica de diseño del hardware que permite a los periféricos
conectados a un sistema realizar transferencias sobre la memoria sin la intervención del procesador. De esta
manera, las lentas operaciones de entrada y salida de bloques de datos, se pueden realizar en la sombra
mientras la CPU se dedica a otras tareas más útiles. Como la memoria del ordenador sólo puede ser accedida
a un tiempo por una fuente, en el momento en que el DMA realiza las transferencias el microprocesador se
desconecta de los buses, cediéndole el control. El funcionamiento del controlador de DMA se basa en unos
registros que indican la dirección de memoria a ser accedida y cuántas posiciones de memoria quedan aún por
transferir. La transferencia de datos entre los periféricos y la memoria por DMA no suele efectuarse de golpe,
sino más bien poco a poco, robándole algunos ciclos a la CPU. Los controladores de DMA suelen disponer
de varias líneas de petición de DMA, pudiendo atender las necesidades de varios periféricos que soliciten una
transferencia, quienes deben haber sido diseñados expresamente para soportar el DMA.
1 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
DESCRIPCIÓN FUNCIONAL
Los modos de operación del 8237 están diseñados para soportar transferencias de una sola palabra de datos y flujos de
datos discontinuos entre la memoria y los periféricos. El controlador de DMA es realmente un circuito secuencial generador
de señales de control y direcciones que permite la transferencia directa de los datos sin necesidad de registros temporales
intermedios, lo que incrementa drásticamente la tasa de transferencia de datos y libera la CPU para otras tareas. Las
operaciones memoria-memoria precisan de un registro temporal intermedio, por lo que son al menos dos veces más lentas
que las de E/S, aunque en algunos casos aún más veloces que la propia CPU (no es el caso de los ordenadores compatibles).
Tipo de registro Tamaño Nº registros
El 8237 consta internamente de varios
Registro base de dirección 16 bits 4
bloques: un bloque de control de tiempos
Registro base contador de palabras 16 bits 4
que genera las señales de tiempo internas y
Registro de dirección en curso 16 bits 4
las señales de control externas; un bloque de
Registro contador de palabras en curso 16 bits 4
gestión de prioridades, que resuelve los
Registro temporal de dirección 16 bits 1
conflictos de prioridad cuando varios
Registro temporal contador de palabras 16 bits 1
canales de DMA son accedidos a la vez;
Registro de estado 8 bits 1
también posee un elevado número de
Registro de comandos 8 bits 1
registros para gestionar el funcionamiento.
Registro temporal 8 bits 1
Los registros internos del 8237 están
Registro de modo 6 bits 4
resumidos en la figura de la derecha.
Registro de máscara 4 bits 1
Registro de petición 4 bits 1
OPERACIÓN DEL DMA
En un sistema, los buses del 8237 están conectados en paralelo al bus general del ordenador, siendo
necesario un latch externo para almacenar la parte alta de la dirección de memoria. Cuando está inactivo, el
8237 está desconectado de los buses; cuando se produce una petición de DMA pasa a controlar los buses y
a generar las señales necesarias para realizar las transferencias. La operación que realiza el 8237 es
consecuencia de la programación realizada previamente en los registros de comando, modo, base de
dirección y contador de palabras a transferir.
2 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
Para comprender mejor el funcionamiento del 8237 es conveniente considerar los estados generados por
cada ciclo. El DMA opera básicamente en dos ciclos: el activo y el inactivo (o idle). Tras ser programado,
el DMA permanece normalmente inactivo hasta que se produce la solicitud de DMA en algún canal o vía
software. Cuando ésta llega, si ese canal no estaba enmascarado (es decir, inhibido) el 8237 solicita los buses
a la CPU y se pasa al ciclo activo. El ciclo activo se compone de varios estados internos, en función de la
manera en que sea programado el chip.
El 8237 puede asumir 7 diferentes estados, cada uno de ellos compuesto de un ciclo de reloj completo. El
estado 1 (S1) es el estado inactivo o idle. En él se entra cuando no hay pendiente una petición de DMA
válida, al final de la secuencia de transferencia, o tras un reset o un Master Clear (que se verá más adelante).
En S1 el DMA está inactivo pero puede ser programado por el microprocesador del sistema. El estado 0
(S0) es el primer estado de servicio DMA. El 8237 ha solicitado los buses a la CPU a través de la línea HRQ
pero la CPU aún no ha respondido a través de HLDA. En esta situación, el 8237 puede aún todavía ser
programado. Una vez que la CPU responde, la labor del 8237 puede comenzar: los estados S2, S3 y S4 se
suceden entonces para realizar el servicio. Si se necesitara más tiempo, está prevista la posibilidad de insertar
estados de espera entre S2 ó S3 y S4 a través de la patilla READY.
Téngase en cuenta que los datos son pasados directamente de la memoria hacia/desde los periféricos, por
lo tanto no cruzan a través del DMA (las líneas -IOR y -MEMW, o -IOW y -MEMR, son activadas al
mismo tiempo). El caso de las operaciones memoria-memoria es especial, ya que para cada palabra a mover
hay que realizar la operación de lectura (en unos estados denominados S11, S12, S13 y S14) y después la de
escritura (estados S21, S22, S23, S24).
Ciclo Inactivo.
Este es el estado en el que el 8237 espera pacientemente a que aparezca alguna solicitud de DMA,
comprobando las líneas DREQ en los flancos de bajada de las señales de reloj: en esto consisten los estados
S1. En esta situación, el 8237 puede ser programado por la CPU. Para ello, las líneas A0..A3 seleccionan el
registro interno y -IOR e -IOW indican si se trata de leer o escribir. Como algunos de los registros internos
son de 16 bits, existe un flip-flop interno que conmuta en cada operación de escritura sobre ellos, para que el
8237 sepa si está recibiendo el byte alto o el bajo (este flip-flop es puesto a cero en un Reset o en un
comando Master Clear, existiendo también comandos especiales para controlarlo). Algunas combinaciones
de A0..A3 y las líneas -IOR e -IOW, en lugar de acceder a los registros, constituyen comandos especiales.
Ciclo Activo.
Cuando el 8237 está en el ciclo inactivo y se produce una petición por software o un canal no
enmascarado solicita servicio DMA, se pasa al estado activo y se opera en uno de estos 4 modos:
El dispositivo es programado para realizar una única transferencia. El registro contador de palabras es
decrementado y el de direcciones se incrementa/decrementa según ha sido programado. Cuando el registro
contador de palabras se desborda (pasa de 0 a 0FFFFh) se activa el bit Terminal Count (fin de cuenta) en
el registro de estado y la patilla -EOP genera un pulso. Si el canal estaba programado para autoinicializarse
esto es lo que realiza; en caso contrario, se activa automáticamente el bit de máscara para inhibir hasta nueva
orden ese canal.
DREQ debe permanecer activo hasta que DACK responda. Sin embargo, si DREQ permanece activo
hasta que acaba el proceso de transferencia, la línea HRQ baja y se ceden momentáneamente los buses al
sistema. Después, vuelve a subir, y cuando se recibe el HLDA de la CPU se pueden realizar más
transferencias de este tipo. En la serie 8080 y 80x86, esto asegura al menos un ciclo para la CPU entre las
sucesivas transferencias del DMA.
3 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
Se diferencia del anterior en que en lugar de transferir una sola palabra se mueven todas las necesarias
hasta que el registro contador de palabras se desborda. Lógicamente, también se acaba el proceso si alguien
actúa sobre la patilla -EOP. DREQ sólo es preciso activarlo hasta que DACK responde.
Se diferencia del anterior en que la transferencia se realiza sólo mientras DREQ permanece activo. Esto
significa que se pueden transferir datos hasta agotar las posibilidades del dispositivo; cuando el dispositivo
tenga más datos listos puede volver a activar DREQ para continuar donde lo dejó. Esta modalidad permite
dejar ciclos a la CPU cuando no es realmente necesario que el DMA opere. Además, en los períodos de
inactividad, los valores de dirección en curso y contador de palabras son almacenados en el Registro de
direcciones en curso y en el Registro contador de palabras en curso correspondientes al canal
implicado; mientras tanto, otros canales de mayor prioridad pueden ser atendidos por el 8237.
Esta conexión es empleada para conectar más de un 8237 en el sistema. La línea HRQ de los 8237 hijo es
conectada a la DREQ del 8237 padre; la HLDA lo es a la DACK. Esto permite que las peticiones en los
diversos 8237 se propaguen de uno a otro a través de la escala de prioridades del 8237 del que cuelgan. La
estructura de prioridades es por tanto preservada. Teniendo en cuenta que el canal del 8237 padre es
empleado sólo para priorizar el 8237 adicional que cuelga (hijo), no puede emitir direcciones ni señales de
control por sí mismo: esto podría causar conflictos con las salidas del canal activo en el 8237 hijo. Por tanto,
el 8237 padre se limita en el canal del que cuelga el 8237 hijo a controlar DREQ, DACK y HRQ, dejando
inhibidas las demás señales. El -EOP externo será ignorado por el 8237 padre, pero sí tendrá efecto en el
8237 hijo correspondiente.
Cuando de un 8237 cuelga otro, estamos ante un sistema DMA de dos niveles. Si del DMA hijo cuelga a
su vez otro, sería un sistema DMA de tres niveles, como el mostrado a continuación:
Al programar los 8237 en cascada, se debe empezar por el primer nivel. Tras un Reset, las salidas DACK
son programadas por defecto para ser activas a nivel bajo y son colocadas en alto. Si están conectadas
4 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
directamente a HLDA, el segundo nivel de 8237 no puede ser programado hasta que la polaridad de DACK
no se cambie para que sea activa a nivel alto. Los bits de máscara de canales del 8237 padre funcionan como
cabría esperar, permitiendo inhibir 8237's de niveles inferiores.
Modos de transferencia.
Cada uno de los 3 modos de transferencia puede realizar 3 tipos distintos de transferencias: lectura,
escritura y verificación. La lectura pasa datos de la memoria al dispositivo E/S (activando -IOW y -MEMR);
la escritura mueve datos desde los dispositivos E/S a la memoria (activando -IOR y -MEMW). Las
transferencias de tipo verificación son pseudotransferencias: el funcionamiento es similar a la lectura o escritura
pero sin tocar las líneas de control de la memoria ni de los periféricos; durante el modo de verificación se
ignora la línea READY; este modo no es permitido en las operaciones memoria-memoria.
Autoinicialización.
Cualquier canal puede ser programado para incluir esta característica. En el momento de programar el
chip, los registros base de dirección y base contador de palabras son cargados a la vez y con el mismo valor
que los registros de dirección en curso y contador de palabras en curso. Los registros base permanecen
inalterados en todo momento, por lo que al final del servicio sirven, en este modo de trabajo, para recargar de
nuevo los registros en curso. Esto sucede justo tras la señal -EOP, quedando el 8237 listo para repetir de
nuevo la misma transferencia (cuando se solicite a través de la línea DREQ o por software). En esta
modalidad, los bits de máscara están a 0.
Memoria-Memoria.
En este tipo de transferencia se emplean siempre los canales 0 y 1. La transferencia comienza activando la
línea DREQ del canal 0, bien por hardware o por software. El 8237 solicita entonces un servicio de DMA
ordinario, con el que lee el byte de la memoria a través de 4 estados y empleando el Block Transfer Mode
visto con anterioridad. El registro de dirección en curso del canal 0, que indica la dirección origen en la
memoria, es incrementado/decrementado (según haya sido programado) y el dato es almacenado en el
registro temporal del 8237. En otros 4 estados más, el dato es pasado del 8237 de nuevo a la memoria,
usando la dirección del registro de dirección en curso del canal 1, que indica la dirección destino en memoria,
el cual es también incrementado/decrementado según proceda. Además, se decrementa el registro contador
de palabras en curso del canal 1: si al decrementar se desborda (pasa de 0 a 0FFFFh) se activa el bit TC del
registro de estado (Terminal Count, fin de cuenta) y se genera un pulso -EOP, finalizando el proceso. En el
caso de que el valor del registro contador de palabras del canal 0 pase de 0 a 0FFFFh, sin embargo, no se
actúa sobre TC ni sobre EOP (no finaliza el proceso) aunque este canal se autoinicializa si así estaba
programado.
Si se desea una autoinicialización total en este tipo de transferencias, los registros contadores de palabras
del canal 0 y 1 han de ser programados con el mismo valor inicial; de lo contrario, sólo uno de los dos canales
se autoinicializará (el que primero desborde su registro contador de palabras).
El canal 0 puede ser también programado para retener siempre la misma dirección durante todas las
transferencias, lo que permite copiar un mismo byte en todo un bloque de la memoria.
El 8237 puede responder a señales -EOP externas durante este tipo de transferencias, pero sólo cede el
control de los buses después de completar la transferencia de la palabra que tenga entre manos. Los circuitos
para comparar datos en búsquedas de bloques pueden emplear -EOP para terminar la operación tras
encontrar lo que buscan. Las operaciones memoria-memoria se pueden detectar por hardware como una
combinación de AEN activo sin que al mismo tiempo se produzcan salidas DACK.
5 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
Prioridad.
El 8237 tiene dos maneras de codificar la prioridad, seleccionables por software. La primera es la
prioridad fija, basada en el número del canal (0-máxima, 3-mínima). Una vez que un canal es atendido, los
demás esperan hasta que acabe. La segunda modalidad es la prioridad rotatoria: el último canal servido pasa
a tener la menor prioridad y el que le sigue la máxima. La rotación de prioridades se produce cada vez que se
devuelven los buses a la CPU. Esta última modalidad de prioridad asegura que un canal sea atendido al
menos después de haber atendido los otros 3, evitando que un solo canal monopolice el uso del DMA. Con
independencia del tipo de prioridad programada, ésta es evaluada cada vez que el 8237 recibe un HLDA.
Compresión de tiempo.
De cara a mejorar el rendimiento en los sistemas más potentes, el 8237 puede ser programado para
comprimir el tiempo de transferencia a dos ciclos de reloj. En cualquier caso, esta posibilidad no está
disponible en las transferencias memoria-memoria.
Generación de direcciones.
Para reducir el número de pines, el 8237 tiene multiplexada la parte alta del bus de direcciones. En el
estado S1, los 8 bits más significativos de la dirección son depositados en un latch externo a través del bus de
datos. La línea AEN indica a la circuitería externa que debe habilitar el latch como parte alta del bus de
direcciones cuando llega el momento (la parte baja la suministra directamente el 8237). En el Block Transfer
Mode y en el Demand Transfer Mode, que implican múltiples transferencias, el 8237 es suficientemente
inteligente como para generar estados S1 sólo cuando hay acarreo en la parte baja del bus de direcciones (1
de cada 256 veces) evitando acceder al latch externo cuando no es necesario modificarlo y ahorrando
tiempo.
El 8237 puede ser programado cuando HLDA está inactivo, siendo responsabilidad del programador que
esto sea así (es decir, programarlo antes de que comience a operar). En cualquier caso, puede existir el riesgo
de que mientras se programa un canal, se produzca una petición de DMA en el mismo antes de acabar la
programación, y probablemente en un punto crítico (cuando, por ejemplo, se acababa de enviar la mitad de
un valor de 16 bits). Para evitar este riesgo, antes de comenzar a programar un canal puede ser necesario
enmascararlo, desinhibiéndolo después.
Cada canal tiene un registro de dirección en curso que almacena la dirección de memoria empleada
durante las transferencias del DMA. Su contenido es incrementado/decrementado después de cada
transferencia. Este registro es inicializado por la CPU enviando dos bytes consecutivos; en modo
autoinicialización, su contenido inicial se restaura cuando ésta se produce.
Cada canal tiene un registro contador de palabras en curso, que determina el número de bytes a transferir
en la operación menos uno (para un valor inicial 100, por ejemplo, se transmiten 101 bytes). Tras cada
transferencia se decrementa: cuando pasa de 0 a 0FFFFh se genera el TC (Terminal Count) y el proceso
finaliza. Este registro es inicializado por la CPU enviando dos bytes consecutivos; en modo autoinicialización,
su contenido inicial se restaura cuando ésta se produce; de lo contrario continúa con un valor 0FFFFh.
6 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
Base Address & Base Word Count Registers (Registros base de dirección y base contador de
palabras).
Cada canal tiene también un registro base de dirección y otro base contador de palabras. Estos registros
almacenan el valor inicial de los registros de dirección en curso y contador de palabras en curso, ya que
ambos tipos de registros se cargan simultáneamente durante la programación. El valor almacenado en estos
registros se emplea en la autoinicialización, para recargar los registros en curso.
+-------+-------------------------------------------------------------+----------+----------
| Canal | Registro(s) | | Direcció
| | | | A3 A2 A1
+-------+-------------------------------------------------------------+----------+----------
| | Base de dirección y de dirección en curso | Escribir | 0 0 0
| 0 | De dirección en curso | Leer | 0 0 0
| | Base contador de palabras y contador de palabras en curso | Escribir | 0 0 0
| | Contador de palabras en curso | Leer | 0 0 0
+-------+-------------------------------------------------------------+----------+----------
| | Base de dirección y de dirección en curso | Escribir | 0 0 1
| 1 | De dirección en curso | Leer | 0 0 1
| | Base contador de palabras y contador de palabras en curso | Escribir | 0 0 1
| | Contador de palabras en curso | Leer | 0 0 1
+-------+-------------------------------------------------------------+----------+----------
| | Base de dirección y de dirección en curso | Escribir | 0 1 0
| 2 | De dirección en curso | Leer | 0 1 0
| | Base contador de palabras y contador de palabras en curso | Escribir | 0 1 0
| | Contador de palabras en curso | Leer | 0 1 0
+-------+-------------------------------------------------------------+----------+----------
| | Base de dirección y de dirección en curso | Escribir | 0 1 1
| 3 | De dirección en curso | Leer | 0 1 1
| | Base contador de palabras y contador de palabras en curso | Escribir | 0 1 1
| | Contador de palabras en curso | Leer | 0 1 1
+-------+-------------------------------------------------------------+----------+----------
Direcciones E/S de los registros de direcciones y contad
Es un registro de 8 bits que controla el funcionamiento del 8237. Se borra tras un Reset o un comando
Master Clear:
7 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
Cada canal tiene un registro de modo asociado, de 6 bits. Cuando se escribe el registro de modo, se envía
un byte al 8237 que selecciona (en los bits 0 y 1) el canal cuyo registro de modo se desea escribir, y el resto
de los bits cargan el registro de modo. Cuando se lee, dichos bits estarán a 1 (para leer un registro de modo
hay que utilizar antes el comando Clear Mode Register Counter, como se verá en la sección de comandos).
El 8237 puede responder a peticiones de DMA tanto por hardware (línea DREQ) como por software. En
este registro posee un bit para cada canal de DMA. Las peticiones por software no se pueden enmascarar,
aunque están sujetas a la lógica de evaluación de prioridades. Cada bit de este registro es activado o borrado
selectivamente por software. Todo el registro es borrado ante un Reset. Para modificar sus bits, se debe
enviar el comando Write Request register. Si se lee el registro, los bits 0 al 3 muestran el estado de las
peticiones en los canales 0 al 3 (los demás bits están a 1). Las peticiones de DMA por software pueden serlo
indistintamente en el modo single o en el block. Para operaciones memoria-memoria, hay que hacer una
petición de DMA por software en el canal 0.
Cada canal tiene asociado un bit de máscara que puede ser activado para inhibir las solicitudes de DMA a
través de la línea DREQ. Este bit es automáticamente activado cada vez que se produce un -EOP (al final de
la transferencia) a menos que el canal esté en modo autoinicialización. Cada bit de máscara puede ser
modificado por separado, o todos a la vez, con el comando apropiado. Todo el registro es puesto a 1 a
través del comando Master Clear o debido a un Reset, lo que inhibe las solicitudes de DMA por hardware
hasta que se envía un comando para limpiar el registro de máscara (o se borran los bits que se desee en el
mismo). Existen tres órdenes para actuar sobre el registro de máscara; la primera es a través del comando
Clear Mask Register, que borra todos los bits de máscara; la segunda es por medio del comando Write
Single Mask Bit, modificando un solo bit; la tercera forma consiste en los comandos Read y Write All Mask
Bits, con los que se pueden consultar y alterar todos los bits de máscara a la vez.
Contiene información de estado lista para ser leída por la CPU. Los bits 0 al 3 indican si los respectivos
canales han alcanzado un TC (Terminal Count) o se les ha aplicado una señal -EOP externa. Estos bits se
borran ante un Reset, un comando Master Clear o, simplemente, al leer el propio registro de estado. Los bits
4 al 7 indican qué canales están solicitando servicio, con independencia de que estén enmascarados o no. De
esta manera, enmascarando todos los canales y leyendo el registro de estado, por software se puede decidir
qué canales conviene desenmascarar, pudiendo el sistema operativo aplicar la gestión de prioridades que
desee llegado el caso. Estos bits (4 al 7) son actualizados cuando el reloj está en alto; un Reset o un comando
Master Clear los borran.
8 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
Se emplea para contener los bytes que se transfieren en las operaciones memoria-memoria. Tras
completar el proceso de transferencia, la CPU puede averiguar la última palabra transferida leyendo este
registro, a no ser que el registro haya sido borrado por un Reset o un comando Master Clear.
A continuación se citan algunos comandos especiales que pueden ser ejecutados leyendo o escribiendo
sobre el 8237. A diferencia de cuando hay que acceder a los registros de direcciones y contadores, aquí el bit
A3 está activo. Por tanto, de los 16 puertos de E/S que ocupa el 8237 en cualquier sistema, los 8 últimos
están relacionados con los comandos y los registros especiales. En el siguiente cuadro se recogen todos, y
después se explican los más confusos.
+-----------------------------------------------------------------------+----------+--------
| Comando | Modo de | Direcc
| u operación | acceso | A3 A2 A
+-----------------------------------------------------------------------+----------+--------
| Read Status Register (leer registro de estado) | Leer | 1 0
| Write Command Register (escribir registro de comandos) | Escribir | 1 0
| Read Request Register (leer registro de petición de DMA) | Leer | 1 0
| Write Request Register (escribir registro de petición de DMA) | Escribir | 1 0
| Read Command Register (leer registro de comandos) | Leer | 1 0
| Write Single Mask Bit (escribir un solo bit de máscara de DMA) | Escribir | 1 0
| Read Mode Register (leer registro de modo) | Leer | 1 0
| Write Mode Register (escribir registro de modo) | Escribir | 1 0
| Set Byte Pointer F/F (activar flip-flop primero/último) | Leer | 1 1
| Clear Byte Pointer F/F (borrar flip-flop primero/último) | Escribir | 1 1
| Read Temporary Register (leer registro temporal) | Leer | 1 1
| Master Clear (inicialización principal) | Escribir | 1 1
| Clear Mode Register Counter (limpiar contador de registro de modo) | Leer | 1 1
| Clear Mask Register (borrar registro de máscara de DMA) | Escribir | 1 1
| Read All Mask Bits (leer todos los bits de máscara de DMA) | Leer | 1 1
| Write All Mask Bits (escribir todos los bits de máscara de DMA) | Escribir | 1 1
+-----------------------------------------------------------------------+----------+--------
Direcciones E/S de los co
Dado que los valores de 16 bits se envían de dos veces, existe un flip-flop interno que permite al 8237
conocer si lo que le llega es la primera mitad del dato o la segunda. Por precaución, se puede borrar primero
para asegurar que el primer byte enviado se interprete como el menos significativo y, el segundo, como el más
significativo.
Dado que los valores de 16 bits se envían de dos veces, existe un flip-flop interno que permite al 8237
conocer si lo que le llega es la primera mitad del dato o la segunda. Por precaución, se puede activar primero
9 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
para asegurar que el primer byte enviado se interprete como el más significativo y, el segundo, como el menos
significativo.
Este comando tiene el mismo efecto que un Reset hardware. Los registros de comando, estado, petición
de DMA, temporales y los flip-flops internos (first/last y mode register counter) son puestos a cero, siendo
el registro de máscaras rellenado con bits a 1 (inhibir canales). El 8237 entra en estado inactivo.
El comando Write es empleado para escribir al registro de petición de DMA y provocar una petición de
DMA por software; también se puede utilizar Read para consultar su estado: los bits 0 al 3 muestran entonces
el estado de las peticiones en los canales 0 al 3 (los demás bits están a 1). El formato para escribir es el
siguiente:
Este comando limpia los bits de máscara de los 4 canales, habilitándoles para recibir peticiones de DMA
por hardware.
Read/Write All Mask bits (leer/escribir todos los bits de máscara de DMA).
Este comando permite consultar o establecer el estado de todos los bits de máscara de DMA a la vez, en
los 4 canales.
10 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
Todos los ordenadores compatibles vienen equipados con un 8237 accesible a partir de la dirección E/S
base 0. Es por tanto el chip del ordenador donde resulta más fácil traducir las direcciones E/S de las tablas
técnicas del fabricante a la dirección del espacio de E/S del PC.
Los AT y PS/2 poseen un 8237 adicional, accesible a partir de la dirección E/S 0C0h. Los puertos están
direccionados en intervalos de 2, al repetirse en dos direcciones adyacentes (esto permite en los IBM y otros
muchos hacer un OUT de 16 bits en lugar de dos consecutivos de 8, pero no todas las máquinas lo soportan).
En los AT, este 2º controlador de DMA actúa como maestro y está encargado de las operaciones de 16 bits;
su canal 0 es empleado para colgar de él otro 8259 que realiza las operaciones de 8 bits, por compatibilidad
con el PC. Por ello, los AT poseen 7 canales de DMA, frente a los 4 de los PC/XT.
La siguiente tabla resume todos los puertos de entrada y salida a emplear para acceder a ambos
controladores de DMA (el de 16 bits, recuérdese, sólo disponible en AT):
+-------------------------------+---------------------+---------+---------+
| Comando o registro | Modo de acceso | 8 bits | 16 bits |
+-------------------------------+---------------------+---------+---------+
| Registro dirección canal 0 | lectura y escritura | 00 | C0 |
| Registro de cuenta canal 0 | lectura y escritura | 01 | C2 |
| Registro dirección canal 1 | lectura y escritura | 02 | C4 |
| Registro de cuenta canal 1 | lectura y escritura | 03 | C6 |
| Registro dirección canal 2 | lectura y escritura | 04 | C8 |
| Registro de cuenta canal 2 | lectura y escritura | 05 | CA |
| Registro dirección canal 3 | lectura y escritura | 06 | CC |
| Registro de cuenta canal 3 | lectura y escritura | 07 | CE |
| Status Register | lectura | 08 | D0 |
| Command Register | escritura | 08 | D0 |
| Request Register | lectura y escritura | 09 | D2 |
| Command Register | lectura | 0A | D4 |
| Single Mask Bit | escritura | 0A | D4 |
| Mode Register | lectura y escritura | 0B | D6 |
| Set Byte Pointer F/F | lectura | 0C | D8 |
| Clear Byte Pointer F/F | escritura | 0C | D8 |
| Temporary Register | lectura | 0D | DA |
| Master Clear | escritura | 0D | DA |
| Clear Mode Register Counter | lectura | 0E | DC |
11 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
Los PC/XT utilizan el canal 0 de su 8237 para el refresco de la memoria, el 2 para los disquetes y el 3
para el disco duro. El único canal que queda libre es el 1.
Sin embargo, en los AT el panorama cambia bastante. El 8237 encargado de las transferencias de 8 bits
(esclavo) que cuelga del que controla las transferencias de 16 bits (maestro) define los canales 0 al 3, de los
cuáles sólo el canal 2 está ocupado en las operaciones de disquetes, al igual que los PC/XT. El 8237
encargado de las operaciones de 16 bits define los canales 5, 6 y 7 (el 4 está ocupado en colgar de él el otro
8237), estando todos ellos libres. La razón es que en los AT la memoria no se refresca por el DMA y el disco
duro por lo general se accede directamente, también sin DMA. Por tanto, en estas máquinas quedan nada
menos que 6 canales de DMA libres (el 0, 1 y 3 del DMA de 8 bits y el 5, 6 y 7 del DMA de 16 bits).
Seguramente, el lector se habrá dado cuenta de que los registros de direcciones del DMA son de 16 bits,
mientras que la serie 80x86 puede direccionar entre 1 Mb y 4 Gb de memoria. Si tiene algo de sentido
común, se le habrá ocurrido la pregunta: ¿Cómo es posible entonces que el DMA acceda a la memoria del
ordenador, con direcciones de 20 a 32 bits?. La solución técnica adoptada por los diseñadores del PC
consistió en añadir unos registros externos, ubicados fuera del 8237, que se encargan de suministrar los bits
de direcciones que faltan: son los denominados registros de página de DMA, habiendo uno por cada canal.
En los PC/XT, los registros de página de DMA poseen sólo 4 bits significativos y generan la parte alta de
la dirección de memoria. En los AT, son significativos los 8 bits completos del registro de página de DMA en
el 8237 que controla las operaciones de 8 bits y 7 en el que gestiona las operaciones de 16 bits. El siguiente
esquema muestra cómo se generan las direcciones de memoria:
Los restantes bits del espacio de direcciones (líneas A24 a A31 del 386) no se pueden emplear, de ahí
que algunas implementaciones de Unix tuvieran problemas para soportar más de 16 Mb de memoria.
En general, desde el punto de vista del DMA, se puede imaginar la memoria como 16 bloques de 64 Kb
(caso del PC/XT), como 256 bloques de 64 Kb (en accesos de 8 bits en el AT) o bien como 128 bloques de
128 Kb (en accesos de 16 bits también en el AT). En el DMA que trabaja con 16 bits, se transfieren sólo
palabras (65536 palabras = 128 Kb) y siempre en direcciones pares, de ahí que A0=0.
Nota: Con los controladores de memoria expandida actuales (EMM386), los diseñadores
han sido suficientemente cautos como para colocar los primeros 640 Kb de la memoria virtual justo en
los primeros 640 Kb de memoria física del ordenador. La memoria de pantalla y la de la tarjeta VGA
también están en su sitio. Por tanto, bajo las últimas versiones del DOS es factible (y probablemente
lo seguirá siendo) programar directamente el DMA para realizar transferencias sobre la memoria
12 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
normal. Sin embargo, sobre la memoria superior tampoco hay problemas. Aunque la dirección virtual
ya no coincide con la física, cuando se ejecuta una instrucción OUT sobre un registro de página, el
controlador de memoria detecta la circunstancia, ya que al parecer está protegido el acceso a esos
puertos. A continuación, averigua qué instrucción ha provocado la excepción y modifica
convenientemente el valor con el que se pretendía hacer OUT para adecuarlo a la dirección de
memoria física y permitir que siga funcionando. Esto explica por qué una instrucción de E/S sobre uno
de estos puertos puede tardar nada menos que ¡1000 ciclos! en un 386.
La BIOS del AT inicializa los 8237 con un valor 0 en el Command Register. Casi todos los canales son
establecidos por defecto (y así permanecen cuando no se usan) en el modo single, transferencia de
verificación, autoinicialización inhibida y modo incremento. Por ello, en el 8237 esclavo se escribe el valor
40h en el registro de modo del canal 0, el 41h en el canal 1, el 42h en el canal 2 y el 43h en el canal 3. En el
8237 maestro, el registro de modo del canal 4 (canal 0 de este chip) se programa con 0C0h, que equivale al
modo cascada; los demás canales se programan como en el otro 8237. El siguiente listado ha sido extraído
directamente de la BIOS del AT:
La BIOS del PC/XT inicializa el canal 0 del DMA para el refresco de la memoria. El refresco de las
memorias dinámicas consiste en ir leyéndolas con suficiente rapidez como para que no se borre su contenido;
en realidad, dada su organización en filas y columnas, se puede refrescar a la vez un gran número de bytes
leyendo uno sólo. Para una memoria de 1 Mb, basta con acceder a cualesquiera 1024 posiciones de memoria
consecutivas, cada menos de 4 milisegundos, para garantizar la fiabilidad del sistema. Para ello, el canal 0 del
DMA es colocado en modo single, en modo incremento de direcciones, con autoinicialización y en modo
transferencia de lectura (enviando el valor 58h al registro de modo). A continuación, dicho canal es
desenmascarado, comenzando el refresco de la memoria. La razón es que la salida del contador 1 del
temporizador 8253 está conectada a la línea de petición del canal 0 del DMA, por lo que periódicamente el
8237 sustrae el control de los buses al 8086 para continuar el refresco por la dirección de memoria en que se
llegara (el contador 1 del 8253 está programado con una cuenta 18, igual que en los AT: aunque éstos últimos
no refrescan la memoria por DMA utilizan una base de tiempos compatible). El registro de página del canal 0
no existe en los PC/XT; sin embargo, debido al diseño de la placa, es el registro de página del canal 3 el que
actúa. En cualquier caso, es indiferente la dirección de memoria base empleada para refrescar. Los restantes
canales DMA, así como el Command Register, son programados del mismo modo que sus colegas en el AT.
13 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
Cierto célebre libro de soluciones para programadores de compatibles afirma en la página 328 que los AT
emplean el DMA automáticamente en las instrucciones MOVS para mejorar el rendimiento. Fuera del ámbito
de la ciencia-ficción, aquí propondremos otro uso no más común pero, en cambio, factible: ralentizar el
funcionamiento de los ordenadores AT. La auténtica utilidad del DMA, conviene recordarlo, está ligada al
acceso a los disquetes, aunque de ello hay ejemplos en el apartado donde se trata la programación del
NEC765.
El truco, cuya idea original hay que atribuir a Jesús Arias, consiste en programar un canal en modo
autoinicialización, para que se ponga a trabajar continuamente. Programándolo en modo single, le va robando
ciclos a la CPU de manera continua. En teoría, en el modo block se debería quedar bloqueado el ordenador,
aunque las máquinas en donde lo he probado esto no sucede. En los PC/XT no conseguí un resultado exitoso,
además de que no tiene mucho sentido hacerlos más lentos. Sin embargo, en los AT es bastante sencillo el
proceso y funciona en todas las máquinas en que se probó. A la hora de elegir un canal, se puede optar por el
0, 1, 3, 5, 6 ó 7. Casi todos son válidos, pero el 0 y 1 no son recomendables: son los canales de más
prioridad y, si se utilizan para ralentizar el ordenador, las disqueteras dejan de funcionar (utilizan el canal 2).
Este es otro de los motivos por los que no es conveniente hacer esto en los PC/XT (su único canal disponible
es el 1). Por tanto, la elección queda relegada al canal 3 (de 8 bits) o al 5, 6 ó 7 (de 16 bits). De esta manera,
los disquetes pueden continuar funcionando, ya que su canal de DMA toma el control cuando es necesario
debido a su mayor prioridad.
Resulta interesante observar cómo ralentiza más emplear un canal de 8 bits que uno de 16: en el sistema
386-25 donde lo probé, el famoso test de velocidad de LANDMARK estima la velocidad habitualmente en
27,8 MHz. Poniendo en marcha el canal 7, de 16 bits, la velocidad cae nada menos que a 7,3 MHz;
utilizando el 3 (de 8 bits) baja a 6,3 MHz. Combinando ambos canales a la vez, el descenso es aún mayor,
hasta los 4,3 MHz.
Las tradicionales utilidades de dominio público para ralentizar los AT suelen emplear la interrupción del
temporizador, parando por completo el ordenador durante algunos instantes y dejándole a toda velocidad el
resto del tiempo. La ventaja de ralentizar por DMA es que el ordenador baja la velocidad de una manera
uniforme y no va a saltitos. Por otro lado, ralentiza también los juegos que controlan por su propia cuenta la
interrupción del temporizador. Además, casi ningún programa comercial se ocupa de programar los canales
del DMA, ni el propio BIOS toca los que no le incumben; por ello, una vez activado, es seguro que el efecto
durará cuanto desee el usuario. Por último, el método es aún más elegante porque ni siquiera se trata de un
programa residente: ¡consume 0 bytes!.
Combinando el método de ralentización por DMA con un aumento de los ciclos de refresco de la memoria
(a través del canal 1 del 8254) se puede bajar todavía aún más la velocidad, de manera también uniforme. En
concreto, en la máquina citada anteriormente, si se programa el canal 1 del 8254 con un valor de cuenta 2 la
velocidad cae a 1,4 MHz, según el test de Landmark: los ciclos de refresco de memoria castigan mucho a la
CPU cuando la restan pocos MHz...
El inconveniente de ralentizar demasiado, combinando los dos métodos citados, es que el teclado
14 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
comienza a fallar en mayor o menor medida (se enganchan las teclas de Shift y Ctrl, siendo preciso pulsarlas
de vez en cuando para desengancharlas; aparecen números en los cursores expandidos...). En el siguiente
programita de demostración, existen dos niveles de freno seleccionables. Utiliza el peor método para
comprobar si el ordenador es un AT, a través del byte de identificación de la ROM (es 0FCh en un gran
número de ATs y 0F8h en los PS/2-80), aunque es sin duda una de las maneras más rápidas de hacerlo. Las
funciones dmako() se encargan de poner K.O. el canal correspondiente, activando el DMA. Las recíprocas
dmaok() devuelven el canal asociado a la normalidad, inhibiendo el DMA.
#include <dos.h>
#include <string.h>
void
dmacnt(), dmako3(), dmako7(), dmaok3(), dmaok7();
if ((argc<2) || ((nivel=atoi(argv[1]))>3)) {
printf("\n ");
printf("Indicar nivel de freno (1, 2 ó 3) ó 0 para acelerar.\n");
exit (2);
}
dmacnt();
if (nivel==1) {
dmaok3(); dmaok7(); dmako7();
printf ("\n Ralentización moderada activa.\n");
}
else if (nivel==2) {
dmaok3(); dmaok7(); dmako3();
printf ("\n Ralentización elevada activa.\n");
}
else if (nivel==3) {
dmako3(); dmako7();
printf ("\n Ralentización máxima activa.\n");
}
else {
dmaok3(); dmaok7();
printf ("\n Ralentización desactivada.\n");
}
}
void dmacnt()
{
outportb(0x07, 0xFF); /* cuenta del canal 3 a 0xFFFF */
outportb(0x07, 0xFF);
outportb(0xCE, 0xFF); /* cuenta del canal 7 a 0xFFFF */
outportb(0xCE, 0xFF);
}
15 de 16 12/10/00 19:18
EL CHIP DMA 8237 file:///C|/librosVirtuales/UniversoDigital/1205.html
Velocidad estimada
tras la ejecución
de DMAKO.C en un
AT 386-25. Datos
calculados con el
test de LANDMARK
Al emplear el DMA conviene tener cuidado con evitar un desbordamiento en el offset 0FFFFh de la
página de 64K empleada (DMA 8 bits). Esto se verá con más detalle en el apartado dedicado al controlador
de disquetes. Hay que tener en cuenta que una dirección segmentada aparentemente inocente puede estar
cruzando una frontera de DMA. Por ejemplo, 512 bytes contenidos a partir de 3FF2:0000 (que llegan hasta
3FF2:01FF) ocupan las direcciones físicas 3FF20 a la 4011F, estando contenidos en las páginas 3 y 4.
16 de 16 12/10/00 19:18
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
La superficie magnética de un disco está dividida en pistas concéntricas, en cualquiera de las cuales el
cabezal de lectura/escritura puede ser posicionado con ayuda de un motor paso a paso. Los únicos datos que
se almacenan en el disco son bits, como se verá. El cabezal de la unidad de disco es, en esencia, una bobina
en la que se verifican dos leyes fundamentales de la física electrónica: por un lado, una corriente alterna en
dicha bobina provoca un campo magnético que varía al mismo ritmo que la corriente (lo que permite
magnetizar la superficie del disco para grabar los datos); por otro lado, aplicando un campo magnético
variable de manera constante a la bobina se genera una tensión constante en la misma (lo que permite leer los
datos previamente registrados sobre esa superficie magnética, dejando el cabezal deslizarse sobre la misma).
A simple vista, por tanto, se podría intuir que registrar datos en un disco es una tarea sencilla: se podrían
representar los bits (a 1 ó 0) según la presencia/ausencia de magnetización en cada punto de la superficie. Sin
embargo, la electrónica y mecánicas de precisión necesarias para este tipo de grabación se escapan aún de las
posibilidades tecnológicas actuales. La solución adoptada consiste en registrar, junto a los bits de datos, una
frecuencia de reloj de referencia que permita localizar los bits sin problemas: entre dos registros magnéticos de
referencia en el disco (marcados con '*'), puede existir o no otro registro (que es lo que implica que el dato
sea un 1 ó un 0):
Esto es lo que se denomina grabación en simple densidad (MF). Al final, la superficie magnética se
puede considerar como un conjunto de pequeños imanes magnetizados en un sentido u otro: cuando se
recorra el disco con el cabezal en modo lectura, la variación magnética inducirá una corriente cuya
interpretación permitirá recuperar los datos grabados.
La electrónica de este sistema trabaja con dos tiempos básicos diferentes: el que transcurre entre dos
impulsos del reloj de referencia (bits a 0) y el que separa un impulso del reloj de referencia de los bit a 1. Un
impulso de referencia suele durar unos 500 nanosegundos y la distancia entre estos impulsos es de 8
microsegundos. Por ello, para un byte de datos son necesarios 64 microsegundos: como la disquetera da 300
vueltas por minuto, emplea 200 milisegundos en cada vuelta; esto significa que en cada pista podría almacenar
teóricamente 200000/64 = 3125 bytes. En un disco convencional de 80 cilindros y dos caras (160 pistas),
esto supone 500000 bytes; sin embargo, estos discos suelen almacenar 1.000.000 (doble densidad) y hasta
2.000.000 de bytes (alta densidad) antes de ser formateados (típicamente 720 Kb y 1,44 Mb tras el
formateo). ¿Cómo se las apañan para doblar o cuadruplicar los discos actuales esta capacidad?. La respuesta
consiste en emplear los formatos de doble y alta densidad, respectivamente.
La técnica de grabación en doble densidad (MFM) consiste en prescindir de los impulsos de referencia
en la medida de lo posible. El método se basa en no emplearlos para registrar bits a 1, o bien bits a 0
aislados: tan solo se usarán para registrar secuencias de varios bits consecutivos a 0 (de lo contrario, una
secuencia de bits a 0, sin impulsos de referencia, implicaría una pérdida de sincronización). Aquí existen ahora
tres tiempos diferentes: el intervalo elemental es el lapsus de tiempo entre dos bits a 1; un intervalo de doble
duración que éste representa la secuencia de bits 1-0-1; por último, un tercer lapso de tiempo
correspondiente a 1,5 intervalos de tiempo elementales es empleado para crear los impulsos de referencia
1 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
(marcados con '*') o abandonar su generación. Aunque en el gráfico no queda quizá muy claro, este método
permite grabar el doble de datos en un mismo intervalo de tiempo que el método de simple densidad:
Las unidades de alta densidad y las (ya difuntas) de extra alta densidad se basan en una mayor depuración
de la electrónica de control, que permite reducir los tiempos de los diversos intervalos.
La división del disco en pistas no es suficiente, ya que la cantidad de datos que almacenan es demasiado
elevada (unos 9 Kb por cada cilindro y cara en los discos de alta densidad actuales). Por tanto, se
comprende la necesidad de subdividir cada pista en unidades lógicas menores (sectores) de un tamaño
razonable, que puedan ser accedidas por separado. En esto consiste el proceso de formateo, en el que el
disco queda estructurado como se describirá a continuación. Se ha tomado como referencia el proceso de
formateo que realiza el FDC (Floppy Disk Controller) 765 de NEC en MFM (en MF varía ligeramente).
El disco posee una perforación de índice (el pequeño agujerito de la superficie) que es comprobada por
un sensor óptico, lo que permite detectar el inicio de la información grabada en cada pista. Nada más
comenzar la pista, hay 80 bytes con el valor 4Eh (ver esquema de la página siguiente): es lo que se denomina
el GAP 4A (GAP significa algo así como hueco o espacio). La razón de existencia de este pequeño área se
debe a la necesidad de sincronizar las distintas unidades de disco, ya que no todos los sensores ópticos
actúan de manera totalmente idéntica. Tras el GAP 4Ah se escriben 12 bytes a 0 en un área denominada
SYNC. La misión de estos bytes a cero es crear un área de marcas de sincronismo para que el controlador
de disco se sincronice con el reloj de referencia. Tras el campo SYNC viene un área especial de tres bytes
denominada Index Address Mark o IAM (marca de dirección índice), que existe sólo al principio de la pista.
Tras ella aparece un byte 0FCh y, detrás, un GAP 1, en esta ocasión de 50 bytes con el valor 4Eh: su misión
es dar tiempo a que el FDC procese la marca de dirección índice, que será decodificada e interpretada por
hardware. Después, a continuación vienen ya los sectores de datos del disco, que tienen todos el mismo
formato.
Los sectores comienzan por 12 bytes de SYNC (a 0), a los que sigue la ID Address Mark o ID-AM
(marca de dirección de identificación), también de 3 bytes. Detrás, un byte 0FEh. Tras todo esto, aparece el
campo de ID: son 4 bytes que contienen la siguiente información: número de cilindro, cara del disco, número
de sector y tamaño de sector (en la forma (LOG2 bytes_por_sector)-7). Esto permite identificar a cada
sector por separado. Por razones de seguridad, se realiza una comprobación CRC (especie de suma de
seguridad) de 16 bits entre la ID-AM y los 4 bytes del campo ID, cuyo resultado se almacena en los dos
bytes inmediatamente siguientes, con objeto de detectar futuros fallos en la integridad de la información. Para
dar tiempo al FDC a que se prepare para leer los datos que se vienen encima, hay después un nuevo GAP 2
de 22 bytes con el valor 4Eh. Entre otras razones, este área le sirve al FDC, en las operaciones de escritura,
para abandonar la lectura y prepararse para la inminente escritura (tarea que siempre lleva algo de tiempo).
Detrás vienen otros 12 bytes SYNC. Tras él otros 3 bytes: constituyen la DATA Address Mark o
DATA-AM (similar a la ID-AM o a la IAM) y, finalmente, un byte 0FBh. ¡Ahora sí!, tras ello vienen los
datos del sector: puede tener una longitud de 128, 256, 512, 1024, 2048 ó 4096 bytes (según haya sido
definido) que nada más ser formateado es inicializado con un valor seleccionable por el usuario. Por supuesto,
a este área de datos se le aplica también un algoritmo CRC (junto con los bytes de la DATA AM y el byte
0FBh) y los 2 bytes que se obtienen se graban a continuación. Finalmente, aparece el GAP 3, formado por
cierto número de bytes 4Eh seleccionable por el usuario al formatear (típicamente entre 54 y 116). Este último
2 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
GAP tiene una función muy importante: al escribir un sector en el disco, es difícil que la velocidad de la unidad
sea totalmente idéntica a la de la unidad que formateó el disco: si es menor, no sucede nada (el sector
ocuparía un pelo menos de disco) pero si es mayor, el GAP 3 evita que se invada el siguiente sector. Cuando
se escriben datos, el GAP 3 es mucho menor que cuando se formatea (del orden de la mitad de tamaño),
para asegurar que no se invadirá la zona del siguiente sector si la unidad es algo más rápida de lo previsto.
Los sectores se suceden unos tras otros hasta completar la pista. Después, el resto del espacio hasta que
aparezca de nuevo la perforación de índice se rellena con el GAP 4B final. Todo esto, en MFM (en MF, por
ejemplo, los bytes añadidos entre sectores por el 765 -excluyendo el GAP 3- no son 62 en total sino 31).
Este controlador de disquetes es un chip muy evolucionado que realiza tareas de un nivel relativamente
alto. Fabricado inicialmente por NEC, también lo comercializan Rockwell (R 6765) e Intel (i8272). Sus
principales características son: tamaño de sector programable (128, 256, 512, 1024, 2048 ó 4096 bytes),
posibilidad de programar todos los datos de las unidades, capacidad para controlar 4 disqueteras,
transferencia con o sin DMA, generación de interrupciones; es compatible con múltiples microprocesadores
(Z80, 8086,...) y trabaja con un reloj sencillo de una sola fase (4 u 8 Mhz). Soporta densidades MF (simple
densidad) y MFM (doble densidad) en unidades estándar de 3, 3½, 5¼ y 8 pulgadas.
SEÑALES DEL 765
3 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
Drive Select 0-1. También conocidas como US0-1 (Unit Select). Selecciona una de las cuatro disqueteras
DS0-1:
conectadas.
HDSEL: Head Select. Selecciona el cabezal en unidades de doble cara.
Head Load. Empleado para provocar el contacto físico del cabezal sobre el disquete o levantarlo.IDX:Index.
HDL:
Entrada del sensor óptico que detecta el inicio de la pista gracias a la perforación de índice del disquete.
Ready. Señal enviada por la disquetera indicando que el disco gira a velocidad adecuada (el FDC espera a
RDY:
que se cumpla RDY).
Write Enable. Salida que habilita la escritura de datos en el disquete.
WE: -RW/SEEK:Read Write/Seek. Algunas de las líneas que comunican el FDC con la disquetera tienen doble
función (para ahorrar patillas en el chip): esta señal permite elegir la función de las 4 siguientes patillas.
Fit Reset/Step. La función FR permite borrar el error de flip-flop de algunas unidades. La función STP, mucho
FR/STP:
más utilizada, mueve un paso (un cilindro) la cabeza de lectura/escritura (en la dirección que indica LCT/DIR).
Fault/Track0. La señal FLT es generada por algunas disqueteras en caso de error, pudiendo borrarse a través
FLT/TRK0: de la patilla anterior (FR/STP). La salida TRK0 indica cuándo el cabezal alcanza el cilindro 0, gracias a un
sensor óptico o mecánico, tras el comando de programación Seek o el de recalibración.
Low Current/Direction. La señal LCT es necesaria para limitar la corriente de escritura al acceder a los
LCT/DIR:
cilindros más internos, por razones físicas. DIR indica en modo Seek el sentido del movimiento del cabezal.
Write protect/Two Side. La señal WP indica si el disco está protegido contra escritura y es comprobada en
WP/TS: las operaciones de lectura/escritura; la señal TS se comprueba en las operaciones Seek y sólo es necesaria en
unidades de dos cabezales.
WR DATA: Write Data. Línea de entrada en serie de los datos de escritura (para escribir sector, para formatear,...).
Pre Shift 0-1 (Precompensation). En el formato MFM, el FDC indica a la circuitería electrónica adecuada cómo
PS0-1:
debe ser escrito el flujo de datos: para la precompensación caben tres estados posibles (Early, Normal y late).
RD DATA: Read Data. Entrada al FDC de datos en serie (bits) procedentes de la disquetera y leídos del disquete.
DW: Data Window. Señal obtenida en un separador de datos a partir de los datos leídos.
VCO: VCO Syn. Esta señal es precisa en el separador de datos PLL para el control del VCO.
MFM: MFM Mode. Indica al FDC si se trabaja en simple o doble densidad.
La única línea de direcciones del integrado (A0) define dos únicos puertos de E/S: el primero es el registro principal de
estado que sólo puede ser leído. A través del segundo puerto, de lectura/escritura, se accede al registro de datos, a través
del cual se programa el FDC, se envían y reciben los datos y se obtienen los resultados.
Con el FDC se trabaja en tres fases diferenciadas: la fase de comando u orden es empleada para enviar al FDC información
sobre lo que tiene que hacer, lo que puede implicar enviar hasta 9 bytes en algunos comandos. A continuación viene la fase
de ejecución. Finalmente, la fase de resultados puede obligar a leer del FDC hasta siete informaciones de estado diferentes
(hasta que no se leen, el FDC no admite más órdenes). Este es el esquema general, si bien algunas órdenes carecen de fase
de resultados, otras no tienen fase de ejecución...
El FDC dispone de 5 registros de estado internos. El principal puede ser accedido directamente como se vio (A0=0) en
cualquier momento. Los otros 4 registros (ST0, ST1, ST2 y ST3) sólo son accesibles en algunas órdenes y durante la fase de
resultados.
Para que el FDC lea los datos del disco hay que enviarle 9 bytes de información en la fase de órdenes. Este activa la señal
4 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
Head Load y espera el tiempo de Head Load programado. El FDC comienza a leer los ID's (identificadores) de los sectores
hasta encontrar el sector buscado, con lo que pasa a la fase de ejecución, o hasta encontrar por segunda vez la perforación
de índice del disco (en ese caso se pasa a la fase de resultados para dar el error). En la fase de ejecución, los datos son leídos
del disco y enviados al procesador o al DMA, a razón de un byte cada 8, 16, 26.67 ó 32 microsegundos (según la densidad
empleada: a 1000, 500, 300 y 250 Kbit/seg respectivamente). Tras acabar la transferencia del último byte del último sector hay
que dar un impulso en la patilla TC (Terminal Count) del 765 para evitar que siga leyendo los sectores que van detrás en el
proceso denominado multi-sector-read (se leen más sectores hasta llegar al final de la pista). En este comando, al igual que
en alguno más, se puede igualar el último sector de la pista al primero a ser accedido, pudiéndose prescindir en ese caso de la
señal TC al acceder a un solo sector. De todas maneras, al emplear el DMA, la transferencia finalizará realmente cuando el
registro contador del DMA alcanza el valor 0, al encargarse el propio controlador de DMA de activar la señal TC,
pudiéndose leer por tanto el número de sectores deseado. Personalmente he comprobado que el último número de sector en
la pista es más bien el último sector al que se desea acceder. Este comando produce 7 bytes en la fase de resultados, que
deben ser leídos obligatoriamente para que el FDC pueda admitir más órdenes.
Este comando es totalmente análogo al de lectura, pero actuando en escritura sobre el disco. La secuencia de bytes a
enviar y recibir es idéntica: sólo cambian algunos bits del primer byte de comando.
Por sector borrado se entiende aquel cuyo DATA-AM está borrado (por haber sido grabado dicho sector con el
comando Escribir Datos Borrados): estos sectores son ignorados en las operaciones normales de lectura y escritura,
aunque esta orden también permite leerlos. Por supuesto, esto no tiene relación alguna con la recuperación de ficheros
borrados en la unidad y la utilidad de este comando es bastante cuestionable.
Este comando graba sectores con el DATA-AM borrado, con objeto de que sólo puedan ser leídos con el comando Leer
5 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
Datos Borrados. La secuencia de bytes a enviar/recibir es idéntica al comando Leer Datos: sólo cambian algunos bits del
primer byte.
Este comando es similar a Leer Datos, se diferencia en que se leen todos los sectores de la pista (si el último número de
sector se indica correctamente) empezando cuando se detecta el paso de la perforación de índice (si el sector inicial indicado
no es realmente el primer sector de la pista, se producirá error). Aún en caso de error de CRC en el campo de ID o en el de
datos, se continúa leyendo la pista.
Este comando de 6 bytes realiza de manera automática y sin dar trabajo al programador todas las tareas necesarias para
inicializar una pista del disquete. Tras enviar el comando, habrá que pasar al FDC 4 bytes por cada sector que haya en la
pista a formatear: en ellos, para cada sector se indica el número de sector deseado, lo que permite numerar los sectores de
manera no consecutiva. El factor de Interleave 1:N de un disco equivale al número N de vueltas que hay que dar para
acceder una vez a toda la pista (depende de que los sectores estén numerados consecutivamente o no); elegir un interleave
óptimo es decisivo para mejorar el rendimiento (si la unidad gira lo bastante rápida como para que no de tiempo a acceder a
dos sectores físicamente consecutivos, el interleave debería ser mayor de 1:1; de lo contrario sería necesaria una vuelta
completa del disco cada vez que se accede a dos sectores de número consecutivo, que resulta ser además lo más frecuente).
El formateo comienza cuando el sensor correspondiente detecta el inicio de la pista (por la perforación de índice), por ello
todas las pistas quedan con los sectores colocados exactamente en la misma posición física: así, el sector N en una cara del
disco coincide en su posición con el de la otra y con el del cilindro adyacente (si se numeran todas las pistas igual, claro).
Este comando permite leer del disquete el siguiente ID que aparezca. El ID asociado a cada sector son los 4 bytes
asignados durante el formateo, y consiste en información relativa al número de cilindro, número de cabeza, número de sector
y tamaño del mismo. Estos números suelen coincidir con los valores físicos reales relacionados con la posición que ocupa el
sector en el disco, si bien se pueden falsear en técnicas de protección de datos, aunque los copiones más ordinarios
esquivan sin problemas estas trampas tan simples. Este comando consta de sólo 2 bytes; en la fase de resultado devuelve la
misma información que el comando Leer Datos (precisamente, la información solicitada).
6 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
El comando verificar (SCAN) permite al FDC comparar los datos almacenados en el disquete con un byte enviado por el
procesador. Hay 3 comandos Scan de verificación, que indican el modo de comparación por cada byte cotejado: igual, menor
o igual, mayor o igual. El comando finaliza cuando se cumple el criterio de comparación elegido en todo el sector dado,
cuando se comprueba el último sector de la pista o bien cuando se activa la patilla TC. La secuencia de bytes a enviar (9 en
total) y a recibir es casi idéntica al comando Leer Datos:
Este comando mueve el cabezal al cilindro 0 del disco. El FDC comienza a generar impulsos (por medio de la línea ST) para
mover el motor paso a paso hasta que se le informe que ya se ha alcanzado el cilindro 0 (a través de la patilla TRK0 del 765);
en cualquier caso, el comando finaliza tras enviar un máximo de 77 impulsos a la unidad (de ahí que pueda ser preciso
repetirlo en las actuales unidades de 80 cilindros, que siguen comportándose así por compatibilidad). Este comando carece
de fase de resultados (puede evaluarse el resultado por medio del registro de estado) y consta de sólo 2 bytes.
El 765 posee 4 registros internos que memorizan la posición del cabezal (sobre qué cilindro se halla) en las 4 unidades de
disco soportadas; tras el comando de recalibrado son puestos a 0. Cuando se envía este comando al FDC, para colocar el
cabezal sobre un cierto cilindro, éste comprueba si ya se encuentra sobre el mismo: en caso contrario, genera las señales de
control necesarias para instruir a la disquetera. Este comando no posee fase de resultados: para comprobar el éxito de la
operación hay que emplear la orden Leer Estado de Interrupciones obligatoriamente (de lo contrario, el FDC no aceptará
más órdenes de lectura o escritura). En cualquier caso, si la siguiente operación es de escritura, tras este comando hay que
hacer una breve pausa (15 ms vale) porque si el cabezal no ha dejado de vibrar acarrearía una escritura incorrecta (se
detectaría gracias al CRC en una lectura posterior, pero ¡casi nadie verifica tras escribir!: mejor asegurar que no hay error). Si
la siguiente operación es de lectura, no es necesaria dicha pausa ya que en caso de fallar, sería reintentada y no tendría
mayor consecuencia. Si se trata de seleccionar el otro cabezal en el mismo cilindro, después de haber posicionado el otro,
tampoco es necesaria pausa alguna. Abusar de las pausas podría acarrear una ralentización del acceso, al no hallarse en
ocasiones el sector buscado hasta la siguiente vuelta del disco. 3 bytes:
El 765 genera interrupciones al final de un comando Seek/Recalibrado o debido a un cambio en la señal RDY (Ready) de
alguna unidad; en modo NO-DMA las genera además al inicio de la fase de resultados y durante la fase de ejecución. Las
dos últimas causas pueden ser reconocidas con facilidad por el microprocesador, pero con las primeras es preciso emplear
este comando para conocer la causa con exactitud, gracias a los bits del registro ST0. Esta orden se compone de un solo
byte, devolviendo otros 2 en la fase de resultado:
7 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
Esta orden permite obtener el contenido del registro de estado ST3 de la unidad deseada, siendo éste el único medio de
conseguirlo. Consta de sólo dos bytes, obteniéndose un solo byte de resultado:
Aunque descrito en último lugar, este comando debería ser el primero ejecutado antes de comenzar las operaciones de
disco. Sirve para indicar si se va a trabajar con DMA o no, así como los tres tiempos básicos que regirán la operación del
chip. Estos tiempos están en función de la velocidad de reloj empleada, dependiente de la densidad de disco seleccionada. El
comando emplea 3 bytes y carece de fase de resultados.
Step Rate Time: Tiempo comprendido entre dos impulsos consecutivos en la señal que mueve el motor paso a paso del
cabezal (lo que determina el tiempo de acceso cilindro-cilindro). Depende de las características físicas de la unidad. El valor
para los bits SR se calcula con la fórmula (16-SR)*2 en unidades DD y con (16-SR) en unidades HD (tiempos expresados en
milisegundos).
Head Load Time: Tiempo de demora tras activar la señal Head Load, sólo relevante por lo general en unidades de 8" (en las
demás suele cargarse el cabezal nada más activarse la señal Motor On). El tiempo 'Head Load' (bits HL) se calcula con la
fórmula (HL+1)*4 en unidades DD y (HL+1)*2 en las unidades HD. La unidad de medida es el milisegundo.
Head Unload Time: Tiempo esperado, tras el último acceso al disco, hasta que la señal Head Load vuelva a ser inactiva (sólo
suele ser realmente significativo, una vez más, en las unidades de 8"). Las viejas unidades de 8" normalmente estaban
girando continuamente (para evitar sus lentas aceleraciones y frenados por la inercia) y levantar o bajar el cabezal era un
medio de protección de la superficie magnética. El tiempo 'Head Unload' (bits HU) se calcula con la fórmula HU*32 en
unidades DD y con HU*16 en unidades HD. La unidad de medida es el milisegundo.
Como se comentó, el 765 dispone de 5 registros de estado: el registro principal de estado, que puede ser accedido en
cualquier momento; los registros ST0, ST1 y ST2 que se obtienen como resultado de diversas órdenes; y el registro ST3. Los
registros ST1 y ST2 no se pueden leer directamente (sólo se obtienen como resultado de algunas órdenes), pero ST0 y ST3
pueden ser leídos con un comando al efecto.
8 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
En este registro se representan en todo momento los datos más importantes sobre el estado del FDC. Sirve también para
regular la comunicación entre el microprocesador y el FDC. Significado de sus bits:
Request For Master (listo para E/S). Cuando este bit está a 1, el FDC está listo para
Bit 7 (RQM): recibir o enviar bytes a través del registro de datos; en caso contrario no es posible la
transferencia.
Data Input/Output (entrada/salida de datos). Cuando este bit está a 1, significa que el FDC
Bit 6 (DIO): tiene un byte preparado para el procesador. Cuando está a 0, quiere decir que está
esperando un byte del procesador. Este bit no es válido hasta que RQM=1.
Non DMA Mode (Modo no-DMA). En modo no DMA estará a 1 si empezó la fase de
Bit 5 (NDM):
ejecución; pasa a valer 0 cuando dicha fase finaliza.
FDC Busy (FDC ocupado). Cuando está a 1, el FDC está elaborando una orden de
lectura o escritura y, por tanto, no puede procesar más comandos. Este bit se pone a 1
bit 4 (CB):
nada más recibir el primer byte de un comando, y baja cuando es leído el último byte de
resultados.
FDD0..3 Busy (unidad ocupada). Cada bit está asociado a una unidad (de la A:-D:).
Cuando se inicia un comando Seek o un recalibrado en alguna unidad, su bit se activa:
mientras alguno de estos bits esté a 1, no se podrán enviar órdenes de lectura o escritura al
Bits 0..3 (DB):
FDC, pero sí más comandos Seek o de recalibrado de las demás unidades. Estos bits no
se ponen a 0 por sí solos: se borran enviando el comando Leer Estado de Interrupciones
(si había finalizado ya el comando Seek o el recalibramiento).
Este registro se denomina también registro de estado de interrupciones, ya que en modo no DMA permite
identificar la causa de las interrupciones.
9 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
Este registro informa, durante la fase de resultados, sobre el desarrollo de la fase de ejecución de los
diversos comandos.
10 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
End of Cylinder. Este bit se pone a 1 si se intenta acceder a un sector tras alcanzar el fin de
Bit 7 (EN):
pista programado.
Bit 6: No utilizado (a 0).
Data Error (error de datos). Se pone a 1 si al leer los datos y calcular su CRC (o al calcular el
Bit 5 (DE): CRC de los campos de ID), éste no coincide con el CRC almacenado en el disco junto a
dichos datos óIDs cuando fueron grabados.
Overrun (excedido el tiempo de transferencia). Los datos transitan entre el microprocesador y
el FDC a una velocidad mínima determinada (8, 16, 26.67 ó32 microsegundos). Si al leer
Bit 4 (OR): datos del FDC el procesador no es suficientemente rápido, puede llegar un dato
sobrescribiendo el anterior cuando aún no había sido leído, lo que provoca que este bit se
ponga a 1 para señalar el error.
Bit 3: No utilizado (a 0).
No Data (no hay datos). Se pone a 1 durante la lectura o scan si el FDC no puede hallar el
sector indicado. Se pone también a 1 con el comando leer ID si el FDC no puede leer sin
Bit 2 (ND):
errores el campo ID (si falla el CRC). Por último, también se pone a 1 si en el comando leer
pista el sector inicial no es encontrado.
Not Writable (escritura no permitida). Se pone a 1 al ejecutar algún comando que implique
Bit 1 (NW):
modificar el contenido del disco, si este está protegido contra escritura.
Missing Address Mark (Address Mark perdida). Se pone a 1 cuando en la lectura el FDC no
halla, al cabo de una vuelta completa del disco, la ID de sector. La ausencia de Data Address
Bit 0 (MA):
Mark (y la ausencia también de una Data Address Mark borrada) pone a 1 este bit (junto al
bit MD del registro de estado 2).
Este registro de estado sólo puede ser consultado por medio de la orden Leer estado de unidad. Se
11 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
El controlador de disquetes es accedido a través de dos puertos de E/S, en la dirección 3F4h (registro de
estado) y en la 3F5h (datos). Adicionalmente, existe un registro denominado Registro de Salida Digital, en
la dirección E/S 3F2h, que controla los motores de las unidades y permite reinicializar el sistema de disco y
seleccionar la modalidad de operación (con o sin DMA). Los valores de bits establecidos para el registro de
salida digital son los siguientes (los PS/2 sólo soportan dos disqueteras y el bit 1 está reservado):
Tras poner a 0 el bit que reinicializa el FDC hay que devolverlo a 1 y (con o sin las interrupciones
habilitadas en el bit 3) esperar la interrupción de disquete que vendrá (IRQ6 -> INT 0Eh) ejecutando después
el comando leer estado de interrupciones; también hay que recalibrar, ya que el registro interno del FDC que
indica el cilindro actual es puesto a 0. En las máquinas 486 en particular, es necesario hacer una leve pausa
tras bajar este bit, ya que devolviéndolo inmediatamente a 1 sucede que en ocasiones el 765 no se entera del
cambio ¡y no se resetea! (algunos microsegundos bastan). Efectuar un reset es conveniente tras un error de
disco. En las máquinas AT o con controladoras de alta densidad existe otro registro más al que se accede en
lectura, el Registro de Entrada Digital (3F7h). Su bit más significativo indica si ha habido cambio de disco
en la última unidad seleccionada a través del registro de salida digital; los restantes bits se emplean para
gestionar el disco duro. Una vez detectada la condición de cambio de disco, hay que bajar este bit para
detectar futuros nuevos cambios por el procedimiento, un tanto extraño y quizá absurdo de llevar el cabezal al
cilindro 1 y después al 0. Para leer la línea de cambio de disco el motor debe estar encendido (se puede
encender, leer la línea y volver a apagarlo después tan deprisa que el usuario no note siquiera parpadear el led
de la disquetera). Si no se puede bajar este bit será debido a que no hay disquete introducido. También a
través del puerto 3F7h, pero actuando como salida, se accede al Registro de Control del Disquete, que
permite seleccionar la velocidad de transferencia de la unidad en sus dos bits menos significativos:
12 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
Seleccionar la velocidad correcta en los AT es un requisito totalmente indispensable para lograr enviar y
recibir datos del disco. Las unidades de alta densidad de 1.2M siempre trabajan con 80 cilindros, lo que
sucede es que pueden leer discos de doble densidad saltando los cilindros de dos en dos. Esto significa que
para leer el cilindro 15 de un disco de 360K, será necesario mover el cabezal al cilindro 30 (y programar el
765 para leer el 15, por supuesto, ya que ha sido formateado con ese número). La BIOS automatiza este tipo
de operaciones, pero cuando se accede directamente al disco no queda más remedio que considerarlas. En
los discos de 3½ nunca es necesario esto, ya que tienen siempre 80 cilindros. En la terminología anglosajona,
la velocidad de transferencia se denomina data transfer rate y el movimiento doble del cabezal en los discos
de doble densidad recibe el nombre de double stepping. Los PS/2 poseen en 3F0h y en 3F1h dos registros
de estado adicionales que no es preciso considerar.
Un consejo útil para los programadores en ensamblador es que realicen siempre una pequeña pausa de
algunos microsegundos (40-60) entre bytes sucesivos de un comando enviado al 765. La razón para ello no
está muy clara, pero las BIOS AMI de 486 hacen esto y sus motivos tendrán. Accediendo desde un lenguaje
de alto nivel o en procesadores 386 o inferiores esto probablemente no es necesario.
Las unidades de 5¼ de doble densidad giran a 300 r.p.m. (revoluciones por minuto); esto significa que
dan una vuelta cada 200 milisegundos. La velocidad de transferencia empleada es de 250 Kbit/segundo.
Echando cuentas, en 200 ms se pueden registrar unos 250000*0,2 = 50000 bits de datos = 6250 bytes por
pista. Los disquetes de 360K poseen 9 sectores de 512 bytes; por cada sector hacen falta además 62 bytes
que añade el NEC765 (ver al final del apartado 12.6.1) y otros 80 de GAP 3 que estima oportuno IBM: en
total, 654 bytes. Así, en la pista no caben 10 sectores pero sí los 9 citados. Como hay 40 cilindros en estos
disquetes (y dos caras) en total caben 9*40*2 = 720 sectores (que equivalen a 360 Kb). Por supuesto,
estrechando algo el GAP 3 al formatear sí se pueden introducir 10 sectores, maniobra bastante fiable que
realizan ciertos formateadores avanzados. Sin embargo, IBM fue excesivamente conservadora al principio, ya
que sólo formateaba 8 sectores por pista; luego se dio cuenta y rectificó. Eran los viejos discos de 320 Kb,
totalmente obsoletos aunque soportados aún por el FORMAT del DOS. También han existido antaño
formatos de 180 e incluso 160 Kb, basados en unidades de una sola cabeza. Las unidades de 5¼ de alta
densidad giran a 360 r.p.m.; esto supone 166,66 ms por cada vuelta del disco. El aumento de velocidad se
decidió por motivos de fiabilidad. A nadie se le escapa que si el disco girara más lento y se le enviaran los
datos a la misma velocidad, cabrían más datos... pero todo tiene un límite (lo contrario sería un chollo). La
pretensión de IBM de elevar excesivamente -para la tecnología del momento- la velocidad de transferencia
(de 250 a 500 Kbit/seg) obligó a tomar la medida de acelerar la unidad. Aquí, con los disquetes de doble
densidad de 5¼ se emplea la tasa de 300 Kbit/segundo: la mayor velocidad de rotación del disco es
compensada exactamente por la proporcionalmente mayor velocidad de transferencia, resultando posible de
esta manera leer los discos creados en unidades de doble densidad: 300000*0,16666 = 50000 bits de datos,
¡exactamente igual que en las unidades de doble densidad!. Por supuesto, estas unidades giran siempre a 360
r.p.m. y no es posible alterar la velocidad para leer los viejos formatos, como indican otras publicaciones ¡lo
que cambia es la tasa de transferencia!. Las controladoras de alta densidad pueden, por lo tanto, emplear
velocidades de 300, 500 y (aunque no usada en 5¼) 250 Kbit/seg. Con disquetes de alta densidad de 5¼
y a 500 Kbit/seg caben 500000*0,16666 = 83333 bits por pista (10416 bytes). El GAP 3 que emplea el
FORMAT del DOS es de 84 bytes: cada sector ocupa 512+62+84 = 658 bytes, con lo que caben 15. Esto,
unido a los 80 cilindros del disco permite almacenar 1200 Kb en el mismo (en estas unidades se accede a los
discos de 360K saltando los cilindros de dos en dos).
Las más modernas unidades de 3½ permitieron mantener la velocidad de 500 Kbit/seg con la velocidad
de rotación clásica de 300 r.p.m., sin problemas de fiabilidad, lo que eleva aún más la capacidad. Con ello,
los disquetes de alta densidad de 3½ almacenan 500000*0,2 = 100000 bits de datos (12500 bytes) en
cada pista. El FORMAT del DOS emplea un amplio GAP 3 de 108 bytes; cada sector ocupa por lo tanto
512+62+108 = 682 bytes, con lo que caben 18 por pista en estas condiciones, lo que genera los conocidos
13 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
discos de 1440 Kb. Antes de las unidades de alta aparecieron las de doble densidad de 3½: estas emplean
una velocidad de 250 Kbit/segundo, con lo que sólo admiten 6250 bytes por pista (los mismos que un
disquete de doble densidad de 5¼) y 720 Kb por disco (también emplean un GAP 3 de 80 bytes). Con
controladoras de alta densidad se puede seleccionar con estos disquetes la velocidad de 300 Kbit/segundo, lo
que permite formatear discos de 3½ y doble densidad con cerca de 1 Mb, sin problemas de fiabilidad. Sin
embargo, el FORMAT del DOS y las rutinas de la BIOS sólo soportan en estos discos la velocidad de 250
Kbit/segundo al ser la única que los PC/XT normalmente admiten. Por supuesto, el usuario siempre puede
perforar el disco para convertirlo en uno de alta densidad: la calidad de la superficie magnética en los discos
de 360K es suficientemente baja para que den errores en las últimas pistas (las más próximas al centro y con
menor longitud de circunferencia) al formatearles en alta densidad; sin embargo, en 3½ los fabricantes no se
han complicado la vida y es probable que a veces se puedan formatear los discos de doble densidad como de
alta sin problemas, algo que pese a todo no es quizá recomendable. Las unidades de 3½ detectan el tipo de
disco y las perforaciones del mismo sólo sirven para que la disquetera sepa qué velocidad de transferencia
emplear (sin embargo, en 5¼ no hay perforaciones y la unidad es capaz de detectar la velocidad apropiada).
+-----------------------------------------+-------------------+------------------+----------
| FORMATOS DE DISCO ESTÁNDAR | 5¼ Doble Densidad | 5¼ Alta Densidad | 3½ Doble
+-----------------------------------------+-------------------+------------------+----------
| Velocidad de rotación (R.P.M.) | 300/360(*) | 360 | 3
| Velocidad de transferencia (bits/seg.) | 250000/300000(**) | 500.000 | 250.0
| Esquema de codificación de información | MFM | MFM | M
| Bytes brutos por pista | 6.250 | 10.416 | 6.2
| Tamaño de sector en bytes [1] | 512 | 512 | 5
| GAP 3 al formatear con FORMAT [2] | 80 | 84 |
| Bytes que usa el 765 entre sectores [3] | 62 | 62 |
| Bytes ocupados por sector ([1]+[2]+[3]) | 654 | 658 | 6
| Sectores por pista | 9 | 15 |
| Bytes que usa el 765 en inicio de pista | 146 | 146 | 1
| Bytes aproximados que restan en GAP 4B | 218 | 400 | 2
| Cilindros | 40 | 80 |
| Caras o cabezales | 2 | 2 |
| Sectores en el disco | 720 | 2400 | 14
| Kbytes por disco | 360 | 1200 | 7
+-----------------------------------------+-------------------+------------------+----------
(*) 300 en unidades
(**) 250.000 en unidades de
Los discos normales están formateados con sectores de 512 bytes en todos los casos. Estos sectores son
numerados a partir de 1 (y no a partir de 0) en el momento del formateo, y así habrán de ser accedidos en el
futuro. En una sola vuelta del disco es factible escribir o leer todos los sectores de una pista si se hace de una
vez con el comando apropiado, ya que accediendo de sector en sector podría no dar tiempo a acceder al
siguiente sector cuando el anterior acaba de pasar por delante del cabezal, lo que además obligaría a dar una
vuelta al disco por cada sector, con un desplome en picado del rendimiento. Lo mismo puede suceder si los
sectores están excesivamente próximos debido al empleo de un formato no estándar de más capacidad:
normalmente, los GAP 3 que separan los sectores son bastante amplios como para dar tiempo al 765, en las
operaciones de escritura, a conmutar entre la escritura de los últimos bytes del sector (junto al CRC que va
detrás) y la lectura de los ID del sector siguiente; en caso contrario la operación de escritura de múltiples
14 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
sectores terminaría con error (sector no encontrado), a no ser que fueran escritos de uno en uno, con la
consiguiente ralentización del acceso. Experimentalmente se puede afirmar que el GAP 3 en alta densidad no
debería ser inferior a 32, ni tampoco inferior a 40 en doble densidad, lo que parece indicar que la unidad
necesita que los sectores estén separados al menos entre 0.5 y 1 ms, respectivamente; aunque estas cifras se
pueden rebajar incluso casi a la mitad, esos valores son los mínimos recomendados. En caso de tener que
infringir esta regla, la solución sería emplear un interleave distinto del 1:1 habitual: en otras palabras, los
sectores pueden ser numerados de manera no consecutiva. Por ejemplo, con 9 sectores, se les puede colocar
en la pista, sucesivamente, con los números 1, 6, 2, 7, 3, 8, 4 ,9, 5. Así, entre dos sectores de número
consecutivo hay otro, y se gana tiempo para poder pillarlo; este ejemplo en concreto corresponde a un
interleave 1:2, ya que hay que dar dos vueltas al disco para poder acceder una vez a toda la pista. Hay
casos en que al juntar mucho los sectores e intentar escribir una pista no se produce el error: esto puede
ocurrir sobre todo con sectores de más de 512 bytes, ya que cuando el cabezal acaba de acceder a un sector
y va a por el siguiente (que acaba de pasar de largo), no encuentra los ID del que va detrás hasta pasado un
buen rato; de ahí a volver a encontrarse con el sector buscado puede transcurrir bastante menos de una vuelta
del disco y finalmente lo encontraría sin devolver error. Naturalmente, esto sigue sin ser interesante, una vez
más, por razones de velocidad. Finalmente señalar que el GAP mínimo para operaciones de lectura
multisector es mucho menor que para las operaciones de escritura (bastaría con un GAP de 1 ó 2 bytes), ya
que la unidad no pierde tiempo en conmutar entre la escritura del sector y la lectura de IDs del siguiente.
Un pequeño detalle más: conviene recordar que al formatear una pista, la controladora espera al paso de la
marca de índice -el pequeño agujerito del disquete- lo que provoca que si todas las pistas se numeran por
igual, en ambas caras del disco están colocados físicamente en la misma posición los mismos números de
sector, gracias a esta sincronización, conservando la estructura a lo largo de unos radios imaginarios. Digamos
que si el disco es una tarta, al cortar las porciones cada comensal se lleva todos los cilindros del mismo y
único sector N que le ha tocado. En la operación habitual del disco, cuando se acaba de acceder a una pista,
lo más probable es que haya que continuar en la siguiente (bien en el otro cabezal o en el cilindro adyacente).
Esta conmutación de cabezal hace perder cierto tiempo: cuando se acaba de acceder a una pista, el cabezal
está al final de la misma y, por consiguiente, muy cerca también del principio (a nadie se le escapa que las
pistas son circulares); si se conmuta de cabezal y el disco ya ha girado lo suficiente como para pasar por
delante del primer sector de la nueva pista, habrá que volver a dar una vuelta entera. Esto puede suceder si el
GAP que hay al final de la pista no es lo suficientemente grande. Y, por desgracia, de hecho sucede con todos
los formatos de disco del DOS. Al pasar de una pista a la adyacente, en operaciones de escritura, se pierden
unos 18 milisegundos (3 del desplazamiento del cabezal y 15 de espera hasta que éste deje de vibrar) lo que
equivale a 1125 bytes en un disco de alta densidad de 3½: ¡unos dos sectores!. Por eso, cuando se acaba
con el sector 18 de una pista y se pasa a la siguiente, el cabezal está sobre algún punto del sector 2 ó el 3 y el
primer sector que se encuentra es el 3 ó el 4, teniendo que esperar a que pasen otros 15 ó 16 para llegar al 1.
La solución a este problema pasa por numerar los sectores, de una pista a otra, deslizando la numeración
(técnica conocida como skew o sector sliding):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Pista N
16 17 18 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Pista N+1
13 14 15 16 17 18 1 2 3 4 5 6 7 8 9 10 11 12 Pista N+2
En el esquema se han trazado sólo tres pistas, pero las siguientes tendrían un tratamiento análogo.
Realmente, al conmutar de un cabezal a otro en el mismo cilindro no hace falta deslizar tanto la numeración, ya
que es una operación más ágil y con menos retardos. En el ejemplo, experimentalmente se puede determinar
que en vez de 3 bastaría con desplazar 2 sectores la numeración. En los discos de 5¼ de alta densidad se
pueden recomendar los mismos desplazamientos de numeración. Sin embargo, en los de 5¼ y doble densidad
bastaría con desplazar un sector el orden al conmutar de cabezal (y los mismos 3 al cambiar de cilindro). En
los de doble densidad de 3½ conviene desplazar un sector la numeración al conmutar de cabezal y 2 al
cambiar de cilindro. Por supuesto, estos valores son los más convenientes en general, si bien algún ordenador
en concreto podría operar mejor con otra numeración similar a ésta aunque no idéntica. En cualquier caso,
numerar todos los sectores de las pistas por igual, que es lo que hacen todas las versiones del FORMAT del
15 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
DOS (al menos hasta la versión 6.0 del sistema), resulta extremadamente ineficiente y puede reducir a la mitad
la velocidad de los disquetes. Algunos buenos formateadores (como FDFORMAT con sus opciones /X e /Y)
suelen tener en cuenta estos factores. Por supuesto, esta numeración de los sectores no implica la más mínima
pérdida de compatibilidad en los disquetes estándar: lo que sucede es que los creadores del DOS no se han
preocupado demasiado hasta ahora de optimizar el rendimiento.
Los disquetes son gestionados por la BIOS en todas las máquinas empleando el DMA, por medio del
canal 2 del 8237. Sin embargo, como veremos en un apartado posterior, es factible realizar las operaciones
directamente, sin ayuda del DMA. Al emplear el modo DMA, se produce una interrupción IRQ6 (INT 0Eh)
para avisar del término de la operación de disco realizada. Al emplear el DMA conviene tener cuidado con
evitar un desbordamiento en el offset 0FFFFh de la página empleada. Por ejemplo, intentar leer o grabar un
sector normal de 512 bytes entre las direcciones de memoria 3FF2:0000 y la 3FF2:01FF (direcciones
absolutas 3FF20 a la 4011F) resultará fallido al estar implicadas las páginas de DMA 3 y 4, cuando sólo
puede estarlo una de las dos. En la práctica, será necesario reservar memoria por importe del doble del
tamaño del (o los) sector(es) a ser accedido(s) y hacer cálculos para establecer una dirección de transferencia
que coincida dentro de una sola página de DMA. No tener en cuenta este factor es jugar a la lotería con los
discos. La BIOS del sistema se encarga de comprobar por software si el buffer facilitado cruza una frontera
de DMA antes de realizar las operaciones de E/S, retornando con el error correspondiente en caso
afirmativo. Por hardware es imposible detectar esta circunstancia al no producirse errores, pero sí falla la
operación: se corrompen zonas de memoria no previstas y el resultado probable es disfunción y/o cuelgue del
sistema (a no ser que haya mucha suerte). Sin embargo, cuando el DOS se carga en memoria al principio del
arranque, modifica la INT 13h de la BIOS para que esta interrupción nunca devuelva un error debido a este
motivo (en cambio, la INT 40h, que es quien realmente controla los disquetes en la inmensa mayoría de los
ordenadores AT y que es invocada desde INT 13h, sí puede devolver errores de frontera de DMA).
+-----------------------------------------------------------------------------+
| 765DEBUG 3.1 - UTILIDAD PARA ANALISIS AVANZADO A BAJO NIVEL DE DISQUETES. |
| Programación directa del controlador NEC765 y el DMA 8237. |
| Funcionamiento probado bajo sistemas PC XT, AT, 386 y 486. |
| Soporte para disquetes de 360K, 720K, 1.2M, 1.44M y 2.88M. |
| |
| (C) 1992, 1993, 1994 - Ciriaco García de Celis. |
| |
| |
| F2 - Seleccionar unidad/densidad y resetear. |
| F3 - Recalibrar cabezal (necesario tras F2). |
| |
| F4 - Cambiar de cabezal. |
| F5 - Posicionar cabezal. |
| F6 - Leer ID's. |
| F7 - Leer sector. |
| F8 - Escribir sector. |
| F9 - Formatear pista. |
| F10 - Conmutar MF/MFM. |
| ESC - Salir |
| |
| |
| Unidad A: 500 Kbit/seg en MFM - Cilindro 0 y Cabezal 0 |
| |
| |
| Elige una opción: _ |
+-----------------------------------------------------------------------------+
Figura 12.6.5.1 PANTALLA PRINCIPAL DEL PROGRAMA
16 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
El siguiente programa de ejemplo ha sido realizado íntegramente en Borland C (compilable también sin
errores en Turbo C 2.0) y permite practicar al lector con la operación a bajo nivel del disco. Se pueden leer y
escribir sectores (con tamaños normales o no), formatear pistas, leer los ID de una pista, y todas las
operaciones auxiliares necesarias (seleccionar unidad, velocidad de transferencia, recalibrar, seleccionar
cabezal, posicionar cabezal, elegir MF/MFM). La opción de leer ID's es especialmente útil para analizar
discos con protecciones anticopia; se trata además de una tarea inevitable que ha de realizar necesariamente
cualquier copión, como paso previo a la duplicación del disquete. En esta opción se utiliza una interesante
rutina de temporización de alta precisión, empleando el 8254, para poder medir con exactitud los
milisegundos de disco que ocupa cada sector en la pista y poder hacerse una idea de cómo está organizada
y aprovechada. El formateo también es especialmente versátil, ya que permite editar, sin lujos pero con
eficacia, los bytes de los sectores propuestos por defecto -los más razonables por otra parte- antes de
enviarlos al controlador. Este programa es un útil banco de pruebas para medir la fiabilidad de técnicas de
formateo especial, para idear y probar métodos de protección anticopia y, en general, para aprender sobre el
funcionamiento a bajo nivel de los discos. El dato de la velocidad de transferencia no es relevante por lo
general en los PC/XT. La selección incorrecta de una sola opción puede provocar que el programa falle,
aunque al cabo de unos segundos se recupera el control. Las dos primeras opciones del menú no son
obligatorias; pero conviene seleccionarlas al principio y, en general, cada vez que se cambie de disco. Una
línea inferior informa permanentemente de los principales parámetros activos, si bien no conviene creer
ciegamente en ella. Por ejemplo, si se ha intentado posicionar el cabezal en el cilindro 120 de un disco
formateado, y luego se le vuelve a posicionar en el 70, en esa línea aparecerá el valor 70 aunque al leer los ID
podríamos descubrir que está realmente sobre el cilindro 31, ya que esa unidad no soporta más de 82
cilindros (numerados de 0 a 81) y no pudo pasar del 81 cuando se le ordenó ir al 120. En este ejemplo
particular, lo más aconsejable después sería recalibrar, ya que el programa cree que está sobre el cilindro 70
y las opciones de leer y escribir sector fallarán; ya que no preguntan el número de cilindro y emplean el que se
supone activo al enviar el comando al controlador.
+----------------------------------------------------------------------------------+
| Sector a leer: 1 |
| |
| |
| Tamaño de sector: |
| 0 -> 1-128 bytes |
| 1 -> 256 bytes |
| 2 -> 512 bytes |
| 3 -> 1024 bytes |
| 4 -> 2048 bytes |
| 5 -> 4096 bytes |
| |
| Elige: 2 |
| |
| Resultado de la operación: |
| |
| [ST0=0x01] [ST1=0x00] [ST2=0x00] |
| [Cilindro 1] [Cabezal 0] [Sector 1] [Tamaño 2] |
| |
| Pulsa una tecla para ver el sector [ESC=salir]. |
| |
| |
| |
| |
| |
| |
+----------------------------------------------------------------------------------+
17 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
+----------------------------------------------------------------------------------+
| |
| |
| |
| 0000: EB 3C 90 4D 53 44 4F 53 - 35 2E 30 00 02 01 01 00 .<ÉMSDOS5.0..... |
| 0010: 02 E0 00 40 0B F0 09 00 - 12 00 02 00 00 00 00 00 ...@.=.......... |
| 0020: 00 00 F8 04 00 00 29 EC - 1D 64 3C 4E 4F 20 4E 41 ..'...)..d<NO NA |
| 0030: 4D 45 20 20 20 20 46 41 - 54 31 36 20 20 20 FA 33 ME FAT16 .3 |
| 0040: C0 8E D0 BC 00 7C 16 07 - BB 78 00 36 C5 37 1E 56 +Ä++.|..+x.6+7.V |
| 0050: 16 53 BF 3E 7C B9 0B 00 - FC F3 A4 06 1F C6 45 FE .S+>|+..n.ñ...E. |
| 0060: 0F 8B 0E 18 7C 88 4D F9 - 89 47 02 C7 07 3E 7C FB .ï..|êM.ëG.+.>|. |
| 0070: CD 13 72 79 33 C0 39 06 - 13 7C 74 08 8B 0E 13 7C =.ry3+9..|t.ï..| |
| 0080: 89 0E 20 7C A0 10 7C F7 - 26 16 7C 03 06 1C 7C 13 ë. |á.|=&.|...|. |
| 0090: 16 1E 7C 03 06 0E 7C 83 - D2 00 A3 50 7C 89 16 52 ..|...|â+.úP|ë.R |
| 00A0: 7C A3 49 7C 89 16 4B 7C - B8 20 00 F7 26 11 7C 8B |úI|ë.K|+ .=&.|ï |
| 00B0: 1E 0B 7C 03 C3 48 F7 F3 - 01 06 49 7C 83 16 4B 7C ..|..H=...I|â.K| |
| 00C0: 00 BB 00 05 8B 16 52 7C - A1 50 7C E8 92 00 72 1D .+..ï.R|íP|.Æ.r. |
| 00D0: B0 01 E8 AC 00 72 16 8B - FB B9 0B 00 BE E3 7D F3 *..¼.r.ï.+..+.}. |
| 00E0: A6 75 0A 8D 7F 20 B9 0B - 00 F3 A6 74 18 BE 9E 7D ªu.ì +....ªt.+.} |
| 00F0: E8 5F 00 33 C0 CD 16 5E - 1F 8F 04 8F 44 02 CD 19 ._.3+=.^.Å.ÅD.=. |
| |
| Bytes 0000-0255 del sector (1/2) |
| Utiliza los cursores [ESC=salir] |
| |
| |
| |
+----------------------------------------------------------------------------------+
+----------------------------------------------------------------------------------+
| |
| |
| |
| 0100: 58 58 58 EB E8 8B 47 1A - 48 48 8A 1E 0D 7C 32 FF XXX..ïG.HHè..|2 |
| 0110: F7 E3 03 06 49 7C 13 16 - 4B 7C BB 00 07 B9 03 00 ....I|..K|+..+.. |
| 0120: 50 52 51 E8 3A 00 72 D8 - B0 01 E8 54 00 59 5A 58 PRQ.:.r+*..T.YZX |
| 0130: 72 BB 05 01 00 83 D2 00 - 03 1E 0B 7C E2 E2 8A 2E r+...â+....|..è. |
| 0140: 15 7C 8A 16 24 7C 8B 1E - 49 7C A1 4B 7C EA 00 00 .|è.$|ï.I|íK|... |
| 0150: 70 00 AC 0A C0 74 29 B4 - 0E BB 07 00 CD 10 EB F2 p.¼.+t)+.+..=... |
| 0160: 3B 16 18 7C 73 19 F7 36 - 18 7C FE C2 88 16 4F 7C ;..|s..6.|x+ê.O| |
| 0170: 33 D2 F7 36 1A 7C 88 16 - 25 7C A3 4D 7C F8 C3 F9 3+.6.|ê.%|úM|'+. |
| 0180: C3 B4 02 8B 16 4D 7C B1 - 06 D2 E6 0A 36 4F 7C 8B ++.ï.M|o.+..6O|ï |
| 0190: CA 86 E9 8A 16 24 7C 8A - 36 25 7C CD 13 C3 0D 0A +å.è.$|è6%|=.+.. |
| 01A0: 45 72 72 6F 72 2C 20 64 - 65 20 64 69 73 63 6F 20 Error, de disco |
| 01B0: 64 65 20 73 69 73 74 65 - 6D 61 0D 0A 52 65 65 6D de sistema..Reem |
| 01C0: 70 6C 61 63 65 20 79 20 - 70 72 65 73 69 6F 6E 65 place y presione |
| 01D0: 20 63 75 61 6C 71 75 69 - 65 72 20 74 65 63 6C 61 cualquier tecla |
| 01E0: 0D 0A 00 49 4F 20 20 20 - 20 20 20 53 59 53 4D 53 ...IO SYSMS |
| 01F0: 44 4F 53 20 20 20 53 59 - 53 00 00 00 00 00 55 AA DOS SYS.....U¬ |
| |
| Bytes 0256-0511 del sector (2/2) |
| Utiliza los cursores [ESC=salir] |
| |
| |
| |
+----------------------------------------------------------------------------------+
Figura 12.6.5.2 LECTURA DE UN SECTOR
Al principio del programa se asignan valores por defecto a las variables, se establece la velocidad de
transferencia en 500 Kbit/seg y se reserva memoria para almacenar un sector. Como se vio anteriormente,
18 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
hay que asegurar que el buffer no cruza una frontera de DMA, por lo que en la práctica se reserva el doble de
la memoria necesaria y se asigna el puntero de tal manera que esto no suceda en ningún caso. El programa
consta de un menú desde el que se accede a las diversas opciones que desembocan finalmente en funciones
independientes. La función seleccionar() permite elegir la unidad activa, reseteándola y enviando el comando
specify al FDC.
La función recalibrar() envía este comando al FDC y lo repite si falla, por si estaba sobre un cilindro
superior al 77; en esta función y en las restantes, para detectar el fin de la operación se espera la llegada de la
interrupción de disco correspondiente (IRQ 6, ligada a INT 0Eh). La BIOS se encarga en esta interrupción
de activar el bit más significativo de la posición 40h:3Eh. La función esperar_int() espera la llegada de la
interrupción comprobando dicho bit durante un par de segundos antes de considerar que la operación ha
fallado, devolviendo después dicho bit a 0. Realmente, aunque haya un error la interrupción debe llegar y el
comando ha de finalizar. Sin embargo, el FDC es a veces demasiado flexible: por ejemplo, si la portezuela de
la unidad (en 5¼) está abierta y hay un disco introducido, se puede quedar esperando indefinidamente.
Además, en general, en la programación a bajo nivel es conveniente no hacer nunca bucles infinitos para
esperar a que suceda algo. Tras el comando de recalibrado hay que ejecutar el de lectura de estado de
interrupciones, cuyo resultado es además impreso en pantalla durante 1,5 segundos para dar tiempo a leerlo
sin tener que pulsar teclas (es muy poca información y se puede leer en menos de un segundo...).
La función posicionar() lleva el cabezal sobre el cilindro solicitado. Si se está trabajando con una
velocidad de 300 Kbit/seg, correspondiente normalmente a un disco de 5¼ y doble densidad (360K), se
pregunta al usuario si la unidad es de 80 cilindros (1.2M) y se le pide que confirme que el disco es de 360K.
En ese caso, el número de cilindro será multiplicado por dos al enviar el comando seek al FDC, ya que es un
disco formateado con 40 pistas. Al final se ejecuta nuevamente el comando de lectura de estado de
interrupciones, imprimiendo el resultado y haciendo una pausa para que de tiempo a leerlo, aunque si se
omitiera este paso y la siguiente operación fuera de escritura al menos habría que esperar 15 milisegundos
para dar tiempo al cabezal a asentarse y dejar de vibrar. Realmente, en este programa ni eso haría falta, ya
que no hay humano tan rápido que en menos de 15 ms después de haber escogido la opción de posicionar
cabezal pueda elegir la de escribir sector en el menú principal. Pero en otros programas, donde se posicione
repetidamente el cabezal y se acceda al disco en escritura repetitivamente, conviene no olvidar hacer la pausa.
Bueno, si se olvida, no sucede nada: sólo se podría producir algún error al escribir que no se detectaría hasta
una posterior lectura. Lo malo es que estos errores son esporádicos y resulta muy difícil localizar su origen.
+----------------------------------------------------------------------------------+
| Longitud (ms) Sector Tamaño Cilindro Cabeza ST0 ST1 ST2 |
| ------------------- ------ ------------ -------- ------ ----- ----- ----- |
| [ 10.77] 10.77 9 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 21.53] 10.76 10 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 32.31] 10.78 11 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 43.07] 10.76 12 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 53.85] 10.78 13 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 64.63] 10.78 14 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 75.52] 10.89 15 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 86.30] 10.77 16 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 97.07] 10.77 17 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 111.31] 14.24 18 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 122.07] 10.76 1 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 132.85] 10.78 2 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 143.61] 10.76 3 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 154.38] 10.77 4 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 165.15] 10.77 5 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 175.93] 10.78 6 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 186.69] 10.77 7 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 197.46] 10.77 8 512 ( 2) 0 0 0x00 0x00 0x00 |
| [ 208.24] 10.78 9 512 ( 2) 0 0 0x00 0x00 0x00 |
19 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
+----------------------------------------------------------------------------------+
| Longitud (ms) Sector Tamaño Cilindro Cabeza ST0 ST1 ST2 |
| ------------------- ------ ------------ -------- ------ ----- ----- ----- |
| [ 399.32] 399.32 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| [ 798.94] 399.62 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| [ 1198.43] 399.50 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| [ 1598.09] 399.66 12 512 ( 2) 0 0 0x40 0x04 0x00 |
| [ 1997.53] 399.44 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| [ 2396.95] 399.41 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| [ 2796.40] 399.45 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| [ 3196.00] 399.61 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| [ 3595.62] 399.61 12 512 ( 2) 0 0 0x40 0x04 0x00 |
| [ 3995.22] 399.61 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| [ 4394.62] 399.40 12 512 ( 2) 0 0 0x40 0x04 0x00 |
| [ 4794.18] 399.56 12 512 ( 2) 0 0 0x40 0x04 0x00 |
| [ 5193.60] 399.42 12 512 ( 2) 0 0 0x40 0x04 0x00 |
| [ 5593.10] 399.50 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| [ 5992.69] 399.59 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| [ 6392.16] 399.47 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| [ 6791.64] 399.48 12 512 ( 2) 0 0 0x40 0x04 0x00 |
| [ 7191.33] 399.70 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| [ 7590.84] 399.50 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| [ 7990.23] 399.40 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| [ 8389.74] 399.51 12 512 ( 2) 0 0 0x40 0x01 0x00 |
| |
| Una tecla para leer más ID's [ESC=salir]. |
+----------------------------------------------------------------------------------+
Figura 12.6.5.3 LECTURAS CORRECTA E INCORRECTA DE ID's
Las funciones leer_sector() y escribir_sector() son muy parecidas. La principal diferencia es que la
primera muestra el sector leído (ver figura 12.6.5.2) y la segunda tiene que preguntar el byte con que rellenará
el sector escrito, ya que no permite editarlo. Antes de leer el sector se rellena el buffer en memoria con la
signatura 5AA5h. Tras la lectura, el sector es mostrado -incluso si se produjo error- aunque si el usuario
observa que contiene precisamente 5AA5h podrá deducir que elerror iba muy en serio. Hay casos en que
con error y todo puede ser interesante ver el sector, como luego veremos. La lectura y escritura de los
sectores se realiza por DMA, el cual es programado por prepara_dma().
La función leer_id() envía 22 veces dicho comando al FDC, para leer los ID (los 4 bytes con que se
formateó cada sector) y la información de estado (registros ST0..ST2). Probablemente no habrá más de 21
sectores en una pista, por lo que será posible echar un vistazo detallado a la misma. El primer sector en
aparecer no es el 1 ni el de número más bajo: sencillamente, el primero en pasar por el cabezal al ejecutar el
comando; como la unidad estaba girando con antelación y el usuario elige la opción cuando quiere, el primer
sector visualizado será cualquier sector de la pista aleatoriamente. Si hubiera más de 21 sectores en la pista,
se visualizarían sólo los 21 primeros en pasar delante del cabezal. Resulta interesante saber cuánto tiempo
transcurre entre el paso de un sector y otro, lo que permite conocer su tamaño real (interesante en discos con
protección anticopia) y también ensayar nuevos formatos de disco. Por ejemplo, si se formatean más sectores
de los que caben en una pista, el comando de formatear termina siempre con éxito, pero alguno de los últimos
sectores habrá machacado a los primeros, y la manera más sencilla de verlo es examinando los ID a ver si
están todos. De hecho, entre el último sector de la pista y el primero debería existir una mayor separación que
entre otros dos sectores cualquiera, debido a los GAP ubicados al final de la pista y al principio de la misma
(que conviene no reducir demasiado). Para medir el tiempo, se programa el 8254 (u 8253 en los PC/XT) con
20 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
una cuenta 0xFFFF. A partir de ese momento, se espera que llegue la interrupción de disco y se comprueba
si el contador se ha decrementado hasta 0 y se ha vuelto a recargar con 0xFFFF: en ese caso, la variable
cnth se incrementa para indicar que han pasado 65535/1193180 segundos más; si llegara a valer más de 8 se
abortaría el proceso al considerar que la interrupción tarda demasiado en llegar (más de 0,4 segundos en los
que el disco más lento ya ha dado dos vueltas). Tras el final de cada comando de lectura de ID, se recarga
inmediatamente la cuenta inicial (el valor 0xFFFF) en el contador 2, por el procedimiento de bajar y subir la
línea GATE del mismo, con objeto de que empiece a contar el tiempo para el próximo sector desde ya
mismo. Se lee la información que devuelve el FDC pero no se imprime por problemas de velocidad, sino que
se almacena en una matriz. La variable cnth y el último valor de cuenta leído del 8254 permiten determinar
con precisión milimétrica el tiempo que ha pasado desde el envío del comando de lectura de ID's hasta la
obtención del resultado. El primer dato de tiempo leído es incorrecto por doble motivo: por un lado, el
cabezal podía estar en medio de un sector cuando se envió el comando y el tiempo medido no sería la longitud
del sector anterior sino de medio sector anterior; por otro lado, la cuenta es recargada (cambio de la línea
GATE) al final de cada comando en lugar de al principio, por razones de precisión. Por ello, se imprimirán los
resultados de las 21 últimas muestras, descartando la primera. En la figura 12.6.5.3 hay dos ejemplos de
lectura de ID, de la primera pista de un disquete de 1.44M creado por el FORMAT del DOS. En el primero
el resultado es correcto; en el segundo, la velocidad seleccionada era incorrecta (no los 500 Kbit/seg
necesarios) y el FDC no ha podido encontrar los sectores, teniendo además que dar dos vueltas al disco (200
ms en cada una de ellas). Si no hubiera disquete o la portezuela estuviera abierta, al cabo de un minuto y
medio aparecería una pantalla con datos de tiempo N.D. (no determinado) y todos los demás bytes con ??
para indicar el error. Resulta increíble la precisión media de la medida: 399,5 ms frente a los 400 reales: una
desviación media de ¡0,5 milisegundos!, si bien esto dependerá del ordenador: cuanto más rápido, más exacta
resulta la medida.
+----------------------------------------------------------------------------------+
| |
| |
| Tamaño de sector: |
| 0 -> 128 bytes |
| 1 -> 256 bytes |
| 2 -> 512 bytes |
| 3 -> 1024 bytes |
| 4 -> 2048 bytes |
| 5 -> 4096 bytes |
| |
| Elige: 0 |
| |
| Número de sectores: 25 |
| |
| Valor para el GAP 3: 50 |
| |
| Byte para inicializar sectores: 65 |
| |
| |
| |
| |
| |
| |
| |
| |
+----------------------------------------------------------------------------------+
+----------------------------------------------------------------------------------+
| Puntualizaciones sobre el formateo: |
| |
| He establecido por defecto una tabla con los cuatro |
21 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
+----------------------------------------------------------------------------------+
| Resultado de la operación: |
| |
| [ST0=0x01] [ST1=0x00] [ST2=0x00] |
| [Cilindro 65] [Cabezal 1] [Sector 0] [Tamaño 0] |
| |
| Formateo correcto. Pulsa una tecla. |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
+----------------------------------------------------------------------------------+
+----------------------------------------------------------------------------------+
| Longitud (ms) Sector Tamaño Cilindro Cabeza ST0 ST1 ST2 |
| ------------------- ------ ------------ -------- ------ ----- ----- ----- |
| [ 6.25] 6.25 19 128 ( 0) 0 0 0x01 0x00 0x00 |
| [ 12.52] 6.26 20 128 ( 0) 0 0 0x01 0x00 0x00 |
| [ 18.77] 6.26 21 128 ( 0) 0 0 0x01 0x00 0x00 |
| [ 25.03] 6.26 22 128 ( 0) 0 0 0x01 0x00 0x00 |
| [ 31.30] 6.27 23 128 ( 0) 0 0 0x01 0x00 0x00 |
| [ 37.56] 6.26 24 128 ( 0) 0 0 0x01 0x00 0x00 |
| [ 50.42] 12.86 25 128 ( 0) 0 0 0x01 0x00 0x00 |
22 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
La función formatear_pista() pregunta los parámetros básicos (número de sectores, tamaño, GAP y byte
de inicialización) y genera una tabla con los 4 bytes que hay que enviar al FDC por cada sector. Sin embargo,
permite al usuario editar rudimentariamente dicha tabla con la función editar_tabla_fmt(), para permitir a éste
ensayar trucos, ya que los valores propuestos por defecto son por lo general los más convenientes. En esos 4
bytes que hay por cada sector se almacenan el número de cilindro, el de cabezal, el número de sector y el
tamaño. En la función de edición se permite cambiar los bytes de un sólo sector, o cambiar uno de los 4 bytes
en todos los sectores. Estos 4 bytes identifican cada sector y son comparados con los que se envían en el
futuro comando de lectura o escritura de sector, debiendo coincidir plenamente para que el FDC encuentre el
sector. El número de cilindro y el de cabezal suelen coincidir -y así son propuestos por defecto- con el
cilindro y el cabezal en que esté dicho sector; cambiar esto puede ser interesante en técnicas de protección de
información, ya que el sector desaparece pero realmente sigue estando ahí: la diferencia es que a la hora de
leerlo hay que indicar al FDC no el cilindro real sobre el que está posicionado el cabezal sino el número de
cilindro y cabezal que se programaron al formatear el sector, que pueden ser cualquier otro. Este programa, a
la hora de leer los sectores no pregunta el número de cilindro ni cabezal -para ahorrar tiempo- por lo que no
permite verificar esta propiedad, pero con una pequeña y sencilla modificación el lector podría comprobarlo
por sí mismo. Lo que sí puede resultar más interesante es cambiar el número de sector propuesto por defecto
o, mejor aún: su tamaño. Al formatear la pista, el tamaño de los sectores es asignado al enviar el comando de
formateo al FDC: todos los sectores tendrán dicho tamaño, con independencia del tamaño particular que se
asigne al enviar los 4 bytes específicos. En otras palabras, si se programa un tamaño 2 (de 512 bytes) en el
comando de formateo, todos los sectores serán de 512 bytes, aunque alguno esté definido como de 1024, de
256 bytes,... en el 4º byte de información enviado por cada sector al FDC. Por tanto, ¿Para que sirve este
byte?: una vez más, para posibilitar la lectura. Si un sector está programado con tamaño 3 (1024 bytes) habrá
de ser leído indicando tamaño 3. Si era de 512 bytes, lo que sucede es que además del sector se leen, ni más
ni menos, los GAPs que van detrás, los ID's e incluso parte del siguiente sector; por supuesto que se produce
un lógico error de CRC al leer, pero los datos leídos son correctos. La figura 12.6.5.4 constituye un ejemplo
de formateo: en un disquete de 360K se colocan 25 sectores de 128 bytes con un GAP 3 de 50 bytes,
rellenándolos al formatear con el byte 65 (41h, código ASCII de la A). Teniendo en cuenta los 62 bytes que
el FDC añade entre sectores en MFM, (128+62+50)*25=6000, por debajo del límite de 6250 en este tipo
de disquetes. Los 4 bytes del sector 6 resultan modificados para asignarle un tamaño 1 (256 bytes), aunque el
sector es realmente de 128 bytes. La posterior lectura de ID's demuestra cómo ha quedado la pista, si bien
sólo se pueden ver en una pantalla los ID de 21 sectores. En la figura 12.6.5.5 se intenta leer dicho sector y,
pese al error de CRC, resulta evidente que es bien leído (junto con todo lo que va detrás). La última línea del
volcado hexadecimal es el inicio del siguiente sector de la pista. El lector puede verificar que el esquema del
final del apartado 12.6.1 es rigurosa y milimétricamente cierto: todos los GAPs, ID y bytes introducidos por el
FDC entre sectores aparecen claramente reflejados en la figura. Por supuesto, una posterior escritura del
23 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
sector 6 pisaría el 7. De ahí que, anécdotas a parte, no suele resultar muy útil generalmente hacer este tipo de
maniobras... ¿o tal vez si?.
+----------------------------------------------------------------------------------+
| Sector a leer: 6 |
| |
| |
| Tamaño de sector: |
| 0 -> 1-128 bytes |
| 1 -> 256 bytes |
| 2 -> 512 bytes |
| 3 -> 1024 bytes |
| 4 -> 2048 bytes |
| 5 -> 4096 bytes |
| |
| Elige: 1 |
| |
| Resultado de la operación: |
| |
| [ST0=0x41] [ST1=0x20] [ST2=0x20] |
| [Cilindro 0] [Cabezal 0] [Sector 6] [Tamaño 1] |
| |
| Error de lectura (el sector puede estar mal leído). |
| Nota: el buffer de lectura contenía el patrón 5AA5. |
| Pulsa una tecla para ver el sector [ESC=salir]. |
| |
| |
| |
| |
+----------------------------------------------------------------------------------+
+----------------------------------------------------------------------------------+
| |
| |
| |
| 0000: 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA |
| 0010: 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA |
| 0020: 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA |
| 0030: 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA |
| 0040: 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA |
| 0050: 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA |
| 0060: 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA |
| 0070: 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA |
| 0080: 6B 70 4E 4E 4E 4E 4E 4E - 4E 4E 4E 4E 4E 4E 4E 4E kpNNNNNNNNNNNNNN |
| 0090: 4E 4E 4E 4E 4E 4E 4E 4E - 4E 4E 4E 4E 4E 4E 4E 4E NNNNNNNNNNNNNNNN |
| 00A0: 4E 4E 4E 4E 4E 4E 4E 4E - 4E 4E 4E 4E 4E 4E 4E 4E NNNNNNNNNNNNNNNN |
| 00B0: 4E 4E 4E 4E 00 00 00 00 - 00 00 00 00 00 00 00 00 NNNN............ |
| 00C0: A1 A1 A1 FE 00 00 07 00 - 40 8B 4E 4E 4E 4E 4E 4E ííí.....@ïNNNNNN |
| 00D0: 4E 4E 4E 4E 4E 4E 4E 4E - 4E 4E 4E 4E 4E 4E 4E 4E NNNNNNNNNNNNNNNN |
| 00E0: 00 00 00 00 00 00 00 00 - 00 00 00 00 A1 A1 A1 FB ............ííí. |
| 00F0: 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA |
| |
| Bytes 0000-0255 del sector (1/1) |
| Utiliza los cursores [ESC=salir] |
| |
| |
| |
+----------------------------------------------------------------------------------+
Figura 12.6.5.5 LECTURA DEL SECTOR DE TAMAÑO TRUCADO
24 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
La función mostrar_resultados() es invocada desde las anteriores, con objeto de leer los 7 bytes que
devuelve el FDC al término de los principales comandos e imprimirles en pantalla. La función
mostrar_sector() enseña en pantalla el volcado hexadecimal del buffer donde se leen los sectores, en páginas
de 256 bytes, teniendo en cuenta el tamaño de los mismos y permitiendo cierta movilidad.
La función motor_on() arranca el motor de la unidad si aún no estaba en marcha, ajustando al valor
máximo la variable que indica cuándo se detendrá, con objeto de evitarlo en lo posible. Al menos estará
girando durante 14 segundos en el peor de los casos. La función motor_off() ajusta dicha variable para que
el motor se pare en unos 3 segundos. La función outfdc() envía bytes al FDC pero sin esperar más de 440 ms
en caso de que éste, por cualquier error, no esté dispuesto a recibirlos. Su recíproca infdc() lee un byte del
FDC considerando un fracaso la operación si éste no responde en menos de 440 ms (en estos casos devuelve
un valor negativo para que la función que llama advierta el error). La función esperar_int() ya fue comentada
anteriormente. Por último, la función prepara_dma() programa el 8237 para transferir el número de bytes
indicado, en el modo apropiado (lectura/escritura) y en la dirección del buffer empleado.
Si bien lo normal es emplear el DMA para realizar los accesos a disco, ello no es estrictamente necesario
(excepto en los auténticos PS/2): generalmente también se puede acceder enviando directamente los bytes al
FDC, aunque sería más útil emplear el DMA (la CPU no tendría tiempos muertos de espera para mover los
bytes). Realmente, bajo DOS da lo mismo acceder con el DMA que sin el, ya que aún cuando se emplea el
DMA ¡la pobre CPU se queda esperando a que llegue la interrupción que indica el final de la operación!. La
única ventaja real de utilizar el DMA, que motivó su uso por parte de los programadores de IBM, es que el
contador de hora de la BIOS sigue avanzando (y el reloj no se atrasa), mientras que sin el DMA se pararía al
tener que inhibir las interrupciones en el momento crítico de la transferencia del sector, con objeto de no
perder datos. En otros sistemas operativos multitarea, el DMA permite a la CPU continuar trabajando
(perdiendo sólo los ciclos estrictamente necesarios para la transferencia) a la par que es realizada la operación
de disco: aunque el rendimiento global del sistema se degrada durante la operación, al menos no se detienen
todos los procesos.
El siguiente programa de ejemplo, realizado íntegramente en ensamblador, permite leer y escribir sectores
de disco aislados en el formato MFM habitual. Soporta las unidades A: y B:, así como discos y disqueteras
de todos los formatos y densidades -incluidos los no estándar-. Se preguntan todos y cada uno de los
parámetros necesarios, dando algunas pautas para ayudar. Es importante responder correctamente, aunque el
control de errores suele recuperar los fallos, sin dejar bloqueado el ordenador, en un plazo de tiempo
razonable. Esta utilidad se basa en un menú principal donde se tiene acceso a las diversas opciones, que
desembocan en las rutinas de bajo nivel que controlan el disco. No describiremos las rutinas encargadas de
tomar datos del teclado ni tampoco las de impresión en pantalla, bastante obvias. Sin embargo, daremos un
ligero repaso a las subrutinas encargadas de controlar el disco.
25 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
espera_int). Debido a que las interrupciones no llegan cuando está activo el modo NO DMA en el registro
de salida digital, por algún oscuro motivo que desconozco, es preciso establecer momentáneamente el modo
DMA a través del bit 3 de dicho registro (rutina habilita_int) y volverlo a desactivar una vez que llega la
interrupción; realmente, aún seleccionando esta modalidad, el DMA no será empleado ya que no se utiliza en
los comandos de recalibración ni en el de posicionamiento del cabezal. En esta última rutina se tiene en cuenta
el caso especial que supone un disquete de 40 pistas en una unidad de 80, multiplicándose entonces por 2 el
número de cilindro antes de enviarlo al FDC.
La rutina sector_io es la encargada de leer y escribir los sectores de disco. Tras enviar el comando al
FDC, se espera que éste encuentre el sector y seguidamente se pasa a leer/escribir el mismo directamente,
aunque en lugar de emplear las rutinas E/S habituales (fdc_read y fdc_write) se realiza el proceso de manera
directa para acelerarlo. Más que para acelerarlo, para que no nos pille: la velocidad es aquí crítica (el proceso
se realiza con las interrupciones apagadas) ya que cada 16-32 microsegundos hay que transferir un byte entre
la CPU y el FDC y dormirse en los laureles supondría un error irrecuperable. Si se está escribiendo un sector
y se produce un fallo, es fácil detectarlo (el FDC deja de recibir datos e intenta enviar los bytes de la fase de
resultados) pero en la lectura de sectores serían leídos dichos resultados confundidos como datos del sector,
aunque al terminar el comando (y bajar el bit CB del registro de estado) se detectaría afortunadamente el final
de la operación y se podría suponer que los últimos 7 bytes leídos no eran del sector sino la fase de
resultados. En general, si el usuario ha indicado bien todos los parámetros y el disquete no está defectuoso, no
habrá problemas. Estas rutinas de lectura de sectores no están diseñadas de manera tolerante a fallos, ya que
realizan saltos condicionales comprobando los bits del registro de estado, que en caso de quedarse
congelados y no cambiar supondrían un cuelgue del sistema. Sin embargo, añadir controles de timeout
alargaría los tiempos de ejecución y podría provocar, si no se tiene cuidado, que los PC/XT más lentos no
fueran bastante potentes para acceder al disco con la suficiente rapidez. Además, la mejor técnica para
controlar los timeout es, indiscutiblemente, la monitorización de los ciclos de refresco de la memoria dinámica
de los AT (ese bit del puerto 61h que cambia 66287 veces por segundo): en los PC/XT sería más
complicado...
Por último, las rutinas fdc_read y fdc_write se encargan de la comunicación CPU-FDC en ambos
sentidos, aunque aquí sí se han establecido unos rudimentarios controles de timeout, de esos que tardan más
tiempo en recuperar el control en las máquinas más lentas. De ahí que estas subrutinas no sean empleadas
desde sector_io, por razones de velocidad.
Acceder a disco sin DMA es más incómodo y problemático que hacerlo a través del DMA, y no ofrece
absolutamente ninguna ventaja adicional, a no ser que el 8237 esté averiado en el ordenador. De hecho, yo
personalmente dejé de utilizar durante algún tiempo el DMA en los accesos de disco (me hice un controlador
especial que además me ayudó a subir nota en una asignatura), creyendo que los errores en la transferencia de
datos en mis disqueteras se debían a este integrado. Sin embargo, finalmente averigué que la causa estaba en
los SIPPs de memoria un tanto flojos (por fortuna, resulta que un amigo mío sí tenía estropeado el DMA de
verdad en las operaciones de escritura, y ese driver le vino muy bien para poder escribir en sus disquetes).
Anécdotas aparte, este programa es meramente educativo y no un modelo a seguir.
Hasta ahora hemos descrito todo lo necesario para poder programar la controladora de disquetes. Ahora
aplicaremos dicha información a un caso práctico real, con un programa. Ciertas aplicaciones comerciales de
backup ya emplean formatos de disco de más capacidad para almacenar los datos, además de manera
comprimida. Sin embargo, estos disquetes no pueden ser empleados directamente por el DOS. Por el
contrario, la utilidad que desarrollaremos, 2M, es un programa residente que permite gestionar disquetes con
sectores de más de 512 bytes e, incluso, con sectores de distinto tamaño en las pistas. Este último formato
obtendrá algo más de capacidad, pero menos velocidad y fiabilidad. En 3½", los disquetes más comunes de
26 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
1.44M (1440K) se podrán formatear a 1804K y 1886K, respectivamente. Los de 720K alcanzarán los
984/1066K. En 5¼" los de 1.2M pasan a 1476/1558K y los de 360K a 820/902K. Los formatos de
1886K, 1066K y 1558K no pueden ser reproducidos por la versión de enero de 1992 del poderoso copión
COPYWRITE; el de 902K sí es duplicado en algunos ordenadores, aunque a veces algunas pistas quedan
mal. Esto no es problema para el usuario normal, que podrá hacer DISKCOPY (si 2M está instalado en
memoria) hacia un disco destino ya formateado. Para formatear estos nuevos disquetes se empleará un
pequeño programa escrito en C (2MF.C) que se limitará a llamar a las funciones de INT 13h reforzadas por
2M; dicho programa será descrito más adelante.
Los programas que formatean los discos a mayor capacidad de la normal suelen limitarse a reducir el GAP
3 al formatear, colocando gracias a ello más sectores en las pistas. Sin embargo, la utilidad propuesta aquí
rompe con el tamaño estándar de 512 bytes: al colocar sectores de mayor tamaño, existen menos sectores y
también menos GAP de separación. El inconveniente de este método es que difícilmente sectores de 1024,
2048 ó más bytes pueden encajar aprovechando óptimamente la capacidad de la pista. Por ello se han
adoptado dos soluciones diferentes que han originado 8 nuevos formatos de disco (2 por cada tipo de medio
magnético):
Empleo de sectores de 1 Kb. Pese a ser más grandes, se pueden colocar más o menos bien en los 4
tipos de disco (360-1.2-720-1.44) aprovechando más la capacidad de la pista, ya que al haber menos
sectores también se derrocha menos espacio en GAPs sin necesidad de reducirlos excesivamente ni,
por tanto, degradar la fiabilidad de los discos. Esta solución, si se tiene cuidado de optimizar el
formateo de las pistas (con la numeración adecuada de los sectores en las mismas) permite obtener
disquetes de mayor capacidad de la normal, tan fiables como los estándar del DOS y sensiblemente
más rápidos que los creados por el FORMAT debido a dos motivos: en estos formatos el disco da
sólo las vueltas necesarias para acceder a los datos y, además, se leen más datos en dichas vueltas.
La otra solución alternativa consiste en emplear sectores aún de mayor tamaño, hasta 2 Kb (mayores
no permitirían una ventaja significativa) y rellenar el hueco restante de la pista, donde no cabe otro
sector de 2 Kb, con sectores menores. Esto implica colocar sectores de distinto tamaño en las pistas,
lo cual escapa en teoría de las posibilidades del controlador de disquetes, si se repasa la
documentación de las páginas anteriores. Sin embargo, sólo en teoría, ya que existen programas
comerciales con protección anticopia que realizan esta tarea. La técnica que veremos permite realizar
esto, pese a lo cual estos formatos de disco no son recomendados: son poco seguros en cuanto a
portabilidad -disquetes creados en una máquina podrían tener problemas para ser reconocidos en otro
ordenador o incluso ser destruidos al escribir- y aumentan poco la capacidad respecto a la 1ª solución;
pese a todo han sido calibrados de tal manera que se puede afirmar que en un elevadísimo porcentaje
de veces el funcionamiento y la portabilidad serán satisfactorios.
+---------------------+
| Parámetros /X e /Y |
| de FDFORMAT para un |
| formateo correcto. |
+---------------------+
| /X /Y |
+-------+----------+----------+
| 5¼-DD | 1 | 3 |
| 5¼-HD | 2 | 3 |
| 3½-DD | 1 | 2 |
| 3½-HD | 2 | 3 |
+-------+----------+----------+
A lo largo de este apartado se hará alguna referencia al popular programa de formateo FDFORMAT
creado por Christoph H. Hochstätter; esta utilidad permite formatear disquetes normales desplazando los
sectores de manera óptima (opciones /X e /Y) y también añadir más sectores (estrechando el GAP 3). Para
27 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
superar las limitaciones de flexibilidad de la BIOS es preciso tener residente un pequeño programa de sólo
128 bytes de cara a soportar los formatos extendidos. Este programa, bastante superior al FORMAT en
todos los aspectos, con el que además es compatible, está muy extendido en las principales BBS (su código
fuente en Turbo Pascal viene incluido) y aborda desde otro punto de vista la ampliación de la capacidad
normal de los disquetes, respetando los sectores de 512 bytes. No hay que olvidar que este programa
permite crear, además de algunos formatos extendidos, disquetes totalmente estándar de 360K, 1.2M, 720K
y 1.44M que, por supuesto, no necesitan soporte residente y son mucho más rápidos que los creados por el
FORMAT del DOS. Mientras el FORMAT del sistema operativo no corrija la numeración incorrecta de
sectores, que lleva practicando desde 1981, y a la espera de que David Astruga saque la próxima versión de
su programa de copia y formateo (a finales del 94 o comienzos del 95); por el momento, FDFORMAT y sus
parámetros /X e /Y constituyen la única solución para los usuarios más entendidos (aquellos que usan 4DOS
en vez de COMMAND.COM, QEMM en lugar de EMM386, etc): emplear el FORMAT actual no es de
conservadores sino de no informados. 2M (abreviatura de 2 megas, aunque no se alcanza esa capacidad por
disco) es un programa residente que da soporte a los nuevos formatos de disco. Una vez instalado 2M en
memoria, los nuevos disquetes serán reconocidos sin problemas: se podrá hacer DIR, COPY, CHKDSK,...
e incluso DISKCOPY hacia un disco destino ya formateado. El código residente de 2M funciona también
bajo WINDOWS 3.X; sin embargo, en OS/2 2.1 hay problemas, aunque se pueden arreglar, como veremos
luego, usando el DOS de Microsoft (y no el que viene con el propio OS/2) desde un disquete o, mejor aún,
creando una imagen en disco duro de ese disquete. De esta última manera, el usuario ni siquiera nota al
diferencia entre estas ventanas de DOS y las normales. Tal vez alguien escriba algún día el driver oportuno
para facilitar la operación en este sistema... de momento, 2M está diseñado sólo para los sistemas más
extendidos. En WINDOWS NT, donde no ha sido probado, probablemente existirán problemas y
limitaciones mayores de las que se producen bajo OS/2. Al momento de escribirse estas líneas, el autor de
2M tiene constancia de que hay intentos de portarlo al sistema operativo Linux por parte de Alain Knaff y
David Niemi, si bien desconoce el grado de avance en esta materia.
+------------------------------------------------------------------------+
| |
| [1867/1867] B:\>dir |
| |
| Volume in drive B is unlabeled Serial number is 2FE6:7632 |
| File not found "B:\*.*" |
| 0 bytes in 0 file(s) |
| 1.912.320 bytes free |
| |
| |
| [1867/1867] B:\>chkdsk |
| Número de serie de volumen es 2FE6-7632 |
| |
| 1912320 bytes de espacio total en disco |
| 1912320 bytes disponibles en disco |
| |
| 512 bytes en cada unidad de asignación |
| 3735 total de unidades de asignación en el disco |
| 3735 unidades de asignación disponibles en disco |
| |
| 655360 bytes de memoria total |
| 649760 bytes libres |
| |
| |
| [1867/1867] B:\>testdisk |
| TD-Test Disco, Edición Estandar 4.50, (C) Copr 1984-88, Peter Norton |
| Traducción Castellano, Copyright (C) 1989 ANAYA Multimedia, S.A. |
| |
| Verificar DISCO, ARCHIVO, o AMBOS |
| Pulse D, F, o A ... D |
28 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
| |
| Puede pulsar BREAK (Ctrl-C) durante la |
| verificación para interrumpir Test Disco |
| |
| Test leyendo el disco B:, zonas del sistema y de datos |
| La zona del sistema consta de boot, FAT, y directorio |
| Zona del sistema sin errores |
| |
| La zona de datos consta de clusters numerados 2 - 3.736 |
| Zona de datos sin errores |
| |
| |
| [1867/1867] B:\>_ |
+------------------------------------------------------------------------+
EJEMPLO DE ACCESO A DISQUETE 2M DE 1.44 FORMATEADO A CASI 1.90
2M añade un nuevo servicio a la INT 13h para poder formatear los nuevos disquetes. No es probable que
gracias a ello la próxima versión de PC-TOOLS soporte los nuevos formatos, pero añadir rutinas de
formateo apenas alargaba el código residente (sólo 0.75 Kb más hasta alcanzar los 5 Kb) y se trataba de la
solución más elegante. Para formatear los nuevos disquetes se ha creado un programa en C de alto nivel, que
sencillamente invoca la INT 13h sin verse obligado a realizar ni un solo acceso directo al hardware, pese a
que el código residente de 2M accede siempre a disco a través del controlador de disquetes, sin una sola
llamada al DOS/BIOS en ningún momento.
+-----------------------------+----------------------------------+--------+
| Ensamblador | Comentario | Offset |
+-----------------------------+----------------------------------+--------+
| JMP SHORT BootP ; 2 bytes 0 |
| NOP ; 1 byte 2 |
| DB "2M-STV08" ; ID sistema 3 |
| DW 512 ; bytes/sector 11 |
| DB 1 ; sectores por cluster 13 |
| DW 1 ; sectores reservados al principio 14 |
| DB 2 ; nº copias de la FAT 16 |
| DW 224 ; entradas al directorio raíz 17 |
| DW 3608 ; nº total de sectores del disco 19 |
| DB 0F0h ; byte descriptor de medio 21 |
| DW 11 ; sectores ocupados por la FAT 22 |
| DW 22 ; sectores por pista 24 |
| DW 2 ; nº de cabezales 26 |
| DD 0 ; sectores especiales reservados 28 |
| DD 0 ; nº sectores (unidad 32 bit) 32 |
| DB 0 ; unidad física 36 |
| DB 0 ; reservado 37 |
| DB 29h ; disco con número de serie 38 |
| DD 8BC1AD20h ; número de serie provisional 39 |
| DB "NO NAME " ; título del disco 43 |
| DB "FAT12 " ; tipo de FAT 54 |
| DB Flags ; bit 0 = 1 si FechaF/HoraF definido 62 |
| DB ? ; checksum de la información vital 63 |
| DB 7 ; versión formato (>=7 si BOOT virtual) 64 |
| DB 0 ; a 1 si escribir al formatear 65 |
| DB 0 ; velocidad transferencia pista 0 66 |
| DB 0 ; velocidad transf. demás pistas 67 |
| DW BootP ; offset al programa de arranque 68 |
| DW Infp0 ; T1: información para pista 0 70 |
| DW InfpX ; T2: información demás pistas 72 |
| DW InfTm ; T3: tabla tamaños demás pistas 74 |
29 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
La capacidad obtenida por 2M supera la conseguida por los programas comerciales de backup en los
formatos especiales para almacenar sólo datos. Con la ayuda de un compresor de datos de dominio público
líder (PKZIP, ARJ, etc) también superior en rendimiento a los programas de backup, se puede conseguir el
método de backups que, indiscutiblemente, más aprovecha los disquetes, con una aplastante diferencia -y
además el más barato-. Sin embargo, el usuario debería tener cuidado con el tipo de datos que almacena en
estos discos, ya que no son tan portables como los estándar y sería problemático migrarlos después a otros
entornos.
Existen versiones de 2M tanto para sistemas AT como para PC/XT, con el único requisito de que la
controladora y las unidades sean de alta densidad.
La primera pista (cilindro y cabezal 0) de los nuevos disquetes tiene el formato normal de sectores de 512
bytes, conteniéndolos en cantidad también más o menos normal. Uno de los motivos es permitir que la FAT,
zona del disco en la que a menudo cambia un sólo sector (y no varios consecutivos) tenga un acceso más ágil.
En algunos formatos de disco, parte del directorio raíz también cabe en esta pista; en cualquier caso, esto no
es demasiado importante porque sólo se accede al directorio raíz una vez por cada fichero.
Debido al empleo en la primera pista de sectores físicos de 512 bytes, no se pueden emular todos los
sectores virtuales. En 3½-HD por ejemplo, los nuevos formatos de disco contarán aparentemente con 22-23
sectores por pista. Realmente serán muchos menos y de más de 512 bytes, pero se engañará al DOS para
hacerle creer que son la cantidad citada de sectores de 512 bytes, de cara a mantener la compatibilidad. En
cualquier caso, esta cifra es muy superior a los 18 sectores habituales en este tipo de disco. Como la primera
pista contiene sectores reales de 512 bytes, no se pueden meter tantos (no caben más de 21 y eso juntando
excesivamente los sectores, como hace FDFORMAT en el formato 1.72M).
Para arreglar este problema, el código residente de 2M se extralimita en sus funciones y, suponiendo que
los discos se emplean bajo DOS, ignora las escrituras sobre la segunda copia de la FAT (que estaría sobre
alguno de los sectores que no existen en la primera pista) devolviendo la primera copia de la FAT a quien
quiera leer la segunda. Así se consigue además una pequeña velocidad extra, ya que la escritura sobre la
segunda copia de la FAT que realiza el DOS al crear ficheros resulta ignorada. Realmente, es un poco
innecesaria la presencia de 2 FAT en un disquete, máxime teniendo en cuenta que su adyacencia física
propicia que en caso de daño se estropeen las dos (¿cuántas veces el lector ha tenido que echar mano de la
segunda copia de la FAT para recuperar sus datos?). El MS-DOS, incluso en la versión 6.0 no respeta sus
propias especificaciones y asume que los disquetes tienen 2 copias de la FAT: aunque se indique sólo una en
el sector de arranque, hará caso omiso. Esta es, por un lado, una buena manera de darle el corte de mangas;
por otro, un medio ideal para simular más sectores en la primera pista física.
30 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
+-----------------------------+----------------------------------+--------+
| Ensamblador | Comentario | Offset |
+-----------------------------+----------------------------------+--------+
| JMP SHORT BootP ; 2 bytes 0 |
| NOP ; 1 byte 2 |
| DB "2M-STV04" ; ID sistema 3 |
| DW 512 ; bytes/sector 11 |
| DB 1 ; sectores por cluster 13 |
| DW 1 ; sectores reservados al principio 14 |
| DB 2 ; nº copias de la FAT 16 |
| DW 224 ; entradas al directorio raíz 17 |
| DW 3772 ; nº total de sectores del disco 19 |
| DB 0F0h ; byte descriptor de medio 21 |
| DW 11 ; sectores ocupados por la FAT 22 |
| DW 23 ; sectores por pista 24 |
| DW 2 ; nº de cabezales 26 |
| DD 0 ; sectores especiales reservados 28 |
| DD 0 ; nº sectores (unidad 32 bit) 32 |
| DB 0 ; unidad física 36 |
| DB 0 ; reservado 37 |
| DB 29h ; disco con número de serie 38 |
| DD 4B368A0Eh ; número de serie (aleatorio) 39 |
| DB "NO NAME " ; título del disco 43 |
| DB "FAT12 " ; tipo de FAT 54 |
| DB Flags ; bit 0 = 1 si FechaF/HoraF definido 62 |
| DB ? ; checksum de la información vital 63 |
| DB 7 ; versión formato (>=7 si BOOT virtual) 64 |
| DB 1 ; a 1 si escribir al formatear 65 |
| DB 0 ; velocidad transferencia pista 0 66 |
| DB 0 ; velocidad transf. demás pistas 67 |
| DW BootP ; offset al programa de arranque 68 |
| DW Infp0 ; T1: información para pista 0 70 |
| DW InfpX ; T2: información demás pistas 72 |
| DW InfTm ; T3: tabla tamaños demás pistas 74 |
| DW FechaF ; Fecha de formateo (2M 3.0+) 76 |
| DW HoraF ; Hora de formateo (2M 3.0+) 78 |
| Infp0 DB 19, 70 ; nº sectores / GAP de formateo |
| DB 1,2,3,4,5,6,7,8 ; sectores ordenados (20..23 no existen) |
| DB 9,10,11,12,13,14 |
| DB 15,16,17,18,19 |
| InfpX DB 64, 3 ; nº sectores / GAP de formateo |
| DB 7 ; nº sectores a renumerar |
| DB 128+1, 4, 4 ; tabla de renumeración formateo: |
| DB 128+12, 1, 4 ; nº sector, nuevo número, tamaño |
| DB 128+23, 5, 4 |
| DB 128+34, 2, 4 |
| DB 128+45, 6, 3 |
| DB 128+51, 3, 4 |
| DB 128+62, 7, 2 |
| InfTm DB 4,4,4,4,4,3,2 ; tamaño sector 1, 2, 3,... |
| BootP:... ; programa del sector de arranque |
+-------------------------------------------------------------------------+
SECTOR DE ARRANQUE DE UN DISQUETE 2M DE 3½ A 1.88M
El sector de arranque de los nuevos disquetes es en principio similar al de cualquier otro disco, pero
contiene más información adicional para describir el formato físico de disco que se trate y así poder
gestionarlo luego. De esta manera, se sistematiza el soporte de los nuevos formatos y se simplifica el programa
residente. Detrás de los primeros 62 bytes, donde va la información colocada por el FORMAT normal del
31 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
DOS (incluyendo las últimas modas, como campos para etiqueta de disco, número de serie, etc.) existen unos
campos con información adicional, que describiremos más adelante. Detras de este área está el programa de
arranque del disquete, que en sus primeras versiones se limitaba a imprimir en pantalla un mensaje diciendo
que el disco no es de arranque; actualmente arranca desde el disco duro si éste existe y, desde 2M 2.0, carga
el código SuperBOOT almacenado en el disco si es de alta densidad. Los discos 2M de alta densidad utilizan
5 sectores libres de la segunda copia de la FAT (ubicados en la primera pista) para almacenar gran parte del
código residente de 2M (todo, excepto las rutinas de formateo). De esta manera, desde 2M 2.0 es posible
botar de un disco 2M de alta densidad, que puede crearse con un SYS ordinario. De hecho, el primer sector
de la segunda copia de la FAT emula al auténtico sector de arranque, y los 5 restantes almacenan el código
residente de 2M. Así, cuando 2M está instalado, el comando SYS y cualquier aplicación que acceda al sector
de arranque estará accediendo realmente a un falso sector de arranque que está físicamente colocado en la
FAT2. Y podrá modificarlo sin riesgo alguno para 2M, ya que el auténtico sector de arranque permanece
inmutable; las versiones anteriores de 2M necesitaban proteger este sector restringiendo de alguna manera su
acceso (para evitar que un simple SYS lo modificara y borrara la información vital que contiene). La
denominación SuperBOOT para el código de 2M almacenado en la primera pista de los discos se debe
exclusivamente a cuestiones de marketing. Debido a que se necesita un tamaño mínimo de FAT, modificar el
tamaño de cluster en el sector de arranque no es conveniente, aunque está permitido y puede generar discos
que no funcionen. Sin embargo, la utilidad estándar de formateo no deja cambiar el tamaño de cluster (por
otra parte de sólo 512 bytes) y no hay muchos programas conocidos que alteren estos parámetros de los
disquetes ya formateados.
Cuando el sistema arranca de un disco 2M de alta densidad, el código SuperBOOT rebaja la memoria
libre en 5 Kbytes (normalmente, de 640K a 635K) ubicándose al final de la memoria convencional y se instala
en la INT 13h. Después, se carga el sector de arranque vía INT 13h (que en adelante será el falso sector de
arranque emulado, al que pudo acceder el SYS) y se ejecuta, procediéndose al arranque normal del sistema,
ya que la nueva BIOS soporta discos 2M... este sector de arranque ubicado en la FAT2 es denominado
sector de arranque virtual en la documentación de 2M. Como puede observar el lector, dejar la primera
pista con sectores de 512 bytes y emular la segunda copia de la FAT sobre la primera fue una idea primitiva
que luego ha permitido muchas aplicaciones interesantes.
Naturalmente, está previsto un mecanismo para poder acceder a los sectores físicos sin emulaciones: esto
es útil además para permitir al programa de formateo grabar el código SuperBOOT y acceder al sector de
arranque físico, ya que los programas normales no tienen motivos especiales para necesitar un acceso a dichas
áreas. Cuando 2M está instalado, cualquier acceso al cabezal 128 ó 129 en lugar del 0 ó el 1 permite acceder
al disco sin realizar ningún tipo de emulación; si bien esto sólo funciona con discos 2M (con un disco estándar
en la unidad, aunque 2M esté instalado, el acceso a estos cabezales devuelve un error).
En adelante nos referiremos al sector de arranque físico, no al virtual (que puede ser distinto si el disco es
de sistema o ha sido alterado por alguna utilidad). El primer campo propio de 2M en el sector de arranque es
una variable con flags, empleada sólo desde 2M 3.0 para indicar si se almacena la fecha y hora de formateo
en el sector de arranque (bit 0 = 1 en caso afirmativo). Detrás hay un checksum o suma de comprobación de
la zona vital del sector de arranque. El algoritmo empleado ha variado en las sucesivas versiones del
programa. Desde la versión 6 del formateador (byte ubicado justo después del checksum) la zona total
afectada por el checksum va desde el offset 64 hasta justo antes del programa de arranque del disco. Las
versiones anteriores de 2M realizaban un checksum distinto, por lo que los discos formateados por ellas no
están sujetos a la comprobación de checksum para evitar problemas. La suma total de este área (en número
de 8 bits) debe dar un resultado 0. Por tanto, se permite modificar el programa de arranque e incluso los
campos del principio. Cualquier otro cambio no permitido hará que 2M falle en la comprobación del
checksum la primera vez que el disco es introducido en la unidad; en este caso INT 13h devuelve un Seek
Error poco habitual para señalizar la circunstancia. Sin embargo, un cambio en el campo ID (bytes 3 al 10)
podría acarrear que 2M no reconociera el disco como suyo. Quizá el lector opine que hubiera sido mejor ser
más tolerantes, pero yo opino que no: si el sector de arranque está corrompido, el código residente de 2M,
32 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
que no valida nada de dicho sector, podría estrellarse si se fía de la información del mismo. Así nadie podrá
decir: «se me cuelga al hacer DIR A:», como mucho: «me dice Seek Error y no me deja acceder al
disco». En realidad, es difícil que se produzcan estos errores porque nadie que intente alterar el sector de
arranque físico lo podrá conseguir con 2M en memoria, sin saber como hacerlo o sin acceder directamente a
la controladora.
+---------------------------------------+
| GAPs y /X e /Y probados en 2MF /F |
+---------+---------+---------+---------+
| 5¼-DD | 5¼-HD | 3½-DD | 3½-HD |
+--------------------------------------------------+---------+---------+---------+---------+
| GAP mínimo de lectura soportado en las pruebas | 1 | 2 | 1 | 2 |
| GAP mínimo de escritura soportado en las pruebas | 13 | 26 | 20 | 28 |
| GAP máximo de escritura soportado en las pruebas | 197 | 76 | 187 | 49 |
| GAP 3 de formateo adoptado finalmente | 100 | 50 | 100 | 40 |
| Valor óptimo obtenido experimentalmente para /X | 1 | 1 | 1 | 1 |
| Valor óptimo obtenido experimentalmente para /Y | 1 | 2 | 1 | 2 |
+--------------------------------------------------+---------+---------+---------+---------+
2MF ES EL FORMATEADOR PARA 2M. CON /F SE CREAN DISCOS NORMALES Y /M INDICA MÁXIMA CAPACIDAD.
Tras el checksum hay un byte que indica la versión del formateador, de cara a permitir que futuras
versiones de 2M sepan con qué formato de disco se enfrentan para respetar los viejos formatos (en caso de
que surjan otros nuevos). El siguiente byte indica si es necesaria una escritura tras el formateo: en los formatos
de más capacidad, trasformatear la pista hay que escribirla para evitar que una lectura posterior produzca
errores de CRC, como luego veremos y explicaremos. En los formatos normales este byte estará a 0, y a 1
en los de más capacidad.
Los siguientes 2 bytes indican la velocidad de transferencia a emplear en la primera pista (cilindro y
cabezal 0) y en las demás; el dato no está, por supuesto, en Kbit/seg sino que se trata del valor que hay que
enviar al registro de salida digital. En los disquetes de 3½-DD se utilizará la velocidad de 250 Kbit/seg en la
primera pista y 300 Kbit/seg en las demás. El motivo es que las primeras versiones de 2M delegaban parte
del trabajo de reconocer la densidad de disco a la BIOS, la cual sólo soporta 250 Kbit/seg en estas unidades.
Actualmente no sería necesario, ya que 2M detecta la densidad de los discos (y de hecho, sustituye a la BIOS
original en esta tarea), pero se ha mantenido por compatibilidad con los primeros formatos de disco de 2M.
Tras estos campos hay unos punteros a diversas áreas interesantes: el primero apunta al programa de
arranque y será empleado por dicho programa para conocer con comodidad su propia ubicación; después
hay un puntero a una tabla con información sobre la estructura de la primera pista del disco, otro puntero
apunta a una tabla con información de las demás pistas y, finalmente, un último puntero referencia una tabla de
tamaños de los sectores de las pistas (excepto la primera). Los últimos campos sólo se emplean desde 2M
3.0 y almacenan la fecha y hora de formateo.
La primera tabla contiene un byte que indica el número real de sectores de la primera pista, seguido de
otro byte con el valor de GAP 3 empleado al formatear. Después vienen los números de sectores, uno tras
otro, lo que permite elegir líbremente el interleave. Las últimas versiones de 2M acceden de manera eficiente a
la primera pista (y a todas las demás) soportando perfectamente un interleave 1:1, si bien los primeros
disquetes 2M fueron formateados con un factor 1:2. En los formatos de 1.80/1.88M la FAT ocupa 11
sectores, y otro el sector de arranque físico. Los sectores que van del 1 al 12 están, por lo tanto,
necesariamente ocupados; pero del 13 al 19 hay sitio para 7 sectores que pueden contener el BOOT virtual
(1 sector) y el código SuperBOOT (5 sectores). El sector restante se debe a que en discos de 1.88M con 84
pistas la FAT1 ocuparía un sector más.
33 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
+--------------------------------+--------------------------------------------------
| Capacidad bruta real antes de | Bytes netos obtenidos por los prin
| formatear (con 82 pistas y en +---------------------+---------------------+------
| controladora de alta densidad) | FORMAT (40/80p) (*) | FDFORMAT (82p) (**) | 2MF
+-------+--------------------------------+---------------------+---------------------+------
| 5¼-DD | 1.025.000 bytes (0,98 Mb) | 368.640 (360K) | 839.680 (820K) | 83
| 5¼-HD | 1.708.224 bytes (1,63 Mb) | 1.228.800 (1200K) | 1.511.424 (1476K) | 1.51
| 3½-DD | 1.230.000 bytes (1,17 Mb) | 737.280 (720K) | 839.680 (820K) | 1.00
| 3½-HD | 2.050.000 bytes (1,96 Mb) | 1.474.560 (1440K) | 1.763.328 (1722K) | 1.84
+-------+--------------------------------+---------------------+---------------------+------
(*) También FDFORMAT cuando se
(**) Formatos de máxima
La segunda tabla contiene información de las demás pistas del disco. El contenido y el formato de esta
tabla varía según el tipo de disco: los formatos normales (como el caso de 1.80M) poseen 5 bytes: el primero
indica el número de sectores de la pista, el siguiente el GAP 3 al formatear, otro byte indica el tamaño de
sector empleado (siempre 3, esto es, 1024 bytes) y los dos últimos bytes son equivalentes a los parámetros
/X e /Y de FDFORMAT para desplazar de manera óptima la numeración de los sectores en las pistas
consecutivas. Estos valores de /X e /Y son sensiblemente menores que los de FDFORMAT, pero no hay que
olvidar que aquí los sectores son dos veces más grandes. En los formatos de disco de máxima capacidad
(como en 1.88M) esta tabla cambia radicalmente de estructura: el primer byte sigue siendo el número de
sectores, pero ahora son sectores de 128 bytes. Esto se debe a que en estos formatos, las pistas son
preformateadas (en una primera pasada) con sectores de 128 bytes. El siguiente byte es el GAP 3, que como
se puede observar es muy pequeño (de 3 a 5 bytes). Finalmente, viene el número de sectores a renumerar.
La razón es que, durante el formateo, se asignan números a partir de 129 a la mayoría de los sectores; sin
embargo, algunos de ellos no se llevan el que les correspondería sino que siguen otra numeración más baja a
partir de 1. En estos sectores, además, al ser enviada su información al FDC durante el formateo, se indicará
un tamaño distinto de 128 (512, 1024 ó 2048). Así, por ejemplo, en 1.88M la pista queda formateada con
nada menos que 64 sectores de 128 bytes numerados desde 129, habiendo sin embargo algunos de ellos con
números más bajos (1, 2,..., 7) y definidos con mayor tamaño. Al ser escritos dichos sectores (segunda fase
del formateo) se machacarán los sectores de 128 bytes que les siguen y quedarán sólo ellos en la pista. Esto
permite colocar sectores de distinto tamaño en la pista. El GAP 3 definitivo será mayor (13 bytes en el peor
de los casos). Ahora comprenderá el lector por qué había que escribir la pista, después del formateo, en estos
formatos de disco... Por último, señalar que en esta tabla se elige un factor de interleave adecuado, que si se
echa un vistazo resulta ser de 1:2, ya que los sectores están demasiado próximos para numerarlos
consecutivamente (por razones de velocidad, si bien al ser accedidos uno a uno la controladora no tendría
problemas para encontrarlos). En el caso del formato 1.88M, por ej., quedan numerados: 4,1,5,2,6,3,7.
La última tabla es la única que realmente emplea 2M para acceder a todas las pistas, con excepción de la
primera. Se trata de una lista ordenada de los tamaños de los sectores. En los formatos de disco normales es
una lista de treses, ya que todos los sectores son iguales y de 1024 bytes. En los formatos de máxima
capacidad, como 1.88M, se puede comprobar que la lista es más variada. Las otras dos tablas vistas con
anterioridad sólo son empleadas durante el formateo del disco.
El formateo de disquetes 2M se realiza con un programa que veremos más adelante, 2MF.EXE, que
permite elegir entre formatos normales (2MF sin parámetros o con la opción /F) y formatos de máxima
capacidad (2MF /M). Como se vio en la descripción del sector de arranque, el formato de máxima capacidad
logra introducir sectores de distinto tamaño en la misma pista. Seguramente la descripción dada en el apartado
anterior no ha quedado muy clara, por lo que ahora puntualizaremos un poco más.
Uno de los principales objetivos al realizar 2M fue conseguir un nivel de compatibilidad lo suficientemente
alto, incluso en los formatos menos seguros como el que se describirá a continuación, al menos en
34 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
comparación con los ya estudiados de sectores de 1 Kb. Hay disqueteras de 1.44M que soportan el
formateo de 3 sectores de 4096 bytes en una pista, lo que permitiría obtener 1968K (en 82 cilindros,
soportados por prácticamente todas las unidades). Sin embargo, hay muchos ordenadores en que esto no es
posible, por tanto esta solución fue descartada. En los casos en que es posible, lo es además a costa de
rebasar con creces los mínimos niveles de seguridad (machacando no sólo el GAP ubicado al final de la pista,
sino también el del principio e incluso el IAM; resulta increíble que algunas controladoras de disquete
continúen reconociendo los sectores). Además, se trataría de una solución exclusiva para disquetes de
1.44M.
El truco explicado con anterioridad consiste en formatear los discos con sectores muy pequeños de 128
bytes, pero definiéndoles con tamaños de 512, 1024 y 2048 bytes al enviar la información de cada sector al
controlador, de cara a agruparles posteriormente para obtener sectores de mayor tamaño. Echando cuentas,
con un GAP 3 provisional de sólo 3 bytes (podríamos denominarlo GAP virtual) cada sector ocupa
128+62+3 = 193 bytes. Agrupando 11 de estos sectores se obtienen 193*11=2123 bytes, suficientes para
contener un sector de 2048 bytes, los 60 bytes añadidos al principio del primer sector de 128 bytes por el
FDC, los 2 bytes añadidos al final del último sector por el FDC y otros 13 bytes de GAP 3. Agrupando 6
sectores se obtienen 1158 bytes, suficientes para contener un sector de 1024 bytes con un GAP 3 de 72
bytes. Finalmente, agrupando 3 se consiguen 579 bytes, en los que cabe un último sector de 512 bytes con un
GAP 3 de 5 bytes. Así, en un disquete estándar de 1.44M, con 12500 bytes por pista, donde caben bastante
holgadamente 64 sectores de 128 bytes de las características mencionadas, se pueden colocar 5 grupos de
11, 1 de 6 y otro de 3. En total: 11,5 Kb en cada pista (1886 en todo el disco, a 82 cilindros). Una vez
formateada la pista, es conveniente escribir todos los sectores (la primera lectura daría error de CRC en caso
contrario), de paso se asegura de esta manera, en una posterior lectura, que la escritura no ha provocado que
ningún sector pise a otro, asegurando la fiabilidad del método. Una vez que el disco ha sido formateado, la
verificación realizada durante el formateo garantiza que es seguro; la separación o GAP 3 medio menor es de
13 bytes y puede considerarse bastante razonable (el sector de 512 bytes con un GAP 3 de sólo 5 es
colocado siempre al final de la pista); en los disquetes de doble densidad es además superior, al emplearse un
GAP 3 virtual en la primera fase de 4 ó 5 bytes en vez de 3.
El formateo es relativamente lento, ya que requiere tres fases: formateo, escritura y lectura para verificar;
cada una de ellas, dada la proximidad de los sectores, requiere de dos vueltas del disco (los sectores estarán
numerados alternamente con un razonable interleave 1:2); en total, 6 vueltas en un disco de 1.44M por cada
pista, lo que equivale a 1,2 segundos por pista y 3:17 minutos en el conjunto del disquete (2 caras y 82
cilindros). Este es el precio que hay que pagar para obtener 1.912.320 bytes libres netos (los que aparecen al
hacer un DIR) frente a los 1.457.664 conseguidos por el FORMAT del DOS.
Un último detalle a tener en cuenta es que, en este tipo de formato, al escribir el cabezal 1 del cilindro 0, el
código de 2M se saltará el acceso al primer sector de la pista (al estar la FAT2 en él, por regla general, y
debido a las emulaciones). Por tanto, en este caso, es necesario escribir en el cabezal 129 para asegurar que
realmente se escribe la pista y el disco queda correctamente inicializado. Por comodidad, se puede escribir en
el cabezal 128/129 de todas las pistas (salvo la primera, que no tiene realmente tantos sectores como las
demás y que además tampoco es necesario escribir tras el formateo).
2M es un programa residente ordinario que desvía la INT 13h/40h. En las máquinas AT con disco duro de
tipo IDE (los más extendidos actualmente) o con una controladora de disco duro ordinaria de AT, la BIOS
desvía a INT 40h los servicios de disquete, siendo invocada esta interrupción desde la INT 13h para atender
las funciones de disquete. Sin embargo, si el ordenador no tiene disco duro o incorpora una controladora de
disco duro de XT, es la INT 13h quien podría controlar los disquetes. La versión 1.0 de 2M desviaba la INT
40h en lugar de la INT 13h, por el motivo que ahora analizaremos (ayuda en la cuestión del DMA); sin
embargo, ésto hacia que el programa no funcionara en algunas máquinas AT sin disco duro o con
35 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
controladora de XT. Por ello, en la versión 1.1 se volvió a trabajar con INT 13h. Pero desde 2M 2.0+,
aunque ahora más por razones de seguridad que de comodidad, se utiliza una técnica mixta: si el ordenador
emplea la INT 40h, 2M se instala desde esta interrupción; en caso contrario, lo hace desde INT 13h
(actuándo desde INT 40h el programa toma el control de los discos antes que otros TSR instalados después).
Y volvamos sobre la cuestión del DMA, que motivó el uso de INT 40h en 2M 1.0. Como el lector recordará,
a la hora de transferir con la disquetera hay que tener cuidado con las fronteras de DMA. Sin embargo,
resultaría muy engorroso tener que tener esto en cuenta en los programas de alto nivel. El propio DOS
considera que es un auténtico fastidio tener que comprobar esto cada vez que se accede al disco. Por ello,
cuando el sistema operativo se carga en el ordenador desvía la INT 13h y la modifica para arreglar de un
plumazo los problemas con el DMA: a partir de ese momento, la INT 13h es realmente controlada por el
DOS, aunque se trate de una interrupción BIOS. Las nuevas rutinas de la INT 13h colocadas por el DOS se
limitan a llamar a la vieja INT 13h (nadie ha hablado aún de INT 40h) y, cuando se produce un error de
frontera de DMA, la operación de disco que lo había provocado es segmentada probablemente en tres fases:
los sectores que estaban antes de la frontera, los que quedan por detrás y el que cae justo en medio; este
sector es probablemente transferido a través de un buffer intermedio del sistema.
+-----------------------------------------------------------------------------------
| Porcentaje de disco aprovechado (perdido) tras el formateo
+--------------------+--------------------+--------------------+--------------------
| FORMAT | FDFORMAT 1.8 | 2MF 3.0 /F | 2MF 3.0 /M
+-------+--------------------+--------------------+--------------------+--------------------
| 5¼-DD | 35,96% (64,04%) | 81,92% (18,08%) | 81,92% (18,08%) | 90,11% ( 9,89%)
| 5¼-HD | 71,93% (28,07%) | 88,48% (11,52%) | 88,48% (11,52%) | 93,39% ( 6,61%)
| 3½-DD | 59,94% (40,06%) | 68,27% (31,73%) | 81,92% (18,08%) | 88,75% (11,25%)
| 3½-HD | 71,93% (28,07%) | 86,02% (13,98%) | 90,11% ( 9,89%) | 94,21% ( 5,79%)
+-------+--------------------+--------------------+--------------------+--------------------
| Media | 59,94% (40,06%) | 81,17% (18,83%) | 85,60% (14,40%) | 91,62% ( 8,38%)
+-------+--------------------+--------------------+--------------------+--------------------
Si 2M se instala colgando de INT 13h, al introducir un disquete de tipo 2M (cuyo control evidentemente
corre a cargo de 2M) todas las llamadas del DOS a la INT 13h serían llamadas a 2M, que ha sido instalado
después de que el DOS arregle la INT 13h. Por tanto, 2M debe en ese caso ocuparse de la engorrosa
gestión de errores de DMA, ya que el DOS no espera nunca este tipo de error de una llamada a la INT 13h.
En la práctica, 2M a partir de la versión 1.1, en las operaciones que afectan a varios sectores de disco
consecutivos, se ve obligado a detectar con antelación el futuro cruce de una frontera de DMA: en caso de
que se vaya a producir, el sector problemático es transferido a través del buffer intermedio del programa. La
versión 1.0 de 2M desviaba INT 40h en vez de INT 13h y se limitaba a devolver la condición de error
cuando se iba a producir, para que el propio DOS en INT 13h llamara de nuevo con más cuidado.
2M podría haber sido creado como controlador de dispositivo que definiera nuevas letras de unidad para
soportar los nuevos disquetes; sin embargo resulta más intuitivo para el usuario continuar empleando las
unidades A: y B: habituales. Esto se consigue, como hemos visto, modificando la INT 13h de la BIOS, lo que
además permite el funcionamiento de ciertas utilidades de bajo nivel en los nuevos disquetes; realmente, en el
mundo del PC no hay casi programas de utilidad a bajo nivel con el disco. Salvo los copiones, la mayoría de
los llamados programas de bajo nivel en materia de disquetes se limitan a llamar a la BIOS. La técnica de
ampliar la funcionalidad de la INT 13h de la BIOS es, por tanto, la más eficiente.
El listado que comentaremos es sólo la parte importante del programa. Desde 2M 3.0 ya no hay listados
con partes repetidas: un único fichero 2M.ASM produce 2M.COM (sistemas AT) y 2MX.COM (en PC/XT)
por medio del ensamblaje condicional. Para ello se apoya en 2MKERNEL.INC, núcleo principal con todo el
código de acceso a la controladora para soportar los discos 2M, y también empleado para generar 2M.SYS
36 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
(versión driver para AT) y 2MFBOOT.BIN (con código SuperBOOT para el formateador). También se
utiliza 2MUTIL.INC para englobar ciertas rutinas de utilidad comunes a más programas de la aplicación. Aquí
nos limitaremos a comentar 2MKERNEL.INC, ya que lo restante no está relacionado con la controladora de
discos.
2M puede controlar las unidades de disco A: y B: si son de alta densidad (de lo contrario se limita a
invocar a la INT 13h original). Por ello, además de un juego de variables globales, hay una estructura que
define las variables propias de una unidad que se emplea para crear dos áreas de datos particulares, una para
cada disquetera. A lo largo de la mayoría del código residente, el registro SI estará apuntando a esa zona de
variables locales de la disquetera que se trate. Al principio del programa está la rutina que controla la
interrupción 2Fh, empleada para gestionar la autodetección en memoria del programa residente y permitir su
posible futura desinstalación.
+----------------------------------------------------------------------------------+
| Longitud (ms) Sector Tamaño Cilindro Cabeza ST0 ST1 ST2 |
| ------------------- ------ ------------ -------- ------ ----- ----- ----- |
| [ 19.58] 19.58 10 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 37.44] 17.86 11 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 55.31] 17.87 1 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 73.18] 17.87 2 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 91.05] 17.87 3 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 108.91] 17.86 4 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 126.79] 17.87 5 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 144.65] 17.86 6 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 162.52] 17.87 7 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 180.39] 17.87 8 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 198.26] 17.87 9 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 217.85] 19.59 10 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 235.71] 17.86 11 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 253.71] 18.00 1 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 271.57] 17.86 2 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 289.44] 17.87 3 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 307.43] 17.99 4 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 325.43] 17.99 5 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 343.42] 17.99 6 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 361.28] 17.87 7 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 379.16] 17.87 8 1024 ( 3) 0 1 0x04 0x00 0x00 |
| |
| Una tecla para leer más ID's [ESC=salir]. |
+----------------------------------------------------------------------------------+
+----------------------------------------------------------------------------------+
| Longitud (ms) Sector Tamaño Cilindro Cabeza ST0 ST1 ST2 |
| ------------------- ------ ------------ -------- ------ ----- ----- ----- |
| [ 33.95] 33.95 3 2048 ( 4) 0 1 0x04 0x00 0x00 |
| [ 45.32] 11.37 7 512 ( 2) 0 1 0x04 0x00 0x00 |
| [ 79.14] 33.82 4 2048 ( 4) 0 1 0x04 0x00 0x00 |
| [ 112.94] 33.80 1 2048 ( 4) 0 1 0x04 0x00 0x00 |
| [ 146.76] 33.82 5 2048 ( 4) 0 1 0x04 0x00 0x00 |
| [ 180.58] 33.82 2 2048 ( 4) 0 1 0x04 0x00 0x00 |
| [ 198.97] 18.39 6 1024 ( 3) 0 1 0x04 0x00 0x00 |
| [ 232.78] 33.82 3 2048 ( 4) 0 1 0x04 0x00 0x00 |
| [ 244.16] 11.37 7 512 ( 2) 0 1 0x04 0x00 0x00 |
| [ 277.97] 33.81 4 2048 ( 4) 0 1 0x04 0x00 0x00 |
| [ 311.78] 33.81 1 2048 ( 4) 0 1 0x04 0x00 0x00 |
| [ 345.60] 33.81 5 2048 ( 4) 0 1 0x04 0x00 0x00 |
| [ 379.42] 33.82 2 2048 ( 4) 0 1 0x04 0x00 0x00 |
| [ 397.80] 18.38 6 1024 ( 3) 0 1 0x04 0x00 0x00 |
37 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
La rutina que controla la INT 13h ó INT 40h es más importante. Su labor consiste en pasar el control de
las funciones 2 (lectura), 3 (escritura), 4 (verificación) y 5 (formateo) a 2M (si el disquete introducido es de
este tipo) o a la interrupción original (si el disquete introducido no es de tipo 2M). Existe una variable por
cada unidad que indica en todo momento si el disquete introducido es de tipo 2M (control2m_flag=ON) o
no. Otro cometido consiste en detectar los cambios de disco, para actualizar dicha variable en consecuencia.
Ante el primer cambio de disco detectado se retorna con un error 6 (porque así lo hace la BIOS original).
En el caso de la función de formateo (no implementada en el código SuperBOOT por falta de espacio), se
mira si quien la invoca solicita un formateo normal o si se trata de una petición de formateo de disquete 2M.
Esto es debido a que 2M aumenta la funcionalidad de la función 5 original de la BIOS para soportar los
nuevos disquetes. En la función de la BIOS, se indica en AL el número de sectores de la pista, en CH la pista,
en DH el cabezal, en DL la unidad y en ES:BX se apunta a un buffer con información para formatear. Cuando
está 2M residente y se invoca la función 5 con el registro SI=324Dh (SI="2M") y con AL=7Fh, se le indica a
2M que no llame a la función de formateo original de la BIOS y que formatee él la pista en la unidad y cabezal
indicados. En este caso AL es ignorado, ya que en ES:BX lo que se le pasa a la BIOS (es decir, a 2M) no es
la dirección de tabla alguna sino el sector de arranque del futuro disquete, que contiene toda la información
necesaria sobre la estructura del disco para poder clonarlo. No hay que crear tablas ni emplear otras
funciones BIOS para seleccionar densidad ni nada por el estilo. Tampoco hay que considerar la complejidad
de los formatos 2M (en los que difiere la primera pista de las restantes): de todo se ocupa el código residente
del propio 2M. La rutina format_2m invocada desde ges_int13 se encarga del formateo. Primero se llama a
la INT 13h original (previa a 2M) para solicitar un formateo en el cabezal 2, inexistente, con objeto de que
retorne rápidamente ante el error. Así, se avisa a todos los demás programas residentes de que el disco va a
ser formateado: el propio DOS invalida los buffers asociados al viejo disquete; si 2M no tomara esta medida,
al hacer DIR sobre el disco recién formateado aparecería aún, falsamente, su contenido previo. A
continuación realiza las siguientes tareas: toma nota de los parámetros del futuro disco, pone en marcha el
motor, lleva el cabezal a la pista, crea la tabla con información para el formateo, formatea la pista y retorna
con el código de error o éxito correspondiente. En los formatos de máxima capacidad, recuérdese que había
que escribir la pista tras el formateo, para evitar que la primera lectura diera error y para completar realmente
el proceso. Sin embargo, el código residente de 2M no escribe nada tras el formateo. Esto permite en este
caso a los programas de copia de disquetes poder ir escribiendo el disco destino a la vez que formatean; lo
contrario sería una pérdida de tiempo con una escritura muerta. En el caso de programas que sólo formateen,
tendrán además que escribir; esto implica que esos programas deben estar diseñados para formatear
disquetes 2M (nadie ha dicho que el FORMAT del DOS pudiera hacerlo por sí solo).
+----------------------------------------------------------------------------------+
| Longitud (ms) Sector Tamaño Cilindro Cabeza ST0 ST1 ST2 |
| ------------------- ------ ------------ -------- ------ ----- ----- ----- |
| [ 31.72] 31.72 2 1024 ( 3) 0 1 0x05 0x00 0x00 |
| [ 63.27] 31.55 3 1024 ( 3) 0 1 0x05 0x00 0x00 |
| [ 103.25] 39.98 4 1024 ( 3) 0 1 0x05 0x00 0x00 |
| [ 134.76] 31.51 5 1024 ( 3) 0 1 0x05 0x00 0x00 |
| [ 166.35] 31.59 1 1024 ( 3) 0 1 0x05 0x00 0x00 |
38 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
+----------------------------------------------------------------------------------+
| Longitud (ms) Sector Tamaño Cilindro Cabeza ST0 ST1 ST2 |
| ------------------- ------ ------------ -------- ------ ----- ----- ----- |
| [ 56.44] 56.44 3 2048 ( 4) 0 1 0x05 0x00 0x00 |
| [ 112.90] 56.46 1 2048 ( 4) 0 1 0x05 0x00 0x00 |
| [ 143.63] 30.73 4 1024 ( 3) 0 1 0x05 0x00 0x00 |
| [ 158.92] 15.29 2 512 ( 2) 0 1 0x05 0x00 0x00 |
| [ 165.85] 6.93 0 128 ( 0) 0 1 0x05 0x00 0x00 |
| [ 222.30] 56.45 3 2048 ( 4) 0 1 0x05 0x00 0x00 |
| [ 278.75] 56.45 1 2048 ( 4) 0 1 0x05 0x00 0x00 |
| [ 309.49] 30.73 4 1024 ( 3) 0 1 0x05 0x00 0x00 |
| [ 324.78] 15.29 2 512 ( 2) 0 1 0x05 0x00 0x00 |
| [ 331.70] 6.92 0 128 ( 0) 0 1 0x05 0x00 0x00 |
| [ 388.16] 56.46 3 2048 ( 4) 0 1 0x05 0x00 0x00 |
| [ 444.61] 56.45 1 2048 ( 4) 0 1 0x05 0x00 0x00 |
| [ 475.34] 30.73 4 1024 ( 3) 0 1 0x05 0x00 0x00 |
| [ 490.63] 15.29 2 512 ( 2) 0 1 0x05 0x00 0x00 |
| [ 497.55] 6.92 0 128 ( 0) 0 1 0x05 0x00 0x00 |
| [ 554.01] 56.45 3 2048 ( 4) 0 1 0x05 0x00 0x00 |
| [ 610.46] 56.45 1 2048 ( 4) 0 1 0x05 0x00 0x00 |
| [ 641.19] 30.73 4 1024 ( 3) 0 1 0x05 0x00 0x00 |
| [ 656.48] 15.29 2 512 ( 2) 0 1 0x05 0x00 0x00 |
| [ 663.41] 6.93 0 128 ( 0) 0 1 0x05 0x00 0x00 |
| [ 719.86] 56.45 3 2048 ( 4) 0 1 0x05 0x00 0x00 |
| |
| Una tecla para leer más ID's [ESC=salir]. |
+----------------------------------------------------------------------------------+
LECTURA DE ID's EN 5¼-DD (FORMATO NORMAL Y DE MAXIMA CAPACIDAD)
39 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
Notas:
- Ficheros: 2 de 256K, 3 de 128K, 4 de 64K, 5 de 32K, 6 de 16K y 1 de 15.5K.
- Prueba bajo DOS 6.2 y con solo 2M y FDREAD instalados.
- La prueba de escritura consistía en COPY C:\TEST\*.* B: y la de lectura
consistía en COPY /B *.* NUL
- Al leer del disco duro se perdieron 5.5 segundos que han sido ya descontados;
el disco ya estaba girando.
- Con FDFORMAT se emplearon siempre los parámetros /X:2 e /Y:3 para lograr
la mayor velocidad posible.
La rutina calc_chk es quien realmente realiza el checksum del sector de arranque, comprobando además
si el disco es de tipo 2M. La rutina set_err, invocada al final del formateo y desde la rutina que accede
directamente a los sectores de disco, analiza el código de error devuelto por el controlador de disquetes y lo
convierte a la notación de errores de la BIOS. Set_bios_err copia el resultado del acceso a disco a las
40 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
variables propias de la BIOS por razones de compatibilidad con el software de disco de bajo nivel.
En el procedimiento control_2m se realiza la gestión a alto nivel del acceso a disco: es aquí donde se
emula la existencia de la segunda copia de la FAT apoyándose en la primera, así como el sector de arranque
virtual ubicado en el primer sector físico de la FAT2. Como 2M 2.0 apareció cuando ya estaba bastante
extendida la versión anterior, se hizo necesario (y lo sigue siendo en 2M 3.0) continuar soportando los discos
antiguos. En ellos, se sigue leyendo el sector de arranque físico en lugar del virtual, que no existe, y se permite
su escritura si es correcto (si no se intentan tocar partes sensibles del mismo). Así mismo se tiene en cuenta el
acceso al cabezal 128 ó 129 para acceder en ese caso al 0 ó al 1 sin emulaciones. Las coordenadas de la
BIOS, en la forma cilindro-cabezal-sector son traducidas momentáneamente a las del DOS para simplificar el
proceso. También se comprueba si el checksum (o suma de comprobación) del sector de arranque, realizado
con anterioridad en set_info, es correcto. Es difícil que no lo sea, porque el código de 2M no deja a
cualquiera escribir sobre el sector de arranque físico. Pero si no lo fuera, se devuelve un seek error al
programa que llama a la INT 13h, habiéndose elegido este código porque no había otro más descriptivo en la
lista de errores de disco de la BIOS. Si al ejecutar un comando DIR sobre un disquete 2M aparecen errores
de seek ya sabrá el lector por qué...
En el caso de los formatos de mayor capacidad (2MF /M) se accede de sector en sector físico, ya que las
operaciones de lectura/escritura de varios sectores en bloque sólo tienen sentido cuando éstos están lo
suficientemente separados pero sin pasarse. En nuestro caso están excesivamente separados, ya que la
numeración es discontinua (interleave 1:2) y entre dos sectores de número consecutivo hay otro; por tanto, no
se ganaría rendimiento en un acceso multisector; por otro lado, algunos formatos de disco tienen un número
par de sectores en las pistas y dos de ellos tienen que tener forzosamente el número consecutivo, con lo que
fallaría el acceso multisector debido a la excesiva proximidad en este caso; además, no está muy claro si se
podrán acceder de esta manera sectores que no sean del mismo tamaño (no me molesté en probarlo). La
lectura es la operación más sencilla: se extrae del disco el sector físico donde está incluida la sección que toca
leer y después se copia a la dirección de memoria definitiva. No se puede leer el sector directamente en el
buffer requerido por el programa que invoca la INT 13h, ya que éste podría requerir sólo 512 bytes (o un
múltiplo impar de esta cifra) y los sectores físicos podrían exceder este tamaño, afectando a zonas no
permitidas de la memoria ubicadas tras el buffer. Por tanto se utiliza un buffer intermedio (definido con un
tamaño de 2 Kb para acomodar el mayor sector posible). El movimiento de la sección a su ubicación
definitiva no es una tarea muy costosa, ya que en un ordenador medio se ejecuta unas cien veces más rápido
que lo que ha tardado la lectura desde el disco. Este proceso de lectura se repite tantas veces como secciones
haya que transferir. En todo momento, unas variables indican qué sector físico (y de qué cilindro, cabezal y
unidad) está en el buffer. De este modo, por ejemplo, cuando se lee un sector de 2 Kb para transferir su
primera sección, se traen a la memoria 4 secciones de golpe y ya no serán necesarios más accesos a disco si
hubiera que transferir también las 3 restantes, porque el sector en que están ya se encuentra en el buffer. La
escritura es algo más compleja, y hay que distinguir dos casos: por un lado, cuando hay que volcar a disco un
número de secciones consecutivas suficientes para completar un sector físico; por otro, cuando hay que
escribir una o varias secciones que no completan un sector físico. En el primer caso, se escribe sin más; en el
segundo caso es necesario leer el sector al buffer, modificar sólo la(s) seccion(es) afectada(s) y escribirlo en
el disco. Este último caso supone una fuerte degradación de la velocidad, ya que tras leer un sector del disco
habrá que volver a escribirlo, hecho que no ocurrirá hasta la siguiente vuelta del mismo. Por fortuna,
cuando se hace un COPY el DOS envía grandes bloques, lo que en la mayoría de los casos (no en todos)
provoca escrituras de pistas completas, tarea en la que no se pierde un ápice de rendimiento. No obstante,
41 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
esta arquitectura de los disquetes 2M provoca que sean notablemente más lentos escribiendo que leyendo.
+--------------------------------------------------+
| MAPAMEM 2.1 |
| - Información sobre la memoria del sistema. |
| |
| Tipo Ubicación Tamaño PID Propietario |
| -------- --------- ------- ----- --------------- |
| Sistema 0000-003F 1.024 Interrupciones |
| Sistema 0040-004F 256 Datos del BIOS |
| Sistema 0050-0105 2.912 Sistema Operat.|
| Sistema 0107-0143 976 0008 |
| Sistema 0145-0144 0 0008 |
| Sistema 0146-0149 64 0008 |
| Programa 014B-015A 256 014B 4DOS |
| Entorno 015C-0174 400 0176 MAPAMEM |
| Programa 0176-01C9 1.344 0176 MAPAMEM |
| Libre 01CB-9FFE 648.000 0000 <Nadie> |
| Sistema A000-D3B4 211.792 0008 |
| Sistema D3B6-D3C2 208 D3B6 |
| Sistema D3C4-D50D 5.280 D3C4 |
| Sistema D50F-E437 62.096 0008 |
| Sistema E439-E49C 1.600 E439 |
| Sistema E49E-E4AD 256 E49E |
| Sistema E4AF-E4CE 512 E4AF |
| Sistema E4D0-E55E 2.288 E4D0 |
| Sistema E560-E568 144 E560 |
| Datos E56A-E631 3.200 014B 4DOS |
| Entorno E633-E672 1.024 014B 4DOS |
| Libre E674-E68C 400 0000 <Nadie> |
| Programa E68E-E810 6.192 E68E SHARE |
| Programa E812-E97A 5.776 E812 PRINT |
| Entorno E97C-E996 432 E998 VIDRAM |
| Programa E998-EA04 1.744 E998 VIDRAM |
| Entorno EA06-EA1F 416 EA21 UNIVESA |
| Programa EA21-EBF1 7.440 EA21 UNIVESA |
| Programa EBF3-EC1D 688 EBF3 KEYBSP |
| Programa EC1F-EC77 1.424 EC1F RCLOCK |
| Programa EC79-EDBB 5.168 EC79 2M |
| Programa EDBD-EDD8 448 EDBD DISKLED |
| Libre EDDA-EDF3 416 0000 <Nadie> |
| Programa EDF5-F281 18.640 EDF5 DATAPLUS |
| Programa F283-F34D 3.248 F283 HBREAK |
| Programa F34F-F354 96 F34F TDSK(D) |
| Datos F356-FB55 32.768 F34F TDSK(D) |
| Libre FB57-FFA5 17.648 0000 <Nadie> |
+--------------------------------------------------+
MEMORIA OCUPADA POR 2M
En los formatos normales (2MF /F) todos los sectores de la pista son del mismo tamaño, lo que también
sucede en la primera pista de los formatos de más capacidad. Están suficientemente separados y numerados
consecutivamente. Por tanto, una acceso multisector es posible y más que interesante. Aquí no sólo no se
emplea el buffer intermedio sino que además no se puede, porque el acceso multisector puede superar los 2
Kb de capacidad del buffer. La transferencia se hace directamente sobre la dirección deseada por el
programa que invoca la INT 13h. Sólo hay un par de excepciones: cuando la primera sección a transferir es la
segunda mitad de un sector (recordemos que son de 1 Kb) y cuando la última sección es la primera mitad de
un sector. En ambos casos se emplea el buffer intermedio por el mismo motivo de siempre: evitar la alteración
de zonas de memoria que vayan detrás del buffer suministrado por el programa que llama a la INT 13h.
42 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
Sobre la escritura se podrían hacer las mismas consideraciones que hacíamos con los formatos de máxima
capacidad. En la operación de acceso multisector hay que considerar también el posible cruce del buffer
suministrado por el programa principal con una frontera de DMA: la rutina acceso_multi se encarga, llegado
el momento, de transferir el sector crítico a través del buffer intermedio, segmentando la operación en tres
fases (los sectores anteriores, el sector que cruza la frontera y los restantes). No controlar los problemas con
el DMA provoca que el ordenador se cuelgue al hacer COPY de un fichero mediano (o que lo copie mal en
cualquier caso). Obviamente, el buffer intermedio se inicializa para que nunca cruce una frontera de DMA. El
único caso en que acceso_multi no necesita tomar precauciones con el DMA es en el código SuperBOOT:
aunque se instale desde la INT 13h, lo hace antes de la carga del sistema operativo (que será el encargado de
arreglar los problemas con el DMA).
Por tanto, en ejecuta_io es donde se toman todas las complicadas decisiones sobre cómo y dónde
cargar/grabar de disco. He de agradecer aquí a Edgar Swank su colaboración en detectar y corregir errores
en esta compleja rutina, proponiéndome además las modificaciones en el listado: antes de 2M 2.0, los discos
2M no soportaban realmente la escritura con verificación (VERIFY ON a nivel DOS). La variable
sector_fin está a 0 para indicar el acceso a un solo sector (sector_ini) o es distinta de cero para indicar el
último sector involucrado en el caso de accesos multisector (junto a sector_ini). Dentro de este
procedimiento, la subrutina acceso_secc se encarga de la transferencia de una sola sección.
El procedimiento trans_secc realiza las transferencias entre el buffer interno y la dirección suministrada
por el programa que llama a la INT 13h. La rutina leido? comprueba si el próximo sector físico a ser
accedido esta ya en el buffer, para evitar una segunda lectura innecesaria.
El procedimiento acceso_sector se encarga de hacer ciertas tareas como determinar la longitud del sector
a ser leído (para poder programar luego correctamente el FDC), llevar el cabezal a la pista adecuada, cargar
los registros convenientemente según haya que emplear el buffer intermedio o no, llamar a la rutina que accede
realmente al disco y tomar nota de qué sector ha sido recién leído (para evitar futuras lecturas innecesarias).
En num_secciones se calcula el número de secciones o bloques de 512 bytes del sector físico en curso,
apoyándose en la información del sector de arranque del disquete que fue anotada cuando se le reconoció por
vez primera.
La rutina motor_ok arranca el motor de la unidad si aún no estaba en marcha. En caso de estar parado, o
de llevar poco tiempo encendido a causa de una reciente lectura de la línea de cambio de disco (el contador
de tiempo que resta para su detención es aún muy alto) se hace la pausa pertinente para que alcance el
régimen de rotación adecuado. Esta rutina es invocada en varias ocasiones; entre otras, desde ejecuta_io.
+-------------------------------------------------------------------+
| |
| [14464/109040] C:\>dir b: |
| |
| Volume in drive B is unlabeled Serial number is EA82:3F1B |
| File not found "B:\*.*" |
| 0 bytes in 0 file(s) |
| 1.912.320 bytes free |
| |
| [14464/109040] C:\>diskcopy b: b: |
| |
| Inserte el disquete de ORIGEN en la unidad B: |
| |
| Presione cualquier tecla para continuar . . . |
| |
| Copiando 82 pistas |
| 23 sectores por pista, 2 cara(s) |
43 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
| |
| Inserte el disquete de DESTINO en la unidad B: |
| |
| Presione cualquier tecla para continuar . . . |
| |
+-------------------------------------------------------------------+
LA COMPATIBILIDAD DE 2M ES PRACTICAMENTE DEL 100%
La rutina seek_drv posiciona el cabezal seleccionado sobre el cilindro adecuado: si ya estaba sobre él
(por haber accedido con anterioridad a la otra cara del disco) no es necesario esperar a que el cabezal deje
de vibrar; en caso de que haya que hacer esta pausa se establecen 1 ms para el caso de la lectura (no es muy
peligroso que se produzca un error, ya que la operación se reintentaría) y 15 ms para la escritura, asegurando
en este último caso el éxito de la operación, ya que escribir con el cabezal no asentado podría dañar la
información del disco. El disco está formateado (salvo en los los formatos de máxima capacidad, que son un
mundo aparte) con ciertos deslizamientos en la numeración de los sectores al conmutar de cilindro y cabezal
(opciones /X e /Y del formateador) de tal manera que el acceso en escritura es factible en una sola vuelta del
disco para todas las pistas a las que se acceda consecutivamente. Rebajar a 1 ms en el caso de la lectura tiene
por objeto asegurar esto mucho más todavía. Así, algún ordenador muy extraño que pinchara en los índices
de rendimiento a la hora de escribir probablemente no lo haría, al menos, al leer. Como un posicionamiento
del cabezal precede siempre a las operaciones de lectura o escritura (seek_drv), se selecciona aquí la
velocidad de transferencia a emplear, acorde con la densidad de la pista a ser accedida (set_rate). En caso
de que la unidad precisara recalibración (debido a algún reset anterior) se llama desde aquí al procedimiento
recalibrar.
El procedimiento sector_io es quien finalmente se encarga de hacer la lectura o escritura del sector o
sectores necesarios, programando el FDC. Se calcula el tamaño en bytes del bloque a transferir, se programa
el DMA por medio de las rutinas calc_dir_DMA y prepara_DMA y se envía el comando adecuado al
FDC (lectura/escritura). Al final, se anotan los resultados. La subrutina calc_dir_DMA traduce la dirección
segmentada al formato necesario para programar el DMA; en el código SuperBOOT tiene que devolver
además un posible error de cruce de frontera de DMA, ya que el código de 2M no evita las llamadas ilegales
en este caso.
En genera_info se construye la tabla de información a enviar al DMA para formatear la pista solicitada en
la función de formateo de 2M. Esta información se obtiene a partir del sector de arranque del futuro disco,
suministrado por el programa que intenta formatear. Conociendo cómo esta estructurado dicho sector, la
arquitectura de los disquetes 2M y qué necesita el comando del FDC para formatear se puede entender cómo
funciona la rutina, por lo que no nos detendremos en analizarla. Es formatea_pista el procedimiento que
formatea la pista a partir de la tabla creada por la rutina anterior.
La subrutina espera_int espera durante no más de 2 segundos la llegada de una interrupción de disquete
que señalice el final de una operación con el FDC. Conviene no esperar indefinidamente porque si la unidad
no está preparada podría tardar muchísimo en devolver la interrupción. Así, se detecta en un tiempo razonable
la circunstancia y posteriormente se reseteará la controladora (ante el error) para arreglar el problema de la
interrupción pendiente (y del FDC que no respondía). Fdc_read y fdc_write se encargan de recibir y enviar
bytes al FDC, típicamente órdenes y resultados. Ambas rutinas también tienen control timeout, en este caso
de 2 milisegundos; al principio de las mismas se realiza una brevísima pausa al igual que hacen las BIOS AMI
de 486 (que para algo servirá). Finalmente, las subrutinas fdc_respiro y retardo efectúan una pausa de 60
44 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
mus y AX milisegundos, respectivamente, apoyándose repetitivamente en la macro pmicro, que pierde unos
15,09 microsegundos muestreando los ciclos de refresco de memoria del AT. Pmicro no es una subrutina
(salvo en el caso del código SuperBOOT, por razones de espacio) porque el CALL y RET asociados
podrían ralentizar la monitorización de los ciclos de refresco de manera excesiva en los ordenadores más
lentos, deparando un retardo efectivo superior.
Finalmente, initcode será invocada sólo desde el sector de arranque físico durante el arranque desde
disquete, con objeto de inicializar ciertas variables y activar el código SuperBOOT. Una precaución
importante es que, ensamblando para obtener código SuperBOOT, éste tiene que ocupar exactamente 2560
bytes (5 sectores). Ciertamente, entra muy justo... pero cabe, con alguna que otra artimaña (excluir rutinas de
formateo, utilizar subrutinas en vez de macros, simplificar la gestión de las fronteras de DMA, etc) aunque los
5 sectores que ocupa impiden ubicarlo en discos de doble densidad. Pero, ¿quién va a querer hacer botable
un disco 2M de doble densidad, cuando uno estándar de alta tiene más capacidad?.
El formateo de los disquetes 2M puede realizarse desde un lenguaje de alto nivel por medio de las
funciones de la BIOS implementadas por 2M cuando está residente. El siguiente programa de ejemplo
demuestra lo sencilla que es esta tarea. El único problema importante que se presentó durante su desarrollo
fueron los conflictos que generaba WINDOWS al intentar formatear un disco en el formato de máxima
capacidad (opción /M): por algún motivo, era imposible crear este tipo de pistas al producirse un extraño
error en la función de formatear. Este problema ya se había presentado en versiones anteriores de 2M, que
también formateaban los discos. La solución adoptada es, sencillamente, invocar la INT 13h mediante un
CALL a la dirección del vector de interrupción. De este modo no se ejecuta el código WINDOWS
responsable de la incompatibilidad, que entraba en marcha al llamar a la INT 13h en modo protegido. Tenga
en cuenta el lector que una inocente instrucción INT es mucho más que eso bajo WINDOWS o con un
controlador de memoria instalado. Este fragmento de código de 2MF ha sido codificado en ensamblador,
entre otros motivos porque antes de llamar con CALL a una interrupción hay que apilar los flags y eso resulta
difícil en C. Durante las restantes fases del formateo (lectura para verificar y la escritura previa en los formatos
de máxima capacidad) se utilizan las funciones estándar de la BIOS vía INT 13h. Aunque WINDOWS no
estorbara, tampoco hubiera sido posible llamar con la función de formateo BIOS del compilador, ya que los
parámetros cambian ligeramente, si bien se podría haber hecho con código C.
El programa admite varios parámetros para controlar el formateo. Por defecto realiza el formateo normal,
más fiable (o indicando la opción /F). Para seleccionar el formateo de máxima capacidad hay que indicar /M.
Desde 2MF 3.0, el programa es capaz de detectar la densidad en discos de 3½ vírgenes (con la excepción
de las unidades que permiten formatear en alta densidad los discos de doble) y lo intenta en los de 5¼ (sólo
funciona si ya tenían algún tipo de formato previo). En cualquier caso, siempre se puede indicar la opción /HD,
/DD ó /ED para seleccionar la densidad necesaria y evitar la pequeña pérdida de tiempo en detectarla.
El número de pistas, por defecto 82, puede elegirse con /T, ya que muchas unidades soportan 84 pistas o
más; de todas maneras, 2MF 3.0 no permite formatear más pistas de las que admita la unidad, al contrario
que las versiones anteriores. Los ficheros permitidos en el directorio raíz se indican con /R. El parámetro /S
evita la producción de sonido. Con /N se evita la verificación, /K y /J eliminan la pausa inicial y final,
respectivamente; /Z anula el parpadeo del led mientras se cambia el disco y /L y /V permiten poner etiquetas
de volumen (serializadas en el último caso) al disco destino.
Finalmente, hay varios parámetros no documentados oficialmente que no deberían ser alterados, salvo
quizá en algún ordenador muy concreto y por parte de usuarios muy especializados, que permiten elegir los
factores de desplazamiento en la numeración de los sectores al conmutar de cabezal (/X) y de cilindro (/Y) en
el formato normal (/F); en el formato de máxima capacidad (/M) no tienen efecto. El parámetro /G permite
45 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
indicar el GAP o separación de sectores en todas las pistas -salvo la primera- en el formato /F; en el formato
/M este valor de GAP se refiere al GAP empleado en la primera pasada del formateo (con sectores de 128
bytes). Con /D0 se formatea en 3½-DD con 820/902K (en lugar de 984/1066K), algo necesario en las
controladoras de algunos portátiles que no soportan la densidad de 300 Kbps (propia exclusivamente de las
unidades de 5¼); si bien no es preciso emplearlo ya que por defecto el programa formatea de esta manera en
esas unidades al autodetectar la densidad del disco destino. /D1 formatea 1148K en lugar de 1066K, pero el
disco resultante es poco seguro y extremadamente lento. Por último, la opción /W hace que se marquen sólo
los clusters defectuosos y no la pista completa.
+-------------------------------------------------------------------------------+
| TIEMPO EMPLEADO EN EL FORMATEO |
+---------------+---------------+---------------+---------------+---------------+
| FORMAT | FDFORMAT (*) | FDFORMAT (**) | 2MF 3.0 /F | 2MF 3.0 /M |
+-------+---------------+---------------+---------------+---------------+---------------+
| 5¼-DD | 0:37 | 0:42 | 1:28 | 1:26 | 2:37 |
| 5¼-HD | 1:13 | 1:24 | 1:52 | 1:29 | 2:38 |
| 3½-DD | 1:24 | 1:38 | 1:46 | 1:39 | 2:51 |
| 3½-HD | 1:34 | 1:42 | 2:17 | 1:47 | 3:22 |
+-------+---------------+---------------+---------------+---------------+---------------+
(*) Usando el formato estándar del DOS (360-720-1.2-1.44) y los parámetros /X e /Y adecuado
(**) Formatos de máxima capacidad soportados (820-1.48-1.72) y los parámetros /X e /Y adecua
La parte más compleja del programa es la función CrearSector0(), que como su propio nombre indica se
encarga de crear el sector de arranque del futuro disquete. En un programa de copia de discos esta función no
sería necesaria, ya que al leer el disquete origen tendríamos ya el sector de arranque del futuro disquete
destino y, por tanto, podríamos formatearle directamente (recordar que la función de formateo de discos 2M
sólo necesita como parámetro el sector de arranque del futuro disco). Sin embargo, aquí nos vemos obligados
a crear dicho sector, lo cual es una tarea un tanto engorrosa, teniendo en cuenta la variedad de formatos. Una
tabla más o menos complicada, de 5 dimensiones, contiene toda la información necesaria para la tarea.
Además, el código ejecutable del sector de arranque resultaba difícil incluirlo dentro del listado C y finalmente
se optó por crear un fichero proyecto e incluir en él 2MF.C y 2MFKIT.ASM (este último integra los sectores
de arranque para alta y doble densidad -con y sin soporte SuperBOOT, respectivamente- así como el código
SuperBOOT y las rutinas de utilidad).
La función TipoDrive() devuelve el tipo de la disquetera que se le indique consultando esta información a
través de la BIOS. La función InicializaDisco() escribe, al final del formateo, el sector de arranque físico, el
virtual, el código SuperBOOT (si el disco es de alta densidad) y la FAT; de esta última sólo la primera copia,
ya que 2M emulará la segunda.
Las funciones de sonido crean efectos especiales bastante atractivos gracias al empleo de retardos de
medio milisegundo con la función PicoRetardo(); este retardo es idéntico en todas las máquinas, con total
independencia de la velocidad de la CPU, y permite que el sonido suene igual en todas. En los PC/XT no se
46 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
realiza retardo alguno y, curiosamente, el sonido suena igual que en los AT (en máquinas de 8 MHz).
Para fomentar que los usuarios envíen la postal al autor, el programa tiene un contador de discos
formateados añadido cuando formatea el primer disco por el método de alargar el tamaño del fichero EXE.
Al cabo de 100 discos, imprime un mensaje recordando al usuario su deber. Naturalmente, si 2MF se ejecuta
desde una unidad protegida contra escritura, no será posible actualizar el contador...
Finalmente, la función HablaSp() comprueba el país en que se ejecuta el programa para inicializar una
variable global que indique si los mensajes han de ser imprimidos en castellano o en inglés.
En las páginas donde se describía el funcionamiento de 2M aparecía una tabla con los tiempos
cronometrados de un COPY de múltiples ficheros, desde y hacia un disquete en los formatos de disco más
comunes. Sin embargo, resulta interesante conocer la velocidad real del sistema de disco cuando éste es
utilizado óptimamente: acceso a múltiples pistas completas y consecutivas en el disco. Los buenos programas
de copia de discos, que leen de un golpe todas las pistas consecutivas que pueden antes de guardarlas en un
fichero auxiliar (o que las almacenan en EMS ó XMS), dependerán de la velocidad que sea capaz de dar el
formato de disco empleado, ya que las disqueteras giran a una velocidad fija en todos los ordenadores. Si
pierden tiempo entre pista y pista (tal vez por escribirlas en el fichero auxiliar una por una) la velocidad
obtenida podría dividirse por dos, al intentar pillar el primer sector de la siguiente pista justo cuando acaba de
pasar de largo por delante del cabezal.
+--------------------------------+--------------------------------------------------
| Velocidad máxima teórica sin | Velocidad real en Kb/seg estimada por 2M
| considerar tiempos de acceso +---------------+---------------+---------------+--
| pista-pista ni el porcentaje | FORMAT | FDFORMAT (**) | FDFORMAT (***)|
| de superficie magnética que +-------+-------+-------+-------+-------+-------+--
| se aprovecha en cada pista. | Lect. | Escr. | Lect. | Escr. | Lect. | Escr. | L
+-------+--------------------------------+-------+-------+-------+-------+-------+-------+--
| 5¼-DD | 36,62 Kb/seg (300 Kbit/seg) | 18.16 18.16 | 22.11 22.12 | 25.00 25.00 | 2
| 5¼-HD | 61,03 Kb/seg (500 Kbit/seg) | 30.13 30.13 | 39.73 39.73 | 25.26 25.23 | 4
| 3½-DD | 30,52 Kb/seg (250 Kbit/seg)*| 15.05 15.05 | 19.32 19.32 | 21.78 21.75 | 2
| 3½-HD | 61,03 Kb/seg (500 Kbit/seg) | 30.14 30.14 | 39.58 39.53 | 24.79 24.79 | 4
+-------+--------------------------------+---------------+---------------+---------------+--
(*) 2M emplea 300 Kbit/seg (no es compatible con controladora
(**) Usando el formato estándar del DOS (360-720-1.2-1.44) y los
(***) Formatos de máxima capacidad soportados (820-1.48-1.72) y los
Con objeto de uniformizar los índices, el siguiente programa de ejemplo realiza la lectura y escritura
completa de un disco (en este último caso, si no contenía datos, ya que se estropearían) llamando a la BIOS.
47 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
La primera versión del programa empleaba el DOS (funciones absread() y abswrite() del C) y obtenía
exactamente los mismos índices, aunque problemas de fiabilidad aconsejaron utilizar funciones de la BIOS,
con lo que el programa ya no puede, por ejemplo, analizar el rendimiento de un disco duro (debido a la
incomodidad que supone buscar el sector de arranque a través de la tabla de particiones). Se recorren en
lectura y escritura todos los cilindros del disco, a partir del 1 y llegando hasta el último que exista. El motivo
de saltar el cilindro 0 es doble: por un lado, saltar las áreas del sistema (de cara a no escribir sobre el sector
de arranque, por ejemplo, ya que por simplicidad se escribe basura y no lo que se ha leído al principio); por
otro lado, los tiempos de este cilindro pueden ser diferentes de los obtenidos en los demás cilindros, bien
debido a la interferencia del sistema o los programas de caché o, simplemente, porque tiene un formato físico
muy especial (como es el caso de los disquetes 2M). En el caso de los disquetes 2M, de esta forma no se
tiene en cuenta el tiempo extra que se pierde en este primer cilindro debido a la extraña maniobra que supone
simular la existencia de la segunda copia de la FAT (que implica volver momentáneamente al primer cabezal
después de haber pasado al segundo).
El programa, 2M-FDTR (2M Floppy Data Transfer Rate), utiliza el contador de hora de la BIOS unido al
temporizador 8254 para cronometrar. Antes de comenzar el test y arrancar el cronómetro se lee uno de los
últimos sectores del cilindro 1 para asegurar que el cabezal está ya sobre el mismo y a punto de pillar el
primer sector. El buffer donde se realizará la lectura/escritura es asignado de tal manera que no cruce una
frontera de DMA (para que INT 13h no tenga que segmentar en varias fases la operación, lo que disminuiría
la velocidad). El acceso a INT 13h se realiza de manera directa, ya que la versión 3.1 del compilador hace
alguna oscura maniobra con biosdisk y al final termina perdiendo demasiado tiempo (lo suficiente como para
que en alguna máquina el disco aparente ser más lento de lo que realmente es). Con Borland C 2.0 no hay
problemas, pero...
NOTA: Los resultados de 2M-FDTR contradicen los que facilitan muchos afamados programas
comerciales de test, sencillamente porque dichos programas no miden correctamente (y de hecho dan
en cada ordenador, e incluso en la misma máquina entre ejecuciones consecutivas, resultados diferentes
y contradictorios). Si estuviera instalado un programa de caché, los resultados podrían verse alterados
por lo que se recomienda no instalarlos para la prueba. De todas maneras, con un disquete recién
introducido no hay programa alguno de caché que pueda disminuir el tiempo de lectura del mismo
(quizá sí la escritura). Insisto en que los resultados de 2M-FDTR son reales y cualquier programa de
aplicación que acceda a disco a medio o bajo nivel, como el propio 2M-FDTR, puede lograrlos si
utiliza correctamente las funciones de acceso a sectores del DOS o de la BIOS.
12.6.7.6 - LA VERSION PARA PC/XT DE 2M: 2MX [Listado no incluido en este libro].
Aunque 2M fue inicialmente concebido para máquinas AT, a partir de la versión 1.2 ha estado
acompañado de una versión para PC/XT. El único requisito es que el ordenador esté equipado con una
controladora y unidades de alta densidad. Algunas máquinas modernas de tipo subnotebook, que caben en la
palma de la mano, vienen preparadas para conectar una de estas disqueteras externas. Otros PC/XT de
reciente fabricación traen ya controladoras de alta densidad y BIOS que las soportan, aunque luego el tacaño
fabricante haya colocado una unidad de doble densidad que el usuario puede sustituir. Finalmente, a aquellas
máquinas más antiguas que no pertenecen a ninguna de estas dos categorías, se les puede sustituir la
controladora y unidades de doble densidad por otras de alta, que en el futuro el usuario podrá colocar en su
máquina AT cuando se la compre; se trata por tanto de una inversión rentable. Si bien resulta difícil encontrar
actualmente en el mercado controladoras de alta densidad para PC/XT, el usuario puede optar por poner una
de AT. Yo, por ejemplo, para probar 2MX me vi obligado a pinchar una controladora de 16 bits en un slot
de 8 bits. La tarjeta era una IDE multi-io; sin embargo, la parte alta del bus (que no se puede pinchar al ser de
8 bits el slot) sólo se utiliza para acceder al disco duro bus AT, pudiendo ser inhibida con el jumper de marras
48 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
(si bien ni esto resultó necesario). La parte correspondiente al control de disquetes, y probablemente los
puertos serie/paralelo, era totalmente funcional, ya que sólo opera con la mitad baja del bus.
El principal problema radica en que la BIOS de los PC/XT en el 99% de los casos no está preparada para
soportar alta densidad. Al hacer DIR sobre un disquete de alta densidad nada más encender el ordenador, lo
más probable es que funcione, ya que ésta es la densidad por defecto normalmente. Sin embargo, con los
discos de doble densidad (donde tiene que seleccionar 250 ó 300 Kbit/seg) es imposible sacar el DIR. En
cualquier caso, sacar un DIR es una cosa y otra muy diferente conseguir que el disco funcione. Como la
BIOS informa siempre que todo es de doble densidad, el muy patoso del DOS modifica la tabla base del
disco para indicar como 9 el último número de sector en la pista (¿quién le mandará tocar las variables de la
BIOS?) por lo que ni los discos de alta densidad funcionan a nivel de COPY (el directorio sí aparece porque
coincide en los primeros sectores de las pistas). La solución en este tipo de máquinas pasa por instalar una
BIOS más moderna... pero sin tener que regrabar la eprom. Basta con cargar 2M-XBIOS.EXE, un programa
residente que emula la BIOS AMI de AT en los XT. De hecho, 2MX solicita al usuario la instalación de este
driver cuando advierte que no puede detectar el tipo de las unidades.
En ese sentido, la combinación 2M-XBIOS + 2MX permite a cualquier máquina PC/XT obsoleta
equipada con una barata controladora de disquetes de AT trabajar con discos de cualquier densidad y
cualquier formato (estándar/2M). Los problemas de versiones anteriores de 2MX han sido eliminados gracias
a la extensión BIOS en que se apoya. De hecho, 2MX es en sus últimas versiones prácticamente idéntico a
2M, sólo cambia en algunos aspectos puntuales relacionados con la diferente arquitectura de los XT respecto
a los AT.
12.6.7.7 - LA OPCION BIOS DE 2M: 2M-ABIOS Y 2M-XBIOS [Listados no incluídos en este libro].
Algunos ordenadores poseen una BIOS antigua o con un diseño propio poco compatible en el control de
disco. En estas máquinas, 2M y otros programas de acceso a bajo nivel pueden fallar. En dichos casos, se
puede instalar esta utilidad antes que 2M, y en general que cualquier otro software que acceda al subsistema
de disco. La versión 2M-ABIOS es para AT y 2M-XBIOS para PC/XT.
Estos programas actualizan el soporte de disco flexible al nivel de las BIOS AMI de 1993. Si con ellos
instalados 2M no opera de manera totalmente correcta (aunque en general 2M depende realmente muy poco
de la BIOS, pero ya conozco algún caso al respecto) y en la máquina no está instalado algún otro software de
disco incompatible con 2M, entonces el ordenador no es 100% compatible hardware con el estándar; esto es
particularmente cierto si ni siquiera se reconocen los discos estándar del DOS.
Esta utilidad también sirve para añadir soporte de 1.44M a máquinas con BIOS antigua, algunas de ellas
incluso AT. En estos casos, el usuario debe ignorar la información sobre el tipo de la unidad que pueda
reportar dicha BIOS al arrancar. El programa se carga desde el CONFIG.SYS con una sintaxis sencilla:
El consumo de memoria es de unos 3.4-4.2 Kb de RAM, y contiene una emulación al 100% del eficaz
código de control de disco de las BIOS AMI, relevando así por completo de esta tarea a la BIOS del
sistema. De ahí que haya sido diseñado en este formato, para forzar al usuario a instalarlo antes de los demás
programas de disco, a los que anularía por completo (ya que nunca más vuelve a llamar a la interrupción de
disco anterior). En AT generalmente no hará falta indicar el tipo de las unidades (0:no hay, 1:360K, 2:1.2M,
3:720K, 4:1.44M, 5:2.88M) pero en PC/XT casi siempre será necesario. La opción /C evita en los equipos
AT ajustar la CMOS, por si la máquina en cuestión tiene un algoritmo no estándar para calcular el checksum
de la misma y aparece un "Incorrect CMOS checksum" al arrancar (muy poco probable). Así mismo, si en
algún momento el usuario dudara acerca de si 2M-ABIOS está controlando realmente las unidades, puede
49 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
utilizar la opción /13 para asegurarlo, si bien esta opción es poco recomendable cuando no es estrictamente
necesaria (se desvía también INT 13h además de INT 40h, incluso aunque detecte el soporte de esta última).
El listado comentado de estos programas (realmente uno solo, con ensamblaje condicional en 2M 3.0) se
omite porque ya hay demasiadas rutinas de acceso a disco a bajo nivel en este libro.
Debido a la ineficiencia de FORMAT a la hora de crear discos rápidos y teniendo en cuenta la limitación
de DISKCOPY en el sentido de no poder formatear discos destino en formato 2M, se comprende la
necesidad de un sustituto de FORMAT y DISKCOPY. Sin embargo, todos los programas al respecto
existentes en la actualidad, a mi juicio, son un perfecto desastre. La mayoría no son rápidos incluso con discos
optimizados, por una cuestión elemental: no colocar los buffers de transferencia de manera que no crucen las
fronteras de DMA (para evitar que el DOS tenga que hacer accesos redundantes para salvarlas). La mayoría,
de hecho, no generan discos optimizados con la clásica técnica de Sector Sliding (que en absoluto implica
reducción de compatibilidad o fiabilidad; más bien al contrario: es como se debe formatear correctamente un
disco y como de hecho se hace con los discos duros). Otros son poco flexibles y no soportan discos 2M
(¡hasta DISKCOPY los supera en esto!) o tienen absurdas rutinas que encuentran virus en sectores de
arranque poco oficiales, o necesitan VGA y ratón (aparte de ser lentos), o no son fiables...
La solución adoptada ha sido crear un programa residente que haga trabajar a todos los demás (con la
excepción de los que también acceden directamente a la controladora de disco) de la manera adecuada. Se
trata de crear una utilidad para que FORMAT o cualquier otro programa que llame a la BIOS formatee
discos optimizados (aún sin saberlo) y que amplíe los formatos de disco oficiales de la BIOS para que
DISKCOPY (y el DUPDISK de las Norton y programas de similar flexibilidad) sean capaces, durante el
proceso de copia, de formatear el disco destino 2M si es preciso.
Con 2MDOS instalado los discos se formatean automáticamente de manera óptima y DISKCOPY
soporta el formateo de discos 2M. Incluso FORMAT puede crear discos 2M (indicando pistas y sectores) si
bien el de MS-DOS (no DR-DOS) tiene problemas con los de alta densidad y necesita un parámetro
opcional (de todas maneras, 2MF sigue siendo más eficiente). Además 2MDOS da soporte por defecto a
disquetes no estándar, creados por la utilidad FDFORMAT y permite a FORMAT poder crear disquetes
FDREAD. El programa consume 5,7 Kb en equipos sin memoria extendida o 2,5 Kb con ella (sólo 1,7 Kb si
no está activo el soporte para hacer DISKCOPY hacia un disco 2M sin formato; esto es, con sólo las
opciones de optimización de formateo y soporte FDREAD activas).
Por si esto fuera poco, 2MDOS incorpora una nueva técnica para acelerar aún más los discos estándar de
1.2M y 1.44M, que recibe el nombre de DiskBoost por razones de marketing. El truco consiste en evitar la
necesidad de Sector Sliding, para de esta manera alcanzar, por ejemplo, una tasa de transferencia de datos de
45 Kb/seg en 1.44M (frente a los 39 Kb/seg del Sector Sliding o los 30 Kb/seg del FORMAT habitual). El
truco consiste en añadir un sector adicional en el cabezal 1 y dos en el cabezal 0, que no se usan, algo que no
reduce sensiblemente el nivel de seguridad del disco (sería el equivalente en seguridad a un disco de 1.64M,
por ejemplo). Los sectores adicionales, no usados, son colocados al principio de la pista. De esta manera,
cuando la controladora acaba de acceder a una pista completa en el cabezal 0 (y está al inicio justo de la pista
tras completar una vuelta) se conmuta al cabezal 1 para acceder a la pista siguiente. Recordemos que en el
cabezal 1 había un sector no utilizado al principio: este sector pasará por delante del cabezal mientras se
conmuta, pero no transcurrirá demasiado tiempo como para que no se pueda pillar el primer sector de la pista
que viene inmediatamente a continuación. Cuando se acabe de leer la pista en el cabezal 1 (y se está de nuevo
al inicio justo de la pista tras completar la vuelta) se conmuta al cabezal 0 pero del siguiente cilindro, algo que
lleva más tiempo que antes... pero para eso ya habíamos dejado dos sectores no utilizados al inicio del
cabezal 0. Por tanto, también da tiempo a pillar el primer sector.
50 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
Con la técnica DiskBoost es factible leer o escribir un disco completo de 1.44M en poco más de 31
segundos, al emplear sólo una vuelta por cada pista. La diferencia de velocidad, contra todo pronóstico, es
aún más espectacular en las operaciones COPY o XCOPY normales. Los discos de 1.2M y 1.44M creados
por FORMAT con 2MDOS instalado son un 50% más rápidos en el uso normal.
Sin embargo, 2MDOS no es la solución definitiva. Aunque es útil para que cada cual utilice sus programas
de copia/formateo favoritos de manera óptima, lo ideal sería un programa de copia/formateo realmente
eficiente. Con dicho programa, 2MDOS no sería necesario...
El listado de 2MDOS tampoco se incluye en estas páginas. 2MDOS también incorpora el código
SuperBOOT a los discos 2M de alta densidad que se formatean bajo su control, aunque su tarea es ampliar la
funcionalidad de algunas interrupciones de la BIOS y no realiza accesos directos al hardware.
12.6.7.9 - COMO SUPERAR LOS 2.000.000 DE BYTES EN 3½: 2MGUI [Listado no incluído en el
libro].
En cierta ocasión un programa llamado 1968 llegó a mis manos. Se trataba de una utilidad para formatear
discos de 1.44M a esa capacidad. Sin embargo, no funcionaba en mi unidad, ni tampoco en la de mis
máquinas de uso habitual. En alguna de ellas lograba formatear (a base de reintentos ante los errores) todo el
disco, pero por desgracia la primera pista quedaba mal. Nunca logré crear un disco de estos, aunque se que
si lo hubiera conseguido, ese disco -como bien decía el autor en la documentación- sí podría ser leído en las
demás unidades.
El método de este programa consistía en introducir 3 sectores de 4 Kb en cada pista. El problema es que
eso requiere (4096+62)*3 = 12474 bytes, sin contar los GAP entre sectores, y la mayoría de las unidades
giran algo más deprisa de lo normal (y por tanto, se alejan del límite teórico de 12500 bytes por pista). Por
otro lado, 26 bytes son incluso pocos para respetar las marcas de inicio de pista y demás. Al final, el tercer
sector suele acabar pisando al primero.
Después de algún tiempo, han aparecido más formateadores que soportan (o dicen soportar) este formato,
alguno incluso en nuestro país. Sin embargo, todos tienen el mismo problema: no hay unidades que soporten a
esos programas. Por tanto, todo parecía indicar que el límite de capacidad se quedaría para siempre en los
1.72M del FDFORMAT ó los 1.88M de 2M, únicos formatos soportados por todas las unidades y
ordenadores (eso sí, compatibles). Pues no. Cierto día, Jesús Arias tuvo una idea genial y me la contó. A raíz
de esa idea, y tras superar numerosas y difíciles trabas técnicas, finalmente ha sido posible el milagro: lograr
utilizar toda la capacidad disponible en la pista del disco, como si estuviera sin formatear.
El programa que realiza esto, 2MGUI (abreviatura de 2M-Guinness), es ya una realidad. Durante su
desarrollo se han puesto de relieve circunstancias curiosas. Por ejemplo, una determinada unidad admite
12440 bytes por pista al grabar información aleatoria, pero si se escribe toda la pista con bits a 0 ó a 1 sólo
caben 12405 bytes. ¿Por qué?: la respuesta sigue siendo un misterio. Las rutinas residentes de 2MGUI
aprovechan las terminaciones normales de error de la controladora (disco protegido contra escritura, sector
no encontrado, etc.) para la detección de errores, aunque graban adicionalmente, en cada pista de datos, un
checksum de la información almacenada junto al número de pista y cabezal reales, para realizar el control de
errores cuando la controladora no puede devolver condiciones de error (debido a una serie de factores
técnicos). De esta manera, la información se graba y recupera con la seguridad de que es correcta -en caso
contrario, se detectaría el fallo-.
Realizando pruebas, la capacidad admitida por diversas unidades se mostró directamente relacionada con
la velocidad de rotación de la misma. Por ejemplo, una unidad de 3½-HD que gire cada 199,9 ms admite
12405 bytes, mientras que otra que lo hace cada 199,1 ms sólo admite 12348 bytes. Ambas son casos
realmente extremos, ya que la inmensa mayoría se encuentra entre estas dos. Aún así, la capacidad finalmente
51 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
adoptada por 2MGUI serán 12329 bytes. El objetivo es permitir que los discos puedan ser intercambiados
entre unidades. En lectura nunca hay problemas, ya que la peor unidad puede leer los datos de la mejor (la
que más lentamente gire) porque la señal de reloj la obtiene de los propios datos registrados en disco. Sin
embargo, al escribir, la señal de reloj la extrae de su base de tiempos propia (casi igual en todos los
ordenadores) y al girar más deprisa se le acaba la pista antes y sobreescribe el principio. Por tanto, los discos
que apuren demasiado la capacidad de una buena unidad serán estropeados al ser escritos (no leídos) en otra
unidad peor.
+-----------+-----------+------------+
| Doble | Alta | Extraalta |
+-------------------------------+-----------+-----------+------------+------+
| Récord absoluto previo a 2M | 820.0 Kb | 1394.0 Kb | -- | |
| Capacidad máxima 2M (2MF /M) | 902.0 Kb | 1558.0 Kb | -- | 5.25 |
| Capacidad mínima de 2MGUI | 979.0 Kb | 1642.4 Kb | -- | (5¼) |
| Capacidad límite teórica (82p)| 1001.0 Kb | 1668.2 Kb | -- | |
+-------------------------------+-----------+-----------+------------+------+
| Récord absoluto previo a 2M | 984.0 Kb | 1722.0 Kb | 2880.0 Kb | |
| Capacidad máxima 2M (2MF /M) | 1066.0 Kb | 1886.0 Kb | 3772.0 Kb* | 3.5 |
| Capacidad mínima de 2MGUI | 1178.3 Kb | 1974.5 Kb | 3949.0 Kb* | (3½) |
| Capacidad límite teórica (82p)| 1201.2 Kb | 2002.0 Kb | 4003.9 Kb | |
+-------------------------------+-----------+-----------+------------+------+
(*) No probado. En esta lista están recogidos sólo los formatos soportados
por prácticamente todas las unidades y en casi todos los ordenadores.
Hay también otro pequeño problema técnico: si la capacidad de la pista es múltiplo del tamaño de sector
lógico empleado (aunque ese sector sea de 128 bytes en lugar de 512) se derrocha espacio al redondear
hacia abajo. La tentación era fuerte: permitir que un sector lógico pueda estar entre dos pistas. De esta
manera, la capacidad total de un disco no puede ser múltiplo entero del número de pistas y cabezales.
Solución: crear un controlador de dispositivo que trate al disco como un array de sectores (un dispositivo con
un sector por pista, un cabezal, y muchísimas pistas, igual que un disco virtual). Así, por ejemplo, los discos
de 3½-HD con 12329 bytes por pista tienen en total (con las 82 pistas habituales) 2.021.956 bytes (que
equivalen a 15.796 sectores de 128 bytes, totalizando 2.021.888 bytes con un desperdicio de sólo 68).
Utilizando una sola FAT, un número razonable de entradas al directorio y clusters de 2048 bytes (que en las
pruebas han demostrado generar discos notablemente más rápidos que los de 512 bytes) el espacio
disponible para el usuario (visible con DIR) alcanza los 2.015.232 bytes netos (1968K). Se trata de nuevo de
1968K... pero esta vez no son brutos, sino netos, y además en todas las unidades (y no en casi ninguna).
En escritura, estos discos son 2 ó 3 veces más lentos que en lectura, aproximadamente. En lectura son sin
embargo algo más rápidos que los discos estándar optimizados. La lentitud escribiendo es obvia: imaginemos
que hay que escribir un sector ubicado entre dos pistas: primero habra que leer una pista, modificar algunos
bytes finales y volverla a escribir, luego leer la siguiente para cambiar unos bytes al principio y escribirla de
nuevo...¡todo eso para cambiar un sector lógico de 128 bytes!. Sin embargo, tampoco es para tanto, ya que
por lo general el DOS envía bloques grandes a los dispositivos y esto supone la escritura directa e inmediata
de las pistas completas... que además utilizan la técnica de Sector Sliding (la posición inicial del sector-pista
está desplazada según la ubicación en el disco). De hecho, cacheando las áreas del sistema, la velocidad de
escritura seria probablemente muy superior, al agilizar el cuello de botella que supone el acceso a la FAT. Sin
embargo, el consumo de memoria del programa (unos 17 Kb) ya es respetable sin caché, y no se llega
tampoco al extremo del viejo 1968 de reservar 240 Kb de XMS.
El programa (un único fichero EXE que se carga en el CONFIG.SYS y luego se puede ejecutar desde la
línea de comandos para formatear) es totalmente flexible tanto a nivel lógico (posibilidad de reprogramar el
tamaño de cluster, el número de entradas al directorio y el número de FATs) como físico (posibilidad de elegir
número de pistas, densidades, Sector Sliding X e Y (expresado además en grados angulares) e incluso un
parámetro nada menos que para indicar los bytes por pista (por si el usuario tiene una unidad que admite
52 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
más). Dispone también de una opción para medir con precisión la velocidad de rotación de la unidad y para
calcular qué capacidad máxima soporta. La flexibilidad de un disco virtual... pero en un disquete; el número
de formatos es prácticamente infinito, según la voluntad del usuario. Una de las opciones es formatear las 28
pistas más externas en alta densidad y las 54 restantes en doble, en un disco de 360K, obteniéndose 1.2M
bastante más fiables de lo que se podría esperar.
+---------------------------------------------------+
| C:\AUXI>2mgui |
| |
| 2MGUI instalado en memoria. |
| - Nueva unidad E: 1.2M (unidad física A:) |
| - Nueva unidad F: 1.44M (unidad física B:) |
| Ejecute 2MGUI /? si desea obtener ayuda. |
| |
| |
| C:\AUXI>dir e: |
| |
| Volume in drive E is unlabeled |
| File not found "E:\*.*" |
| 0 bytes in 0 file(s) |
| 997.376 bytes free |
| |
| C:\AUXI>chkdsk e: |
| |
| 997.376 bytes total disk space |
| 997.376 bytes available on disk |
| |
| 2.048 bytes in each allocation unit |
| 487 total allocation units on disk |
| 487 available allocation units on disk |
| |
| 655.360 total bytes memory |
| 649.776 bytes free |
| |
| |
| C:\AUXI>dir f: |
| |
| Volume in drive F is unlabeled |
| File not found "F:\*.*" |
| 0 bytes in 0 file(s) |
| 2.015.232 bytes free |
| |
| C:\AUXI>chkdsk f: |
| |
| 2.015.232 bytes total disk space |
| 2.015.232 bytes available on disk |
| |
| 2.048 bytes in each allocation unit |
| 984 total allocation units on disk |
| 984 available allocation units on disk |
| |
| 655.360 total bytes memory |
| 649.776 bytes free |
| |
| C:\AUXI>_ |
+---------------------------------------------------+
EJEMPLOS DE ACCESO A UN DISCO DE 360K
Y OTRO DE 1.44M FORMATEADOS CON 2MGUI
53 de 54 12/10/00 19:19
EL CONTROLADOR DE DISQUETES NEC 765 file:///C|/librosVirtuales/UniversoDigital/1206.html
Con QEMM, si se instala el driver en memoria superior hay que indicar DMA=13 (unidades 1.44M) ó
DMA=25 (unidades 2.88M) en las opciones del controlador de memoria, ya que el buffer para acceso
directo a memoria que establece por defecto es de sólo 12 Kbytes (EMM386 establece 32 Kb).
Las nuevas letras de unidad 2MGUI también soportan discos estándar e incluso 2M (teniendo instalado
también 2M). De hecho, estas nuevas unidades posibilitan el empleo de discos 2M en OS/2.
Veamos qué consideraciones hay que tener en cuenta para utilizar disquetes 2M en OS/2. Para empezar,
es necesario arrancar el DOS desde un disquete o desde un fichero imagen de disco, ya que en las ventanas
DOS ordinarias 2M no puede controlar los accesos a disco. Curiosamente, sí se puede formatear en estas
ventanas, pero no trabajar con el disco: lo que sucede es que el sistema de ficheros de la emulación DOS que
incorpora OS/2 está gestionado al parecer sin llamadas a la BIOS, precisamente las que intercepta 2M, que
por tanto no se da cuenta de los accesos a disco. Una vez arrancado desde un fichero imagen con, por
ejemplo, MS-DOS 6 (creado con el VMDISK del OS/2) 2M funcionaría perfectamente. Pero lo más
probable es que el usuario tenga instalada la utilidad FSFILTER.SYS para poder acceder a las particiones
HPFS y, sobre todo, para poder escribir sobre las particiones FAT ordinarias, que serían de sólo lectura en
caso contrario. Y aquí vuelven los problemas: al instalar este driver que altera la INT 21h, 2M deja de nuevo
de funcionar.
La solución más rápida consiste en crear un driver que implemente 2 nuevas unidades lógicas (como la D:
y la E: por ejemplo) que utilicen la BIOS para acceder a disco: en estas nuevas unidades ya no habrá
problemas para trabajar con los disquetes 2M. Este driver sería un programa enteramente DOS, que sin
embargo no se puede instalar en las ventanas DOS normales de OS/2, ya que en ellas están prohibidos los
dispositivos de bloque. Por tanto, su utilización queda restringida a las ventanas de DOS que incorporen una
auténtica versión de este sistema (obtenidas con VMDISK sobre un disquete de arranque, a menos que el
usuario desee arrancarlas desde disquete cada vez que vaya a emplearlas).
Pese a la solución de dicho driver (en nuestro caso 2MGUI), existe algún problema relativamente
importante que comentar. El más interesante consiste en que OS/2 comprueba periódicamente si ha habido un
cambio de disco en alguna unidad, accediendo a la misma en ese caso para comprobar su contenido -con
independencia de que el usuario esté haciendo otra cosa en ese momento, como jugar a los marcianitos
mientras espera los resultados de un programa de cálculo-. Si no hay disco introducido no sucede nada, pero
si lo hay y es de tipo 2M, OS/2 se queda intentando leerlo de manera obsesiva hasta el punto de colapsar la
ventana DOS, que queda literalmente colgada (aunque no el resto de las ventanas ni el sistema en conjunto).
La solución, si se estaba trabajando en esta ventana, es retirar el disquete de la unidad y esperar un segundo o
dos. Ah, y no volver a introducirlo hasta que no se vaya a utilizar, para evitar nuevas molestias. Por fortuna,
OS/2 suele tener cuidado de no fisgar por las disqueteras cuando están siendo usadas. La solución ideal sería
un driver que integrara en OS/2 el soporte de estos disquetes, pero eso requiere saber construir controladores
para OS/2.
Las primeras versiones de 2M venían acompañadas de un driver DOS que realizaba la tarea descrita; sin
embargo, desde 2M 1.3+ fue sustituido incorrectamente por una recomendación al usuario acerca de la
instalación de DRIVER.SYS, programa que no llama a la BIOS (sino al propio DOS; por tanto, con efectos
nulos). Por consiguiente, con 2M 3.0+ aparece de nuevo soporte oficial para este sistema.
54 de 54 12/10/00 19:19
EL DISCO DURO DEL AT (IDE, MFM, BUS LOCAL) file:///C|/librosVirtuales/UniversoDigital/1207.html
La información aquí vertida se aplica al tradicional controlador de disco duro ST506, que ha equipado a
los discos duros MFM/RLL de los AT, con el que es compatible en líneas generales tanto el interface de los
ESDI como el de los IDE (ISA, PCI o Bus Local). Sin embargo, los discos SCSI no son compatibles con la
información que aquí se expone, ni tampoco la controladora de los PC/XT.
12.7.1 - EL INTERFACE.
El disco duro se conecta a la controladora a través de dos cables: uno con las señales de control y otro
con las de datos. El de señales de control consta de 34 conectores, y el de datos de 20.
+------------------+-----------+----------+
| Nombre señal | Pin señal | Pin masa |
+------------------+-----------+----------+
| - HEAD SELECT 3 | 2 | 1 |
| - HEAD SELECT 2 | 4 | 3 |
| - WRITE GATE | 6 | 5 |
| - SEEK COMPLETE | 8 | 7 | +----------------------+--------------------------------+
| - TRACK 000 | 10 | 9 | | Nombre señal | Pin señal |
| - WRITE FAULT | 12 | 11 | +----------------------+--------------------------------+
| - HEAD SELECT 0 | 14 | 13 | | -Unidad seleccionada | 1 |
| RESERVADO | 16 | 15 | | +MFM Escribir datos | 13 |
| - HEAD SELECT 1 | 18 | 17 | | -MFM Escribir datos | 14 |
| - INDEX | 20 | 19 | | +MFM Leer datos | 17 |
| - READY | 22 | 21 | | -MFM Leer datos | 18 |
| - STEP | 24 | 23 | | Masa | 2, 4, 6, 8, 11, 12, 15, 16, 19 |
| - DRIVE SELECT 1 | 26 | 25 | +----------------------+--------------------------------+
| - DRIVE SELECT 2 | 28 | 27 | SEÑALES PARA TRANSFERENCIA DE DATOS
| - DRIVE SELECT 3 | 30 | 29 |
| - DRIVE SELECT 4 | 32 | 31 |
| - DIRECTION IN | 34 | 33 |
+------------------+-----------+----------+
SEÑALES DE CONTROL
El disco duro trabaja con sectores de 512 bytes; soporta corrección de errores ECC, operaciones
multisector rebasando fronteras de pista y cilindro y funciones de autodiagnóstico. El límite de capacidad está
1 de 8 12/10/00 19:20
EL DISCO DURO DEL AT (IDE, MFM, BUS LOCAL) file:///C|/librosVirtuales/UniversoDigital/1207.html
en 1024 cilindros y 16 cabezales. Los registros de operación son los mostrados en la figura, estando la
controladora ubicada normalmente en la localización E/S primaria.
+---------------------+---------------------------------------+
| Dirección E/S hex. | Significado |
+----------+----------+-------------------+-------------------+
| Primaria | Secund. | Lectura | Escritura |
+----------+----------+-------------------+-------------------+
| 1F0 | 170 | Data registers | Data register |
| 1F1 | 171 | Error register | Write precomp |
| 1F2 | 172 | Sector count | Sector count |
| 1F3 | 173 | Sector number | Sector number |
| 1F4 | 174 | Cylinder low | Cylinder low |
| 1F5 | 175 | Cylinder high | Cylinder high |
| 1F6 | 176 | Drive/Head | Drive/Head |
| 1F7 | 177 | Status register | Command register |
+----------+----------+-------------------+-------------------+
Data Register:
Permite acceder al buffer donde está almacenado el sector para leer y escribir en el modo PIO
(esto es, sin DMA). No debería ser accedido a menos que haya una operación de lectura o escritura
en curso. Implementa una dirección de 16 bits dentro del buffer de la controladora que contiene al
sector para las operaciones de lectura y escritura normales. Para una lectura/escritura largas 4 bytes
ECC son transferidos por byte con al menos 2 microsegundos entre transferencias (la línea DRQ debe
estar activa antes de transferir los bytes ECC).
Error Register:
De sólo lectura, contiene información sobre el comando previo. El dato es válido sólo cuando el bit
de error en el registro de estado está activo.
Tras conectar el disco duro a la corriente o tras enviar el comando apropiado, se encuentra en
modo diagnóstico: en esos casos, el registro debe ser comprobado diga lo que diga el bit del
registro de estado (con el significado en estos casos de 01-No hay error, 02-Fallo del
controlador, 03-Error en el buffer del sector, 04-Error en el dispositivo ECC, 05-Error en el
procesador de control).
Cuando no está en modo diagnóstico, caso más común, significado de sus bits:
bit 0: Data Address Mark (DAM) no encontrada en los 16 bytes del campo ID.
bit 1: Error TR 000. Se activa si tras un comando Restore, la señal Track 000 no se
activa después de 1023 pulsos de retroceso.
bit 2: Comando abortado. En estos casos, se debe mirar los registros de Status y Error
para determinar con precisión la causa (que estar en Write Fault, Seek Complete, Drive
ready -- o comando inválido en otro caso).
bit 3: No usado.
bit 4: ID no encontrada. La marca ID que identifica al cilindro, cabezal y sector no ha
sido encontrada. Si están activos los reintentos, el controlador lo reintenta 16 veces antes
de dar error, en caso contrario sólo explora la pista como mucho 2 veces antes de dar el
error.
bit 5: No usado.
bit 6: Error ECC. Indica si se ha producido un error ECC incorregible durante una
lectura.
bit 7: Bad Block detected. Indica que se ha encontrado un sector marcado como
2 de 8 12/10/00 19:20
EL DISCO DURO DEL AT (IDE, MFM, BUS LOCAL) file:///C|/librosVirtuales/UniversoDigital/1207.html
Sector Count:
Indica el número de sectores a transferir durante la lectura, escritura, verificación o formateo. En las
operaciones multisector, este registro se decrementa y el Sector Number se incrementa; al formatear,
antes de enviar cada comando de formateo debe cargarse aquí el número de sectores en la pista. Se
soportan operaciones multisector que crucen fronteras de pista y cilindro. Las características de la
unidad deben establecerse con el comando Set Parameters antes de una transferencia multisector.
Este registro debe cargarse con el número de sectores antes de cualquier comando relacionado con
datos. Un valor 0 representa 256 sectores.
Sector Number:
Número de sector para la lectura, escritura y verificación. El sector inicial se carga aquí en las
operaciones multisector.
Cylinder Number:
Número de cilindro para los comandos de lectura, escritura, verificación y posicionamiento de
cabezales. Entre el registro que almacena la parte baja y el de la parte alta (low y high respectivamente)
se guarda un número entre 0 y 1023.
Drive/Head:
Bits 7 y 5 puestos a 1, el 6 puesto a 0. El bit 4 indica la unidad seleccionada (0 el primer disco
duro y 1 el segundo) y los bits 0-3 el número de cabezal de lectura/escritura deseado. Para acceder a
las cabezas 8-15, es necesario además activar el bit 3 del puerto 3F6h. Importante: este registro debe
cargarse con el número máximo de cabezales antes de enviar un comando Set Parameters.
Status register:
Se actualiza tras ejecutar los comandos. El programa debe mirar este registro para conocer el
resultado. Si el bit busy (7) está activo, los demás bits no son válidos. Una lectura de este registro
borra la petición de interrupción IRQ 14. Si write-fault (bit 5) o error (bit 0) están activos, o si
seek-complete (bit 4) o drive-ready (bit 6) están inactivos, la operación multisector es abortada.
Significado de los bits:
bit 7: Busy. Un 1 indica que el controlador está ejecutando un comando; por tanto, este bit debe
ser examinado antes de leer cualquier registro.
bit 6: Drive-ready. Un 0 indica que la lectura, escritura y seek están inhibidas; para poder
ejecutarlas debe estar a 1 junto con el bit seek-complete (4).
bit 5: Write-fault. Un 1 indica funcionamiento incorrecto de la unidad; la lectura, escritura o seek
están inhibidos.
bit 4: Seek-complete. Un 1 indica que los cabezales han terminado el seek.
bit 3: Data-request. Este bit indica que el buffer del sector necesita ser atendido en un comando
de lectura o escritura: si este bit o el busy (7) están activos, hay un comando en ejecución. Hasta
recibir algún comando, este bit está a 0.
bit 2: Corrected-data. Un 1 indica que los datos leídos del disco fueron corregidos de error
ECC con éxito. Errores suaves no abortan la operación multisector.
bit 1: Index. Este bit se pone a 1 tras cada revolución del disco.
bit 0: Error. Un 1 indica que el comando previo terminó en error, y uno o más bits del Error
register están activos. El próximo comando enviado al controlador borra este bit. Si este bit se
activa, la operación multisector es abortada.
Command Register:
3 de 8 12/10/00 19:20
EL DISCO DURO DEL AT (IDE, MFM, BUS LOCAL) file:///C|/librosVirtuales/UniversoDigital/1207.html
Acepta 8 diferentes comandos. Los comandos se programan cargando primero los demás registros
necesarios y escribiendo después el comando en éste mientras el registro de estado devuelve una
condición de no busy. Un comando no legal provoca un error de comando abortado. La solicitud de
interrupción IRQ 14 se borra al escribir un comando. Los comandos soportados son:
+----------------+-----------------------------+
| Comando | bit 7 6 5 4 3 2 1 0 |
+----------------+-----------------------------+
| Restore | 0 0 0 1 R3 R2 R1 R0 |
| Seek | 0 1 1 1 R3 R2 R1 R0 |
| Read sector | 0 0 1 0 0 0 L T |
| Write sector | 0 0 1 1 0 0 L T |
| Format track | 0 1 0 1 0 0 0 0 |
| Read verify | 0 1 0 0 0 0 0 T |
| Diagnose | 1 0 0 1 0 0 0 0 |
| Set Parameters | 1 0 0 1 0 0 0 1 |
+----------------+-----------------------------+
+----+----+----+----+---------------+
| R3 | R2 | R1 | R0 | Stepping rate |
+----+----+----+----+---------------+
| 0 0 0 0 | 35 ms |
| 0 0 0 1 | 0.5 ms |
| 0 0 1 0 | 1.0 ms |
| 0 0 1 1 | 1.5 ms |
| 0 1 0 0 | 2.0 ms |
| 0 1 0 1 | 2.5 ms |
| 0 1 1 0 | 3.0 ms |
| 0 1 1 1 | 3.5 ms |
| 1 0 0 0 | 4.0 ms |
| 1 0 0 1 | 4.5 ms |
| 1 0 1 0 | 5.0 ms |
| 1 0 1 1 | 5.5 ms |
| 1 1 0 0 | 6.0 ms |
| 1 1 0 1 | 6.5 ms |
| 1 1 1 0 | 7.0 ms |
| 1 1 1 1 | 7.5 ms |
+-------------------+---------------+
+-----+ +------------------------+----------------------+
| Bit | | 0 | 1 |
+-----+--------------------+------------------------+----------------------+
| L | Modo de datos | Sólo datos | Datos y 4 bytes ECC |
| T | Modo de reintentos | Reintentos habilitados | Reintentos inhibidos |
+-----+--------------------+------------------------+----------------------+
Nota: Después de un reset o un comando Diagnose, el step rate queda en 7.5 ms.
Por otro lado, el sistema verifica la operación ECC leyendo y escribiendo estos bytes: cuando
los reintentos están deshabilitados, los reintentos de ECC e ID están limitados a menos de dos
vueltas completas del disco.
Restore:
Envía los cabezales a la pista 0 (hasta que la señal Track 000 es activa). Si Track 000 no se activa
tras 1023 pulsos de step activa el bit de error en el registro de estado y deja el error TR 000 en el
registro error. El step rate es establecido por el propio comando.
4 de 8 12/10/00 19:20
EL DISCO DURO DEL AT (IDE, MFM, BUS LOCAL) file:///C|/librosVirtuales/UniversoDigital/1207.html
Seek:
Mueve los cabezales al cilindro indicado. Está soportado un seek simultáneo en dos unidades. Al
final del comando se produce una interrupción.
Read sector:
Cierto número de sectores (1-256) pueden ser leídos del disco duro con o sin el campo ECC
añadido, en el modo PIO (entrada-salida programada, sin DMA). Si los cabezales no están sobre la
pista necesaria, el controlador envía pulsos step para posicionarlo, utilizando el step rate del último
seek o restore. Los errores de datos de hasta 5 bits son corregidos automáticamente en los comandos
de lectura corta. Si un error no corregible tiene lugar, se continúa leyendo el sector donde apareció
pero ya no se leen más sectores en el caso de los accesos multisector. Se produce una interrupción por
cada sector cuando está preparado para ser transferido, pero no al final del comando.
Write sector:
Cierto número de sectores (1-256) pueden ser escritos a disco duro con o sin el campo ECC
añadido, en el modo PIO (entrada-salida programada, sin DMA). Realiza los seeks que sea necesario
hacer. Las interrupciones suceden cada vez que es transferido un sector al buffer (salvo el primero) y al
final del comando. El primer sector debería ser escrito en el buffer inmediatamente después de que el
comando ha sido enviado y "Data-request" es activo.
Format track:
Se formatea la pista indicada según la tabla de interleave que se transfiere. Hay 2 bytes por cada
sector: 0, Nº sector. Así se puede elegir la numeración deseada. Hay que enviar 512 bytes con
independencia de que sean menos en la tabla (por ej. 34 bytes para 17 sectores). El sector count debe
cargarse con el nº de sectores por pista antes de cada comando de estos. Se genera una interrupción al
final del comando de formateo. Los sectores defectuosos se marcan sustituyendo el 0 que les precede
por 80. Cuando se conmuta entre dos unidades, antes de formatear hay que hacer un restore.
Read Verify:
Similar al comando read sector con la diferencia de que no se envían datos al ordenador; de esta
manera simplemente se verifica la integridad de los mismos. Una única interrupción se genera al
completarse el comando o en caso de error.
Diagnose:
El adaptador ejecuta su auto-test y devuelve el resultado en el error register. Se produce una
interrupción cuando completa el comando.
Set Parameters:
Establece los parámetros de la unidad: máximo número de cabezales y sectores/pista. El registro
drive/head indica qué unidad es afectada. Hay que actualizar los registros sector count y drive/head
antes de enviar este comando. Estos parámetros serán empleados para cruzar los cilindros en las
operaciones multisector. Se genera una interrupción cuando se completa el comando. Este comando
debe ser enviado antes de intentar alguna operación multisector. Se soportan dos discos duros, con
diferentes características cada uno, definidas por este comando.
Registro del controlador de disco duro (3F6h) y Registro de entrada digital (3F7h).
Además de informar de la línea de cambio de disco en los disquetes, los bits 0-5 del registro de entrada
digital (3F7h) están relacionados con el disco duro.
5 de 8 12/10/00 19:20
EL DISCO DURO DEL AT (IDE, MFM, BUS LOCAL) file:///C|/librosVirtuales/UniversoDigital/1207.html
En los AT la interrupción de disco duro es la IRQ 14 (INT 76h). La BIOS, en caso de producirse esta
interrupción, almacena un valor 0FFh en 40h:8Eh con el gestor que tiene por defecto. Las transferencias con
el disco duro tienen lugar sin DMA por regla general. Esto se comprende mejor teniendo en cuenta que la
controladora tiene un buffer interno con capacidad para algún sector y, por tanto, cuando hay que transferirlo,
no hay que esperar a que venga del disco mientras este gira lentamente (como en el caso de los disquetes):
una transferencia con el DMA ordinario aquí sería más lenta que a través de la CPU.
Parte de la documentación vista con anterioridad es sólo oficial. Por ejemplo, los discos IDE suelen venir
formateados de fábrica a bajo nivel e ignoran el comando de formateo: estas unidades son bastante
inteligentes y llevan su propia gestión de sectores defectuosos (reemplazándolos por otros que tienen libres
para simular que todo está correcto) así como de interleaves (generalmente 1:1, valores peores se deben a
controladoras obsoletas que no tenían un buffer con capacidad para una pista) y skews óptimos.
Un acceso directo a bajo nivel puede tener mucho interés para ciertas aplicaciones. Por ejemplo, un
antivirus puede asegurarse de que ha reparado la tabla de particiones (o cualquier otra zona del disco) sin
temor a que en su llamada a INT 13h el virus residente le haya estropeado el trabajo (aunque si el virus
trabaja en modo protegido y controla el acceso a los puertos E/S del disco duro...).
HDIRECT.C
/*********************************************************************
* *
* ACCESO A DISCO DURO ESTANDAR AT (IDE, MFM, BUS LOCAL, ETC) *
* PROGRAMANDO DIRECTAMENTE LA CONTROLADORA *
* *
* - Compilar en modelo Large. *
* - Este programa sólo implementa la función de leer sector. *
* - No soportadas controladoras de XT, SCSI u otras. *
* *
*********************************************************************/
#include <dos.h>
#include <alloc.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
6 de 8 12/10/00 19:20
EL DISCO DURO DEL AT (IDE, MFM, BUS LOCAL) file:///C|/librosVirtuales/UniversoDigital/1207.html
#define HD_ECC 2
#define HD_NORETRY 1
int operahd (int unidad, int cabeza, int cilindro, int sector,
int operacion, char huge *direccion, int numsect)
{
int i;
if (operacion==HD_SETPARAM) {
}
else if (operacion==HD_DIAGNOSE) {
}
else if (operacion==HD_FORMAT) {
}
else if ((operacion & 0xFE) == HD_READVERIFY) {
}
else if ((operacion & 0xFC) == HD_READ) {
outportb (HDR_SECNT, numsect); /* nº sectores */
outportb (HDR_SEC, sector); /* primer sector */
outportb (HDR_LCYL, cilindro & 0xFF); /* nº cilindro 0..7 */
outportb (HDR_HCYL, cilindro >> 8); /* nº cilindro 8..9 */
outportb (HDR_DRVHD, unidad << 4 | cabeza | 0xC0);
outportb (HDR_MAIN, cabeza & 8);
asm {
push es /* máxima lectura soportada: casi 64 Kb */
push cx
7 de 8 12/10/00 19:20
EL DISCO DURO DEL AT (IDE, MFM, BUS LOCAL) file:///C|/librosVirtuales/UniversoDigital/1207.html
push dx
push di
mov cx,numsect
xchg ch,cl /* CX = numsect * 256 = nº palabras */
les di,direccion
cld
mov dx,HDR_DATA
db 0F3h, 6Dh /* instrucción 286+ 'rep insw' */
pop di
pop dx
pop cx
pop es
}
}
else if ((operacion & 0xFC) == HD_WRITE) {
}
else if ((operacion & 0xF0) == HD_SEEK) {
}
else if ((operacion & 0xF0) == HD_RESTORE) {
}
}
void main()
{
/* el puntero huge comienza en XXXX:0004 */
if ((buffer=farmalloc(0xFFFC))==NULL) {
printf("\nMemoria insuficiente.\n");
exit(1);
}
p=buffer;
for (i=0; i<2; i++) {
clrscr();
for (j=0; j<256; j+=16) {
for (k=0; k<16; k++) printf("%02X ", *p++); p-=16;
printf(" ");
for (k=0; k<16; k++) {
if (*p<' ') printf("."); else printf("%c", *p);
p++;
}
printf("\n");
}
printf("\n- Estás viendo 256 bytes del sector.\n");
printf("- Pulsa una tecla para continuar.");
getch();
}
}
8 de 8 12/10/00 19:20
EL CONTROLADOR DEL TECLADO: 8042 file:///C|/librosVirtuales/UniversoDigital/1208.html
En este apartado se estudiará a fondo el funcionamiento a bajo nivel del teclado en los ordenadores
compatibles, si bien es poco frecuente que sea necesario acceder al mismo de esta manera.
12.8.1 - EL 8042.
El teclado se conecta al ordenador por medio de un cable que contiene 4 hilos hábiles: dos que conducen
la corriente, uno para datos y otro para reloj. El teclado es en realidad un pequeño microordenador; de hecho
muchos teclados llevan en su interior el chip 8049 de Intel (el microprocesador esclavo del viejo QL de
Sinclair) que consta de unos 2 Kb de memoria ROM y 128 bytes de RAM (las 8 primeras posiciones son
empleadas como registros). Este procesador se encarga de detectar la pulsación de las teclas, generando
unos bytes que las identifican y enviándolos a continuación por el cable a través de un protocolo de
comunicación en serie que en el AT consta de 11 bits por cada dato (1 de inicio, 8 de datos, 1 de paridad y
otro de stop) y 9 en los XT (entre otras razones, porque no se controla la paridad). Los teclados de AT y de
XT generan códigos diferentes para las mismas teclas. Además, al soltar una tecla, los teclados de XT
generan el mismo código que al pulsarla pero con el bit 7 activo; sin embargo, en AT se generan dos códigos
que se envían consecutivamente (0F0h y después el mismo código que al pulsarla). El teclado se encarga de
repetir los códigos de una tecla cuando ésta lleva cierto tiempo pulsada, en el conocido mecanismo
autorepeat de la mayoría de los teclados. Muchos teclados tienen debajo un interruptor que permite
seleccionar su modo de funcionamiento (XT o AT).
Los datos, cuando llegan al ordenador, reciben un tratamiento diferente en función de si el ordenador es un
XT o un AT, mucho más sencillo en el primero. En los XT se van colocando los bits que llegan en un simple
registro de desplazamiento conectado al puerto 60h; al completarse los 8 se produce una interrupción de tipo
IRQ 1 (INT 9), la segunda de mayor prioridad después de la del temporizador. No obstante, el teclado es
capaz de memorizar hasta 8 pulsaciones cuando la CPU no tiene tiempo para atenderle. Después de leer el
código de la tecla, el programa que la gestione habrá de enviar una señal de reconocimiento a la circuitería del
ordenador para permitir que continúe la recepción de datos.
En los AT hay un circuito integrado encargado de interpretar los datos procedentes del teclado y, después
de traducirles adecuadamente para compatibilizar con los XT si así ha sido programado, enviarles a la CPU:
el 8042 de Intel. También sirve de intermediario a las transmisiones de datos de la CPU al teclado, que en el
AT es un periférico bidireccional que puede recibir comandos para configurar los LEDs, entre otras tareas.
Cuando el 8042 recibe un byte entero del teclado, inhibe la comunicación hasta que la CPU lo acepta. Si el
dato se recibe con error de paridad, automáticamente el 8042 lo solicita de nuevo al teclado enviando un
comando de reenvío al mismo y un byte 0FFh a la CPU indicando esta circunstancia, activando también el bit
7 del registro de estado del 8042. Además, chequea que no pasen más de 2 milisegundos durante la
recepción: si se excede este límite se envía también un 0FFh a la CPU y se activa el bit 6 en el registro de
estado. Cuando la CPU envía algo al teclado, el 8042 inserta el bit de paridad automáticamente. Si el teclado
no empieza la comunicación en menos de 15 milisegundos o tarda en recibir el dato más de 2 milisegundos, se
envía un 0FEh a la CPU y se activa el bit 5 en el registro de estado. Además, el teclado ha de responder a
todas las transmisiones con un byte de reconocimiento, si en esta operación hay un error de paridad se
activarán los bits 5 y 7 en el registro de estado; si tarda más de 25 milisegundos en responder también se
envía el byte 0FEh a la CPU y se activan los bits 5 y 6 del registro de estado.
1 de 7 12/10/00 19:20
EL CONTROLADOR DEL TECLADO: 8042 file:///C|/librosVirtuales/UniversoDigital/1208.html
La comunicación teclado-CPU puede ser inhibida por hardware por medio de la llave que incorpora la
unidad central, aunque la comunicación CPU-teclado sigue habilitada. El 8042 se apoya en tres registros
básicos: uno de estado, uno de salida y otro de entrada. El registro de estado, del que ya se ha explicado
parte de su funcionalidad, se encuentra en el puerto de E/S 64h y puede ser leído en cualquier momento. El
significado de sus bits se explica en el cuadro 1.
El registro de salida está ubicado en el puerto 60h y es de sólo lectura; el 8042 lo usa para enviar los
códigos de las teclas a la CPU y los bytes de datos de los comandos que los soliciten. Debería ser leído sólo
cuando el bit 0 del registro de estado está activo.
El registro de entrada del 8042 es de sólo escritura y puede ser accedido por los puertos 60h y 64h según
que lo que se quieran enviar sean datos o comandos al 8042, respectivamente; los datos serán reenviados por
el 8042 hacia el teclado a menos que el propio 8042 esté esperando un dato de la CPU a consecuencia de un
comando previo enviado por ésta. Los datos deben ser escritos en este registro sólo cuando el bit 1 del
registro de estado esté inactivo. En el cuadro 2 se listan los comandos que admite el 8042 (enviados al puerto
64h). Debe darse cuenta el lector de la particularidad de que los registros de salida y entrada son accedidos
por el mismo puerto (60h), siendo la lectura y escritura las que seleccionan el acceso a uno u otro
respectivamente.
+-----+-------------------------------------------------------------------------------------
| BIT | SIGNIFICADO
+-----+-------------------------------------------------------------------------------------
| | Registro de salida lleno. Un 1 indica que el 8042 ha colocado un dato en el registr
| 0 | salida y la CPU aún no lo ha leído. Este bit se pone a 0 cuando la CPU lee el puerto
+-----+-------------------------------------------------------------------------------------
| 1 | Registro de entrada lleno. Un 1 significa que ha sido colocado un dato en el regist
| | entrada y el 8042 aún no lo ha leído.
+-----+-------------------------------------------------------------------------------------
| 2 | Banderín del sistema: asignado con un comando del 8042. 0 al arrancar.
+-----+-------------------------------------------------------------------------------------
| | Comando/dato. Se pone a 1 o a 0 al enviar algo al puerto 60h o al 64h respectivament
| 3 | esta manera, el 8042 sabe si lo que se le envía son órdenes o datos (órdenes= 1).
| | puertos conectan con el registro de entrada.
+-----+-------------------------------------------------------------------------------------
| 4 | Bit de inhibición. Este bit se actualiza siempre que se coloca un dato en el regist
| | salida, un 0 indica teclado inhibido.
+-----+-------------------------------------------------------------------------------------
| 5 | Transmisión fuera de tiempo. Indica que la transmisión de un dato hacia el teclado
| | sido respondida en los márgenes de tiempo adecuados.
+-----+-------------------------------------------------------------------------------------
| 6 | Recepción fuera de tiempo. Indica si el teclado ha enviado un dato y sigue enviando
| | después del tiempo esperado.
+-----+-------------------------------------------------------------------------------------
| 7 | Error de paridad. Indica la paridad del dato recibido: 0 la correcta.
+-----+-------------------------------------------------------------------------------------
CUADRO 1: REGISTRO DE
Como se dijo en el apartado anterior, el teclado del AT es bidireccional y admite comandos por parte del
ordenador. Estudiaremos ahora cuáles son esos comandos. En primer lugar, tras el arranque del ordenador y
al recibir la alimentación el teclado, éste realiza un autotest denominado BAT (Basic Assurance Test) donde
chequea su ROM, RAM y enciende y apaga todos los LED. Esta operación emplea entre 600 y 900
milisegundos; al acabar el BAT y cuando sea posible establecer la comunicación con el ordenador (líneas de
reloj y datos en alto) envía un byte 0AAh si todo ha ido bien y un 0FCh si ha habido fallos; inicializando
2 de 7 12/10/00 19:20
EL CONTROLADOR DEL TECLADO: 8042 file:///C|/librosVirtuales/UniversoDigital/1208.html
El teclado tiene un buffer interno con capacidad para 17 bytes (unas 8 teclas) con objeto de almacenar las
últimas teclas pulsadas cuando no puede enviarlas al 8042. Cuando este buffer se llena, su última posición
(17ª) se rellena con 0 y se ignoran las siguientes pulsaciones.
Los comandos al teclado pueden ser enviados en cualquier momento al puerto 60h: a menos que el 8042
esté esperando por un byte de datos en el registro de entrada, como consecuencia de un comando previo,
redireccionará todo lo que se le envíe por el puerto 60h hacia el teclado. El teclado responderá en menos de
20 milisegundos, devolviendo una señal de reconocimiento por medio de un byte 0FAh. Los principales
comandos (diferenciados de los datos por tener el bit 7 activo) son:
Reset (0FFh): Al recibirlo envía una señal de reconocimiento y se asegura de que la CPU se de por enterada
poniendo en alto las líneas de reloj y datos un mínimo de 500 microsegundos; el teclado permanece inhibido
hasta que la CPU acepta la señal de reconocimiento o envía otro comando que sobreescribe y anula éste.
Llegados a este punto, el teclado ejecuta de nuevo el BAT, estableciendo valores por defecto para la
autorepetición y limpiando su registro de salida.
Reenvío (0FEh): El sistema puede enviar este comando al teclado cuando detecta un fallo en la recepción
desde el teclado. Este comando sólo puede ser enviado después de una transmisión del teclado y antes de
habilitar la comunicación para la siguiente recepción. El teclado responde enviando de nuevo el dato anterior
(si ya era un 0FEh, el último dato que envió que no fuera 0FEh).
+---------+---------------------------------------------------------------------------------
| COMANDO | SIGNIFICADO
+-----+---+---------------------------------------------------------------------------------
| 20h | Leer el byte de comando del 8042 (ver cuadro 3). Esta orden envía al registro de sal
| | el puerto 60h) dicho byte para que sea leído.
+-----+-------------------------------------------------------------------------------------
| 60h | Escribir el byte de comando del 8042. El siguiente byte que se envíe al registro de
| | (puerto 60h) será el byte de comando del 8042.
+-----+-------------------------------------------------------------------------------------
| AAh | Autotest. El 8042 realiza un diagnóstico interno y coloca un 55h en el registro de
| | si todo va bien.
+-----+-------------------------------------------------------------------------------------
| | Test del interface. El controlador chequea las líneas de reloj y datos devolviendo:
| ABh | hay errores; 1: el reloj está demasiado en bajo, 2: está demasiado en alto; 3: la lí
| | datos está demasiado en bajo y 4: la línea de datos está demasiado en alto.
+-----+-------------------------------------------------------------------------------------
| ACh | Volcado de diagnóstico. Envía al registro de salida, sucesivamente, 16 bytes de la
| | 8042, el estado de los registros de entrada y salida y la palabra de estado del cont
+-----+-------------------------------------------------------------------------------------
| ADh | Inhibir teclado. Esto activa el bit 4 del byte de comando del 8042.
+-----+-------------------------------------------------------------------------------------
| AEh | Habilitar teclado. Esto baja el bit 4 del byte de comando del 8042.
+-----+-------------------------------------------------------------------------------------
| | Leer el puerto de entrada (véase cuadro 4). Esto obliga al 8042 a leer el puerto de
| C0h | y colocar lo que lee en el registro de salida; sólo ha de emplearse este comando cu
| | registro de salida está vacío.
+-----+-------------------------------------------------------------------------------------
| D0h | Leer el puerto de salida. El 8042 lee el puerto de salida y lo coloca en el registro
| | lida; sólo debe emplearse este comando si dicho registro está vacío.
3 de 7 12/10/00 19:20
EL CONTROLADOR DEL TECLADO: 8042 file:///C|/librosVirtuales/UniversoDigital/1208.html
+-----+-------------------------------------------------------------------------------------
| D1h | Escribir el puerto de salida (ver cuadro 5). El siguiente byte que se envíe al regi
| | entrada (puerto 60h) se colocará en el puerto de salida.
+-----+-------------------------------------------------------------------------------------
| E0h | Leer entradas de testeo. El 8042 coloca en el registro de salida los bits de reloj (
| | y datos (bit 1) para permitir la comunicación directa con el teclado.
+-----+-------------------------------------------------------------------------------------
| | Los bits 0 al 3 de este comando (la parte baja de este mismo comando) se relacionan
| Fxh | bits 0 al 3 del puerto de salida del 8042; un 0 indica bit pulsado durante 6 microse
| | (apróx.) y un 1 que el bit no resulta modificado; ¡cuidado con el reset!.
+-----+-------------------------------------------------------------------------------------
CUADRO 2: COMANDOS
Establecer valores por defecto (0F6h): Devuelve la autorepetición a los valores habituales, limpia su registro
de salida y continúa rastreando las teclas si no estaba inhibido; es una especie de reset en caliente.
Establecer valores por defecto y parar (0F5h): Similar al comando anterior, pero dejando de rastrear las
teclas y permaneciendo inhibido hasta recibir más instrucciones.
Habilitar (0F4): Reanuda el funcionamiento interrumpido por el comando anterior o algún otro.
Establecer ratio y retardo de autorepetición (0F3h): Tras este comando debe enviarse otro inmediatamente a
continuación, que se interpretará como dato, estableciendo los valores de autorepetición. De este segundo
byte, el bit 7 estará siempre a cero; el valor de los bits 5 y 6, sumándole una unidad, indica el tiempo que ha
de pasar desde que se pulsa una tecla hasta que comience a autorepetirse, en unidades de 0,25 segundos
(±20%). Los bits 2, 1 y 0 forman un número A; los bits 4 y 3 forman otro número B; por medio de la
siguiente fórmula se obtiene la tasa o ratio de autorepetición en «teclas por segundo»:
1
------------------------------
(8 + A) * ( 2 ^ B) * 0.00417
Una vez recibido este comando, el teclado envía la acostumbrada señal de reconocimiento, deja de rastrear
las teclas y espera por el parámetro de autorepetición, respondiendo al mismo con otra señal de
reconocimiento y volviendo a rastrear las teclas. Si en lugar de recibir el parámetro recibe otro comando (bit 7
activo) dejará inalterados los valores de autorepetición y procesará dicho comando, aunque ¡cuidado!:
permanecerá inhibido hasta que se le habilite con el comando 0F4h. Por defecto, el sistema establece una tasa
de 10 caracteres por segundo y 0,5 segundos de espera (parámetro 4Ch).
+-----+-------------------------------------------------------------------------------------
| BIT | SIGNIFICADO
+-----+-------------------------------------------------------------------------------------
| 0 | Activar la interrupción del registro de salida lleno: un 1 indica que el 8042 genere
| | una IRQ1 (INT 9) tras colocar un dato en el registro de salida (esto es lo normal).
+-----+-------------------------------------------------------------------------------------
| 1 | Reservado (escribir 0).
+-----+-------------------------------------------------------------------------------------
| 2 | Banderín del sistema. Este bit define el bit 2 del registro de estado.
+-----+-------------------------------------------------------------------------------------
4 de 7 12/10/00 19:20
EL CONTROLADOR DEL TECLADO: 8042 file:///C|/librosVirtuales/UniversoDigital/1208.html
+-----+--------------------------------+ +-----+------------------------------------------
| BIT | SIGNIFICADO | | BIT | SIGNIFICADO
+-----+--------------------------------+ +-----+------------------------------------------
| 0-3 | Indefinidos | | 0 | Reset del sistema (como Ctrl-Alt-Del).
+-----+--------------------------------+ +-----+------------------------------------------
| 4 | RAM del sistema. A 1 si insta- | | | Línea A20: 0 fuerza la línea A20 de la CP
| | lada la extensión de 256 Kb. | | 1 | se prohíbe acceder a la memoria por encim
+-----+--------------------------------+ | | emula el direccionamiento de los PC/XT;
| 5 | A 0 si presente el puente (o | | | la controle la CPU aunque hay PC's en que
| | «jumper») del fabricante. | +-----+------------------------------------------
+-----+--------------------------------+ | 2-3 | Indefinidos.
| | Tipo de pantalla. 0 si la pan- | +-----+------------------------------------------
| 6 | talla principal es de color y | | 4 | Registro de salida lleno.
| | 1 si es monocroma. | +-----+------------------------------------------
+-----+--------------------------------+ | 5 | Registro de entrada vacío.
| | 0: el teclado ha sido bloquea- | +-----+------------------------------------------
| 7 | do con la llave externa de la | | 6 | Línea de reloj (comunicación directa con
| | unidad central. | +-----+------------------------------------------
+-----+--------------------------------+ | 7 | Línea de datos (comunicación directa con
CUADRO 4: BYTE RECIBIDO POR EL +-----+------------------------------------------
PUERTO DE ENTRADA CUADRO 5: BYTE A ENVIAR
No operación (0F7h a 0FDh y 0EFh al 0F2h): Son códigos reservados; el teclado al recibirlos envía la señal
de reconocimiento de siempre y no realiza ninguna acción.
Eco (0EEh): Si el teclado recibe este comando, lo reenvía a continuación. Es una ayuda al diagnóstico.
Encender/apagar los LED (0EDh). Tras este comando se ha de enviar otro byte de datos, cuyos bits 0, 1 y 2
están ligados al estado de los LED de Scroll Lock, Num Lock y Caps Lock, respectivamente; los demás
están reservados. Al recibir el comando envía la correspondiente señal de reconocimiento y deja de rastrear
las teclas, esperando por el dato. Si en vez de un dato recibe otro comando, dejará intactos los LED,
procesará dicho comando y continuará rastreando las teclas (sin quedar inhibido en esta ocasión). El siguiente
ejemplo muestra cómo establecer los LED configurados en AH:
CLI
MOV AL,0EDh
OUT 60h,AL ; enviar comando
XOR CX,CX
espera: JMP SHORT $+2 ; insertar estados de espera para AT obsoleto
JMP SHORT $+2
IN AL,64h
5 de 7 12/10/00 19:20
EL CONTROLADOR DEL TECLADO: 8042 file:///C|/librosVirtuales/UniversoDigital/1208.html
TEST AL,2
LOOPNZ espera ; esperar que reciba comando
MOV AL,AH
OUT 60h,AL ; establecer los LED
STI
En general, este será el procedimiento a seguir para cualquier comando que requiera parámetros: hay que
esperar el momento adecuado para enviarlos; el LOOPNZ evita que la CPU se quede colgada si por
cualquier motivo fallara el teclado o el 8042. Como se ve, se establecen los 3 LED a la vez, aunque si sólo se
desea cambiar uno habrá que consultar el estado actual de los otros en las variables de la BIOS. No obstante,
este cambio es sólo puntual ya que al pulsar las teclas que actúan sobre los LED, la BIOS o el KEYB los
reajustarán anulando el cambio, siendo necesario reprogramar parcialmente la interrupción del teclado si se
desea evitarlo.
Más bien cabría llamarla la comunicación teclado -< 8042: aunque muchos de estos códigos acaben
siendo interpretados por la CPU, algunos se los queda el 8042 que siempre es el primero en enterarse. A
continuación se listan los valores que el teclado puede enviar a la CPU o al 8042 en un momento dado.
Reenvío (0FEh): El teclado puede enviar este comando a la CPU para solicitar el reenvío cuando detecta un
fallo en la recepción (normalmente de paridad) o una entrada incorrecta.
Reconocimiento ó ACK (0FAh): El teclado devuelte este valor cada vez que la CPU le envía algo, para
indicar que lo ha recibido (excepto en el caso de los comandos Eco y Reenvío de la CPU).
Desbordamiento (0): Cuando la CPU intenta leer el teclado directamente sin haber códigos en el buffer del
teclado (el buffer interno del propio teclado, se entiende) accederá a la posición 17ª del mismo,
encontrándose este valor.
Fallo en el diagnóstico (0FDh): El teclado periódicamente se autochequea y envía este código si detecta algún
fallo. Si el fallo sucede durante el BAT, dejará de rastrear las teclas en espera de un comando de la CPU; en
cualquier otro momento continuará rastreando las teclas.
Código de tecla soltada ó break code (0F0h): El teclado envía este código a la CPU para indicar que el
siguiente código que enviará a continuación corresponderá a una tecla soltada. Bajo MS-DOS este código lo
intercepta el 8042 y se lo oculta a la CPU, con objeto de emular el código de tecla soltada de los PC/XT.
BAT completado (0AAh): Después de realizar el BAT el teclado envía un 0AAh para indicar que ha salido
bien, o un 0FCh (u otro valor) si ha habido fallos.
Respuesta al eco (0EEh): El teclado envía este valor a la CPU si ésta se lo ha enviado a él.
Debido a la presencia del 8042, normalmente no será preciso que la CPU se comunique directamente con
6 de 7 12/10/00 19:20
EL CONTROLADOR DEL TECLADO: 8042 file:///C|/librosVirtuales/UniversoDigital/1208.html
el teclado a través de las líneas de reloj y datos. No obstante, este capítulo está explicado en el manual de
referencia técnico del IBM AT, al menos en la edición de 1984; por tanto, aquellos aficionados que estén
pensando construirse su propio ordenador y acoplarle un teclado ordinario de PC podrían consultar ese libro.
Por cierto, en los PC y XT no es preciso tampoco realizar esta tarea, ya que el teclado con el conmutador de
selección de la parte inferior en modo XT no es realmente bidireccional (de hecho, lleva un control autónomo
de los LED) por lo que no tiene sentido intentar enviar nada. Y a la hora de recibir, hay métodos mucho más
cómodos...
7 de 7 12/10/00 19:20
EL PUERTO SERIE: UART 8250 file:///C|/librosVirtuales/UniversoDigital/1209.html
La transmisión de datos en serie es una de las más comunes para aquellas aplicaciones en las que la
velocidad no es demasiado importante, o no es posible conseguirla (por ejemplo, vía red telefónica). Para
simplificar el proceso de enviar los bits uno por uno han surgido circuitos integrados que realizan la función,
teniendo en cuenta todos los tiempos necesarios para lograr una correcta comunicación y aliviando a la CPU
de esta pesada tarea. El circuito que estudiaremos es el 8250 de National, fabricado también por Intel,
aunque las diferencias respecto al 16550 serán brevemente señaladas. Esta última UART es más reciente y
mucho más potente -aunque solo sea por unos pequeños detalles- y cada vez está más extendida, en
particular en las actuales placas base.
La línea que transmite los datos en serie está inicialmente en estado alto. Al comenzar la transferencia, se
envía un bit a 0 ó bit de inicio. Tras él irán los 8 bits de datos a transmitir (en ocasiones son 7, 6 ó 5): estos
bits están espaciados con un intervalo temporal fijo y preciso, ligado a la velocidad de transmisión que se esté
empleando. Tras ellos podría venir o no un bit de paridad generado automáticamente por la UART. Al final,
aparecerá un bit (a veces un bit y medio ó dos bits) a 1, que son los bits de parada o bits de stop. Lo de
medio bit significa que la señal correspondiente en el tiempo a un bit dura la mitad; realmente, en
comunicaciones se utiliza el término baudio para hacer referencia a las velocidades, y normalmente un baudio
equivale a un bit. La presencia de bits de inicio y parada permite sincronizar la estación emisora con la
receptora, haciendo que los relojes de ambas vayan a la par. A la hora de transmitir los bytes de datos unos
tras otros, existe flexibilidad en los tiempos, de ahí que este tipo de comunicaciones se consideren
asíncronas. La transmisión de los 8 bits de datos de un byte realmente es síncrona, pero las comunicaciones
en serie siempre han sido consideradas asíncronas.
Para una transmisión en serie básica bastan tres hilos. Sin embargo, el software que controla el puerto serie
a través de la interfaz RS-232-C podría requerir más señales de control para establecer la comunicación, al
igual que para controlar un modem telefónico pueden hacer falta más líneas (de control, no telefónicas...).
Bromas aparte, sobre comunicaciones en serie existe todo un mundo; acerca de este tema se han escrito
muchos libros completos. Lógicamente, aquí no vamos a dar ningún curso de comunicaciones en serie. Sin
embargo, los menos introducidos en la materia no deben temer: ¿qué mejor manera de aprender sobre las
comunicaciones en serie que examinar cómo funciona un chip que las soporta?. Desde luego, también se
podría partir desde el punto de vista contrario, pero como entendido en sistemas digitales, el lector puede que
tenga menos problemas con este interesante enfoque.
1 de 12 12/10/00 19:21
EL PUERTO SERIE: UART 8250 file:///C|/librosVirtuales/UniversoDigital/1209.html
Data In Strobe. Línea de entrada que indica al 8250 que deje los datos en el
bus (D0..D7), los datos dejados dependen del registro seleccionado con
DISTR:
A0..A2. Son necesarias CS0..CS2 para habilitar DISTR. En vez de DISTR se
puede usar -DISTR, pero sólo una de las dos.
DOSTR: Data Out Strobe. Idéntico a DISTR pero en salida.
Data Bits 0..7: Bus triestado bidireccional de 8 líneas para transmitir datos,
D0..D7: información de control y de estado entre la CPU y el 8250. El primer bit
enviado/recibido es D0.
Register Select. Líneas de entrada que indican el registro del 8250 usado en la
A0..A2:
operación.
Crystal/Clock: Conexiones para el cristal del cuarzo del BRG. XTAL1 puede
XTALx: actuar como entrada de reloj externa, en cuyo caso XTAL2 debería quedar
abierto.
Serial Data Output: Salida de datos en serie del 8250. Una marca es un '1' y un espacio es un '0'. SOUT está
SOUT: en marca cuando el transmisor está inhibido, MR está a 1, el registro de transmisión está vacío o en el modo
lazo (LOOP) del 8250. No es afectado por -CTS.
Clear To Send: Línea de entrada. El estado lógico de esta señal puede consultarse en el bit CTS del Modem
Status Register (MSR) -como el bit CTS es el bit 4 del MSR se
-CTS: referencia MSR(4)-. Un cambio en el estado de -CTS desde la última lectura del MSR provoca que se active
DCTS (bit MSR(0)). Cuando -CTS está activo (a 0) el modem indica que el dato en SOUT puede ser
transmitido. -CTS no afecta al modo lazo (LOOP) del 8250.
Data Set Ready: Línea de entrada. El estado lógico de esta señal puede consultarse en MSR(5). DDSR (bit
MSR(1)) indica si -DSR ha cambiado desde la última lectura del MSR. Cuando -DSR está activo el modem
-DSR:
indica que está listo para intercambiar datos con el 8250; ello depende del estado del DCE (Data
Communications Equipment) local y no implica que haya comunicación con la estación remota.
Data Terminal Ready. Línea de salida que puede activarse (poner a 0) escribiendo un 1 en MCR(0), y
desactivarse escribiendo un 0 en dicho bit o ante la activación del pin MR. Con -DTR activo se indica al
-DTR:
DCE que el 8250 puede recibir datos. En algunas circunstancias, esta señal se usa como LED de 'power on'.
Si está inactivo, el DCE desconecta el modem del circuito de telecomunicaciones.
Request To Send. Línea de salida que habilita el modem. Se activa (poner a 0) escribiendo un 1 en MCR(1).
-RTS: Esta señal se pone en alto en respuesta a MR. -RTS indica al DCE que el 8250 tiene un dato listo para
transmitir. En la modalidad half-duplex, esta señal se utiliza para controlar la dirección de la línea.
Esta línea de salida contiene una señal de reloj 16 veces mayor que la frecuencia usada para transmitir.
-BAUDOUT: Equivale a la frecuencia de entrada en el oscilador dividida por el BRG. La estación receptora podría emplear
esta señal conectándola a RCLK (para compartir el mismo reloj).
Estas dos salidas de propósito general se pueden activar (poner a 0) escribiendo un 1 en MCR(2) y MCR(3).
-OUTx:
Son desactivadas por la señal MR. En el modo lazo (LOOP o bucle), están también inactivas.
Ring Indicator. Esta línea de entrada indica si el modem ha detectado que llaman por la línea y puede
consultarse en MSR(6). El bit TERI (MSR(2)) indica si esta línea ha cambiado desde la última lectura del
MSR. Si las interrupciones están habilitadas (IER(3) activo) esta patilla provoca una interrupción al
-RI:
activarse. -RI permanece activo durante el mismo intervalo de tiempo que la zona activa del ciclo de llamada
e inactivo en los intervalos de la zona inactiva (o cuando el DCE no detecta la llamada). El circuito no se
corta por culpa de -DTR.
Data Carrier Detect. Línea de entrada que indica si el modem ha detectado portadora. Se puede consultar su
estado lógico en MSR(7). El bit MSR(3) indica si esta línea ha cambiado desde la última lectura del MSR.
-DCD:
Esta línea no tiene efecto sobre el receptor. Si las interrupciones están permitidas, una interrupción será
generada ante el cambio de esta línea.
Master Reset. Esta línea de entrada lleva el 8250 a un estado inactivo interrumpiendo su posible actividad. El
MR: MCR y las salidas ligadas al mismo son borradas. El LSR es borrado en todos sus bits salvo THRE y TEMT
(que son activados). El 8250 permanece en este estado hasta volver a ser programado.
2 de 12 12/10/00 19:21
EL PUERTO SERIE: UART 8250 file:///C|/librosVirtuales/UniversoDigital/1209.html
(que son activados). El 8250 permanece en este estado hasta volver a ser programado.
Interrupt Request. Línea de salida que se activa cuando se produce una interrupción de alguno de estos
tipos y está permitida: Recepción de banderín de error, dato recibido disponible, registro de retención de
INTRPT:
transmisión vacío, y estado del modem. Esta línea se desactiva con el apropiado servicio de la interrupción o
ante MR.
Serial Data Input. Es la línea de entrada de datos desde el modem. En el modo lazo (LOOP o bucle) están
SIN:
inhibidas las entradas en SIN.
Chip Select. Estas entradas actúan como líneas de habilitación para las señales de escritura (DOSTR,
CS0..2:
-DOSTR) y lectura (DISTR, -DISTR).
Chip Select Out. Esta línea de salida se activa cuando el chip ha sido seleccionado con CS0..2. No comenzará
CSOUT:
transferencia de datos alguna hasta que CSOUT se active.
Driver Disable. Esta salida está inactiva cuando la CPU lee datos del 8250. Una salida activa puede
DDIS:
emplearse para inhibir un transceiver externo cuando la CPU está leyendo datos.
Address Strobe. Cuando esta línea de entrada está activa se enclavan las líneas A0..A2 y CS0..2; esto puede
ser necesario si los pines de selección de registro no son estables durante la duración de la operación de
-ADS:
lectura o escritura (modo multiplexado). Si esto no es preciso, esta señal se puede mantener inactiva (modo
no-multiplexado).
Esta línea se corresponde con la entrada de reloj para la sección receptora, equivalente a 16 veces la
RCLK: frecuencia empleada en la transmisión y puede proceder del BAUDOUT de la estación remota o de un reloj
externo.
El 8250 dispone de 11 registros (uno más el 16550) pero sólo 3 líneas de dirección para seleccionarlos. Lo que permita
distinguir unos de otros será, aparte de las líneas de direcciones, el sentido del acceso (en lectura o escritura) y el valor de
un bit de uno de los registros: el bit DLAB del registro LCR, que es el bit 7 de dicho registro. La notación para hacer
referencia a un bit de un registro se escribe REG(i); en este ejemplo, el bit DLAB sería LCR(7). Realmente, DLAB se emplea
sólo puntualmente para poder acceder y programar los registros que almacenan el divisor de velocidad; el resto del tiempo,
DLAB estará a 0 para acceder a otros registros más importantes.
3 de 12 12/10/00 19:21
EL PUERTO SERIE: UART 8250 file:///C|/librosVirtuales/UniversoDigital/1209.html
Los bits WLS seleccionan el tamaño del dato empleado. STB indica el número de bits de stop, que
pueden ser 1 (STB=0) ó 2 (STB=1), al trabajar con datos de 5 bits STB=1 implica 1.5 bits de stop. PEN
(Parity Enable) permite habilitar o no la generación de bit de paridad, EPS (Even Parity Select) selecciona
paridad par si está a 1 (o impar en caso contrario). Stick Parity permite forzar el bit de paridad a un estado
conocido según el valor de EPS. Cuando Break Control es puesto a 1, la salida SOUT se pone en estado
espacio (a 0), sólo afecta a SOUT y no a la lógica de transmisión. Esto permite a la CPU alertar a un terminal
del sistema sin transmitir caracteres erróneos o extraños si se siguen estas fases: 1) cargar un carácter 0 en
respuesta a THRE, 2) activar Break Control en respuesta al próximo THRE, 3) esperar a que el transmisor
esté inactivo (TEMT=1) y bajar Break Control. Durante el Break, el transmisor puede usarse como un
preciso temporizador de carácter.
El bit DLAB (Divisor Latch Access Bit) puesto a 1 permite acceder a los Latches divisores DLL y DLM
del BRG en lectura y escritura. Para acceder al RBR, THR y al IER debe ser puesto a 0.
2) LSR (Line Status Register). Este suele ser el primer registro consultado tras una interrupción.
DR está activo cuando hay un carácter listo en el RBR y es puesto a 0 cuando se lee el RBR. Los bits 1 al
4 de este registro (OE, PE, FE y BI) son puestos a 0 al consultarlos -cuando se lee el LSR- y al activarse
pueden generar una interrupción de prioridad 1 si ésta interrupción está habilitada. OE se activa para indicar
que el dato en el RBR no ha sido leído por la CPU y acaba de llegar otro que lo ha sobreescrito. PE indica si
hay un error de paridad. FE indica si el carácter recibido no tiene los bit de stop correctos. BI se activa
cuando la entrada de datos es mantenida en espacio (a 0) durante un tiempo superior al de transmisión de un
carácter (bit de inicio + bits de datos + bit de paridad + bit de parada).
THRE indica que el 8250 puede aceptar un nuevo carácter para la transmisión: este bit se activa cuando el
THR queda libre y se desactiva escribiendo un nuevo carácter en el THR. Se puede producir, si está
habilitada; la interrupción THRE (prioridad 3); INTRPT se borra leyendo el IIR. El 8250 emplea un registro
interno para ir desplazando los bit y mandarles en serie (el Transmitter Shift Register), dicho registro se carga
desde el THR. Cuando ambos registros (THR y el Transmitter Shift) están vacíos, TEMT se activa; volverá a
desactivarse cuando se deje otro dato en el THR hasta que el último bit salga por SOUT.
4 de 12 12/10/00 19:21
EL PUERTO SERIE: UART 8250 file:///C|/librosVirtuales/UniversoDigital/1209.html
Las líneas de salida -DTR, -RTS, -OUT1 y -OUT2 están directamente controladas por estos bits; como
se activan a nivel bajo, son puestas a 0 escribiendo un 1 en estos bits y viceversa. Estas líneas sirven para
establecer diversos protocolos de comunicaciones.
El bit LOOP introduce el 8250 en un modo lazo (o bucle) de autodiagnóstico. Con LOOP activo, SOUT
pasa a estado de marca (a 1) y la entrada SIN es desconectada. Los registros de desplazamiento empleados
en la transmisión y la recepción son conectados entre sí. Las cuatro entradas de control del modem (-CTS,
-DSR, DC y -RI) son desconectadas y en su lugar son internamente conectadas las cuatro salidas de control
del modem (-DTR, -RTS, -OUT1 y -OUT2) cuyos pines son puestos en estado inactivo (alto). En esta
modalidad de operación (modo lazo o bucle), los datos transmitidos son inmediatamente recibidos, lo que
permite comprobar el correcto funcionamiento del integrado. Las interrupciones son completamente
operativas en este modo, pero la fuente de estas interrupciones son ahora los 4 bits bajos del MCR en lugar
de las cuatro entradas de control. Estas interrupciones están aún controladas por el IER.
Además de la información de estado del modem, los 4 bits bajos (DDCD, TERI, DDSR, DCTS) indican
si la línea correspondiente, en los 4 bits superiores, ha cambiado de estado desde la última lectura del MSR;
en el caso de TERI sólo indica transiciones bajo-<alto en -RI (y no las de sentido contrario). La línea CTS
del modem indica si está listo para recibir datos del 8250 a través de SOUT (en el modo lazo este bit equivale
al bit RTS del MCR). La línea DSR del modem indica que está listo para dar datos al 8250 (en el modo lazo
-o LOOP- equivale al bit DTR del MCR). RI y DCD indican el estado de ambas líneas (en el modo lazo se
corresponden con OUT1 y OUT2 respectivamente). Al leer el MSR, se borran los 4 bits inferiores (que en
una lectura posterior estarían a 0) pero no los bits de estado (los 4 más significativos).
Los bits de estado (DCD, RI, DSR y CTS) reflejan siempre la situación de los pines físicos respectivos
(estado del modem). Si DDCD, TERI, DDSR ó DCTS están a 1 y se produce un cambio de estado durante
la lectura, dicho cambio no será reflejado en el MSR; pero si están a 0 el cambio será reflejado después de la
lectura. Tanto en el LSR como en el MSR, la asignación de bits de estado está inhibida durante la lectura del
registro: si se produce un cambio de estado durante la lectura, el bit correspondiente será activado después de
la misma; pero si el bit ya estaba activado y la misma condición se produce, el bit será borrado tras la lectura
en lugar de volver a ser activado.
5) y 6) BRSR (Baud Rate Select Register). Son los registros DLL (parte baja) y DLM (parte alta).
Estos dos registros de 8 bits constituyen un valor de 16 bits que será el divisor que se aplicará a la
frecuencia base para seleccionar la velocidad a emplear. Dicha frecuencia base (por ejemplo, 1.8432 MHz)
será dividida por 16 veces el valor almacenado aquí. Por ejemplo, para obtener 2400 baudios:
1843200
--------- = 48 -> DLL=48, DLM=0
16 * 2400
5 de 12 12/10/00 19:21
EL PUERTO SERIE: UART 8250 file:///C|/librosVirtuales/UniversoDigital/1209.html
El circuito receptor del 8250 es programable para 5, 6, 7 u 8 bits de datos. En el caso de emplear menos
de 8, los bits superiores de este registro quedan a 0. Los datos entran en serie por SIN (comenzando por el
bit D0) en un registro de desplazamiento gobernado por el reloj de RCLK, sincronizado con el bit de inicio.
Cuando un carácter completa el registro de desplazamiento de recepción, sus bits son volcados al RBR y el
bit DR del LSR es activado para indicar a la CPU que puede leer el RBR. El diseño del 8250 permite la
recepción continua de datos sin pérdidas: el RBR almacena siempre el último carácter recibido dando tiempo
suficiente a la CPU para leerlo mientras simultáneamente está cargando el registro de desplazamiento con el
siguiente; si la CPU tarda demasiado un nuevo dato podría aparecer en el RBR antes de haber leído el
anterior (condición de overrun, bit OE del LSR).
El registro de retención de transmisión almacena el siguiente carácter que va a ser transmitido en serie
mientras el registro de desplazamiento de transmisión está enviando el carácter actual. Cuando el registro de
desplazamiento se vacíe, será cargado desde el THR para transmitir el nuevo carácter. Al quedar vacío THR,
el bit THRE del LSR se activa. Cuando estén vacíos tanto el THR como el registro de desplazamiento de
transmisión, el bit TEMT del LSR se activa.
Este registro no es empleado por el 8250, y de hecho no existía en las primeras versiones del integrado.
Puede ser empleado por el programador como una celdilla de memoria.
Existen 4 niveles de prioridad en las interrupciones generables por el 8250, por este orden:
La información que indica que hay una interrupción pendiente y el tipo de la misma es almacenada en el
IIR. El IIR indica la interrupción de mayor prioridad pendiente. No serán reconocidas otras interrupciones
hasta que la CPU envíe la señal de reconocimiento apropiada. En el registro IIR, el bit 0 indica si hay una
interrupción pendiente (bit 0=0) o si no la hay (bit 0=1), esto permite tratar las interrupciones en modo polled
consultando este bit. Los bits 1 y 2 indican el tipo de interrupción. Los restantes están a 0 en el 8250, pero el
16550 utiliza alguno más.
+-----------------------------------+-------------------------------------------------------
| IDENTIFICACIÓN DE LA INTERRUPCIÓN | ACTIVACIÓN / RECONOCIMIENTO (RESET) DE LA INTERRUP
+-------+-------+-------+-----------+------------------+---------------+--------------------
| Bit 2 | Bit 1 | Bit 0 | Prioridad | Flag | Fuente | Reconocimien
+-------+-------+-------+-----------+------------------+---------------+--------------------
| X | X | 1 | | Ninguno | Ninguna |
| | | | | | |
+-------+-------+-------+-----------+------------------+---------------+--------------------
6 de 12 12/10/00 19:21
EL PUERTO SERIE: UART 8250 file:///C|/librosVirtuales/UniversoDigital/1209.html
Este registro de escritura se utiliza para seleccionar qué interrupciones activan INTRPT y, por
consiguiente, van a ser solicitadas a la CPU. Deshabilitar el sistema de interrupciones inhibe el IIR y desactiva
la salida INTRPT.
El 16550 genera también una interrupción de TIMEOUT (prioridad 1) si hay datos en la cola FIFO y no
son leídos dentro del tiempo que dura la recepción de 4 bytes o si no se reciben datos durante el tiempo que
tomaría recibir 4 bytes.
El bit 0 debe estar a 1 para escribir los bits 1 ó 2. Cuando el bit 1 ó el 2 son activados, la cola afectada es
borrada y el bit es devuelto a 0. Los registros de desplazamiento de la transmisión y la recepción, en cada
caso, no resultan afectados.
La sección de transmisión del 8250 consiste en el Registro de Retención de transmisión (THR), el Registro
de Desplazamiento de la Transmisión (TSR) y en la lógica de control asociada. Dos bits en el LSR indican si
está vacío el THR (bit THRE) o el TSR (bit TEMT). El carácter de 5-8 bits a ser transmitido es escrito en el
THR; la CPU debería realizar esta operación sólo si THRE está activo: este bit es activado cuando el carácter
es copiado del THR al TSR durante la transmisión del bit de inicio.
Cuando el transmisor está inactivo, tanto THRE como TEMT están activos. El primer carácter escrito
7 de 12 12/10/00 19:21
EL PUERTO SERIE: UART 8250 file:///C|/librosVirtuales/UniversoDigital/1209.html
provoca que THRE baje; tras completarse la transferencia vuelve a subir aunque TEMT permanecerá bajo
mientras dure la transferencia en serie del carácter a través de TSR. Si un segundo carácter es escrito en
THR, THRE vuelve a bajar y permanecerá bajo hasta que el TSR termine la transmisión, porque no es posible
volcar el contenido de THR en TSR hasta que este último no acabe con el carácter que estaba transmitiendo.
Cuando el último carácter ha sido transmitido fuera del TSR, TEMT vuelve a activarse y THRE también lo
hará tras un cierto tiempo (el que tarda en escribirse THR en TSR).
En la recepción, los datos en serie asíncronos entran por la patilla SIN. El estado inactivo de la línea se
considera el '1' lógico. Un circuito de detección de bit de inicio está continuamente buscando una transición
alto-<bajo que interrumpa el estado inactivo. Cuando la detecta, se resetea un contador interno y cuenta 7½
pulsos de reloj (tener en cuenta que la frecuencia base es dividida por 16), posicionándose en el centro del bit
de inicio. El bit de inicio se considera válido si SIN continúa aún bajo en ese momento. La validación del bit
de inicio evita que un ruido espúreo en la línea sea confundido con un nuevo carácter.
El LCR tiene toda la información necesaria para la recepción: tamaño del carácter (5-8 bits), número de
bits de stop, si hay paridad o no... la información de estado que se genere será depositada en el LSR. Cuando
un carácter es transmitido desde el Registro de Desplazamiento de la Recepción (RSR) al Registro Buffer de
Recepción (RBR), el bit DR del LSR se activa. La CPU lee entonces el RBR, lo que hace bajar de nuevo
DR. Si el carácter no es leído antes de que el siguiente carácter que se está formando pase del RSR al RBR,
el bit OE (overrun) del LSR se activa. También se puede activar PE en el LSR si hay un error de paridad.
Finalmente, la circuitería que chequea la validez del bit de stop podría activar el bit FE del LSR en caso de
error.
El centro del bit de inicio se define como 7½ pulsos de reloj; si los datos que entran por SIN constituyen
una onda cuadrada simétrica, el centro de las celdas que contienen los bits se desviará a lo sumo un ±3.125%
del centro real, lo que deja un margen de error del 46.875%; el bit de inicio puede comenzar, como mucho, 1
ciclo de reloj (de los 16) antes de ser detectado.
El BRG genera las señales de reloj para el funcionamiento de la UART, permitiendo los ratios de
transferencia del estándar ANSI/CCITT. Se puede conectar un cristal a XTAL1 y XTAL2 ó una señal de
reloj a XTAL1. La salida -BAUDOUT puede excitar la línea XTAL1 de otro 8250.
La velocidad es determinada por los registros DLL y DLM almacenando un valor divisor de la frecuencia
del reloj conectado al 8250. El resultado debe ser 16 veces mayor que la frecuencia en baudios deseada, ya
que el 8250 utiliza 16 pulsos de reloj para cada bit. El siguiente cuadro resume los valores que hay que
asignar al divisor para lograr las frecuencias más usuales con los cristales más comunes.
8 de 12 12/10/00 19:21
EL PUERTO SERIE: UART 8250 file:///C|/librosVirtuales/UniversoDigital/1209.html
Tras dar corriente al 8250 hay que tenerlo unos 500 ns con MR alto para resetearlo. Un nivel alto en MR
provoca:
Tras el reset (MR llevado a estado bajo) el 8250 permanece en estado inactivo hasta ser programado. Un
reset hardware activa THRE y TEMT: cuando las interrupciones sean habilitadas, THRE provocará una.
Por software se puede forzar al 8250 a retornar a un estado totalmente conocido. Dicho reset consiste en
escribir el LCR, DLL y DLM, así como MCR. LSR y RBR deberían ser leídos antes de habilitar las
interrupciones para borrar cualquier información residual (datos o estado) de las operaciones anteriores.
El 8250 se programa a través de los registros de control LCR, IER, DLL, DLM y MCR. Aunque los
registros de control pueden ser escritos en cualquier orden, IER debe ser escrito al final porque controla la
habilitación de las interrupciones. Una vez que el 8250 ha sido programado, los registros pueden ser
actualizados en cualquier momento en que el 8250 no se encuentre enviando o recibiendo datos.
Los ordenadores compatibles pueden tener conectados, de manera normal, hasta 4 puertos serie,
nombrados COM1-COM4. En el área de datos de la BIOS (segmento 40h) y justo al principio de la misma,
hay 4 palabras con la dirección de memoria base de los puertos serie. A esta dirección de memoria base
habrá que sumar el desplazamiento relativo del número de registro a ser accedido.
El principal problema reside en que sólo están previstas 2 interrupciones para los puertos serie. Ello
implica que generalmente sólo 2 de los puertos podrán emplear interrupciones a un tiempo, debido a la
arquitectura del bus ISA. Generalmente COM1 y COM3 compartirán la IRQ4 (INT 0Ch) y COM2/COM4
la IRQ3 (INT 0Bh). Estas asignaciones pueden ser cambiadas por el usuario actuando sobre los switches de
configuración de las tarjetas (que en ocasiones permiten incluso elegir la IRQ5). Por tanto, no está de más
tener cuidado en los programas y permitir un cierto grado de configuración en estas cuestiones.
9 de 12 12/10/00 19:21
EL PUERTO SERIE: UART 8250 file:///C|/librosVirtuales/UniversoDigital/1209.html
El cuadro superior muestra los desplazamientos (offsets) que hay que sumar a la dirección E/S base del
puerto serie para acceder a sus registros. COM1 suele estar en 3F8h, COM2 en 2F8h, COM3 en 3E8h y
COM4 en 2E8h. Sin embargo, es mejor acceder a las variables de la BIOS para obtener la dirección.
La INT 14h de la BIOS se encarga de controlar el puerto serie. El trabajo del DOS a través de los
dispositivos COM1: (conocido también como AUX:) al COM4: se realiza también apoyándose en esta
interrupción. El comando MODE del sistema permite inicializar el puerto serie a alto nivel. Sin embargo,
tanto el DOS como la BIOS no permiten exceder los 9600 baudios, velocidad excesivamente baja para la
transmisión de datos entre dos ordenadores cercanos o el trabajo con un modem.
Nota: El bit OUT2 del MCR controla en los PC la salida de la línea INTRPT. Esto significa que si
dicho bit, por defecto inicializado a 0, es puesto a 1, las interrupciones del puerto serie quedan
inhibidas. El bit OUT1, por el contrario, debe estar a 1 por motivos no muy claros. También se podría
inhibir la INTRPT a través del 8259, por lo que este dato no es muy importante, con la excepción de
10 de 12 12/10/00 19:21
EL PUERTO SERIE: UART 8250 file:///C|/librosVirtuales/UniversoDigital/1209.html
evitar que una involuntaria e incorrecta asignación de OUT1 y OUT2 inhiba las interrupciones. La
ventaja de inhibir las interrupciones en el 8250 radica en la posibilidad de utilizar plenamente todas sus
funciones incluso en el modo de no interrupciones: el olvido del diseñador de incluir esta característica
obligó a IBM a utilizar para este fin OUT2. Realmente, el 8250 está concebido para ser utilizado por
medio de interrupciones, y hay quien duda incluso de la veracidad de la afirmación del fabricante
acerca del double buffering (buffers duplicados) que son muy aconsejables al trabajar sin
interrupciones.
El siguiente programa de ejemplo coloca el 8250 en modo lazo (LOOP) y seguidamente comienza a
transmitir datos de 8 bits (desde 0 hasta 255) comprobando que le llegan los mismos datos que envía y sin
que se produzcan errores. Se permite elegir el puerto deseado así como la velocidad de transmisión.
/*********************************************************************
* *
* 8250T.C 1.0 - UTILIDAD DE AUTODIAGNOSTICO DEL 8250 EN TURBO C *
* *
* (c) 1993 Ciriaco García de Celis. *
* *
*********************************************************************/
#include <dos.h>
#include <conio.h>
void error()
{
printf ("\r ¡¡Fallo del puerto serie!!\n");
exit (2);
}
void main()
{
unsigned com, base, divisor, dato, entrada, lsr;
11 de 12 12/10/00 19:21
EL PUERTO SERIE: UART 8250 file:///C|/librosVirtuales/UniversoDigital/1209.html
if (base==0) {
printf("\n ¡El COM elegido no existe para la BIOS!.\n");
exit (1);
}
if (dato!=entrada) error();
printf ("\rEnviado y recibido byte %d",dato);
}
if (!kbhit())
printf("\rAutodiagnóstico del 8250 en COM%d superado.\n", com);
else
{ getch(); printf("\rTecla pulsada - prueba abortada.\n");}
}
12 de 12 12/10/00 19:21
EL PUERTO DE LA IMPRESORA file:///C|/librosVirtuales/UniversoDigital/1210.html
La impresora se controla desde el DOS referenciándola como dispositivo LPT1 (PRN) ó LPT2. La BIOS
utiliza la INT 17h para los servicios de impresora. En ambos casos, el funcionamiento es realmente trivial y la
dificultad estriba en el modelo de impresora que se trate (IBM, Epson, HP-III, PostScript, etc.) de cara al
lenguaje que soporta. Eso no lo trataremos aquí, ya que todas las impresoras vienen acompañadas de un
manual técnico de programación (o en su defecto se puede adquirir opcionalmente). Lo que veremos a
continuación son los registros a bajo nivel del puerto paralelo, así como pistas para una utilización algo más
allá de la impresora: la comunicación entre ordenadores.
La dirección base del puerto paralelo en los ordenadores compatibles depende del tipo de adaptador que
incorporen. Las primeras máquinas traían un puerto paralelo en el adaptador de vídeo monocromo, cuya
dirección base es 3BCh. Sin embargo, otros adaptadores utilizan la dirección base 378h para LPT1 y 278h
para LPT2. Por fortuna, la BIOS tiene en el área de datos una tabla con las direcciones base de los 4
posibles puertos paralelos. Dicha tabla comienza en 40h:8 y consta de 1 palabra por puerto (a 0 si ese puerto
no existe). La asignación que realizan diversas BIOS puede ser un tanto discutible, pero si el usuario no ve
salir los datos por la impresora que desea, siempre puede cambiar los cables o configurar su programa...
Los registros de que consta el puerto paralelo son 3: el primero es el registro de datos, de 8 bits, ubicado
en la dirección base (3BCh, 378h, 278h, etc.). Este registro es de sólo escritura, para enviar los caracteres a
la impresora. El siguiente registro, de sólo lectura, es el registro de estado, inmediatamente a continuación
del anterior (3BDh, 379h, 279h). Finalmente, tras ellos hay un registro de sólo escritura, el registro de
control (en 3BEh, 37Ah, 27Ah). Aunque en los tres casos he indicado la dirección, hay que tener en cuenta
que lo correcto es consultar la variable de la BIOS y tomarla como punto de partida.
Los registros de estado y control están asociados a unas líneas físicas del puerto paralelo estándar, y
poseen un significado concreto que resumimos a continuación. En el valor pin se hace referencia al pin del
puerto paralelo del ordenador y al correspondiente en la impresora (ordenador/impresora). Las líneas o pines
que no aparecen aquí son las de datos (líneas 2 a la 9, conectadas también con las líneas 2 a la 9 del lado de
la impresora; las restantes están a masa).
Registro de estado:
- Bits 0-2: no utilizados.
- Bit 3: pin 15/32 (-ERROR). A 0 si hay un error gordo (a revisar los cables).
- Bit 4: pin 13/13 (SLCT). A 1 si la impresora está ON LINE.
- Bit 5: pin 12/12 (PE). A 1 si la impresora no tiene papel (PAPER ERROR).
- Bit 6: pin 10/10 (-ACK). A 0 si la impresora confirma la recepción del carácter.
- Bit 7: pin 11/11 (-BUSY). A 0 si la impresora está ocupada.
Registro de control:
- Bit 0: pin 1/1 (-STROBE). A 0 si hay un carácter en el registro de datos.
- Bit 1: pin 14/14 (-AUTO FEED). A 1 si la impresora debe saltar línea tras cada código 13 (CR).
- Bit 2: pin 16/31 (-INIT). A 0 para resetear la impresora.
- Bit 3: pin 17/36 (SLCT IN). A 1 para seleccionar la impresora (0 para OFF-LINE).
- Bit 4: no conectado al puerto de impresora. A 1 activa la interrupción de la impresora.
- Bits 5-7: no utilizados.
1 de 3 12/10/00 19:22
EL PUERTO DE LA IMPRESORA file:///C|/librosVirtuales/UniversoDigital/1210.html
La posibilidad de emplear interrupciones es realmente interesante: cuando la señal -ACK se pone a nivel 0
(esto es, se activa) viene una IRQ7 ó una IRQ5 (según cómo esté configurada la tarjeta). De todos modos,
habrá que mandar primero un carácter por el método tradicional para iniciar la transmisión. La BIOS, sin
embargo, no utiliza la interrupción de la impresora.
Ante todo dejar claro que cuando digamos 0 ó 1 nos referimos al valor del bit en el registro del PC,
olvidando ya cuestiones como el nivel al que son activas las señales, para evitar lios: los nombres de las
señales les tomaremos como referencia, sin considerar su polaridad. Para enviar un carácter, primero se le
coloca en el registro de datos. A continuación se pone a 0 en el registro de control el bit de STROBE. Este bit
debe estar muy poco tiempo activo, para evitar que la impresora lea dos veces el mismo carácter (del orden
de un microsegundo). Como la impresora no tiene una capacidad de aguante ilimitada, se puede defender
poniendo el bit de BUSY en el registro de estado a 0 para poder leer con tranquilidad el STROBE que le
llega. Cuando lo haya leído, pondrá un 0 en ACK para indicar que ya ha recibido el carácter.
Este es el esquema básico del envío de caracteres. Sin embargo, hay que tener en cuenta que la impresora
puede devolver ciertas condiciones de error, tanto leves (falta de papel) como más graves, como el caso de
ERROR. También el ordenador puede provocar ciertos efectos en la impresora, a través del registro de
control, como vimos anteriormente. Quizá el más curioso es el del AUTO FEED: ya se podían haber puesto
de acuerdo el primer día, resulta triste que además de perder horas configurando impresoras y programas,
hasta el propio puerto pueda meter las narices en el control del salto de línea...
Anteriormente hemos visto una descripción de patillas del puerto paralelo suficiente para que cualquiera se
pueda construir su propio cable centronics. De todas formas, estos cables afortunadamente se venden ya
construidos por un precio poco aceptable. Los que no se venden, aunque sí acompañan a ciertas aplicaciones
software e incluso hardware (como disqueteras externas vía puerto de impresora) permiten una comunicación
bidireccional. El truco consiste en utilizar las líneas del registro de estado para recibir datos, aunque esto limita
la transferencia a 5 bits (realmente 4, más otro para el protocolo de transferencia).
Se toman dos conectores centronic 25-pin machos. Se unen los pins de la siguiente forma:
El motivo de emplear esta asignación y no otra se debe a que es la ya utilizada por ciertas aplicaciones
comerciales, como LAPLINK. Es por razones de compatibilidad, para que no pase como con los saltos de
línea. La línea común (18) es masa, aunque valdría cualquier patilla entre la 18 y la 25; si se emplea un cable
de 10 hilos más malla, esta última es la más adecuada para hacer de masa.
Con este cable, para enviar datos se utilizan las líneas D0 a D4 del registro de datos y para recibirlos las 5
líneas útiles del registro de estado. Como D0-D1-D2-D3-D4 están conectados en este mismo orden a
2 de 3 12/10/00 19:22
EL PUERTO DE LA IMPRESORA file:///C|/librosVirtuales/UniversoDigital/1210.html
Si el cable no rebasa los 3 metros o poco más la transmisión será fiable, y además bastante rápida: 4 bits
en paralelo, a la velocidad que pueda alcanzar la CPU del ordenador más lento. No emplear el ensamblador
sería un acto imperdonable.
3 de 3 12/10/00 19:22
EL RATÓN file:///C|/librosVirtuales/UniversoDigital/1211.html
12.11. - EL RATÓN.
El ratón se controla normalmente a través de llamadas a la INT 33h. Existen toda suerte de funciones para
controlar su posición, el estado de los botones, el puntero que se visualiza... todas ellas son bastante intuitivas
y aptas para un programador en lenguajes de alto nivel. Aquí estudiaremos, sin embargo, el funcionamiento a
bajo nivel del ratón. En concreto, del ratón de Microsoft, el más extendido y con el que son compatibles casi
todos los demás (aunque sea accionando el correspondiente conmutador).
La mayoría de los ratones se conectan vía puerto serie a 1200 baudios, 7 bits y sin paridad. Para detectar
la presencia del ratón, hay que poner la línea DTR del puerto serie a 1. Al cabo de un rato, el ratón devuelve
el código ASCII de la letra M (¿será por lo de Mouse o por Microsoft?). Los controladores de Microsoft
son un poco estrictos en esta comprobación, y si el ratón no responde en unos márgenes de tiempo muy
concretos consideran que no existe, de ahí que en ocasiones haya que emplear otro controlador un poco más
flexible.
Los desplazamientos se toman en complemento a dos; como hay 8 bits por cada eje, el movimiento puede
oscilar en el rango +128 a -127. Hay además un bit por cada botón. De los 7 bits recibidos en cada
interrupción, el más significativo (bit 6) está a 1 en el primer envío y a 0 en los restantes, con objeto de evitar
malas interpretaciones de la secuencia si se pierde alguna interrupción por cualquier motivo. El formato
empleado para codificar la información es el siguiente:
El otro gran estándar de ratón, el Mouse Systems, permite trabajar hasta con tres botones. Estos ratones
envían (cuando están en modo Mouse) 5 bytes por cada evento. En el primero hay información sobre el
estado de los botones; los 4 siguientes parecen contener el desplazamiento relativo en los ejes X e Y. El
funcionamiento es, por tanto, similar, y al parecer quizá todavía con 7 bits. Curiosamente, al conmutar el
selector de modo (Microsoft-Mouse) aparece una secuencia de bytes un tanto especial, distinta según el
sentido de la conmutación, para ayudar al controlador de ratón a detectar el paso al nuevo protocolo con
objeto de poder adaptarse al mismo.
1 de 1 12/10/00 19:22
EL RELOJ DE TIEMPO REAL DEL AT: MOTOROLA MC146818 file:///C|/librosVirtuales/UniversoDigital/1212.html
El MC146818 incorpora un completo reloj con alarma, calendario, interrupción periódica programable,
generador de onda cuadrada y 64 bytes libres de RAM estática de bajo consumo. Los primeros 10 bytes de
esta RAM son empleados para gestionar la fecha y la hora y los 4 siguientes son registros (A, B, C y D); los
50 restantes quedan a disposición del usuario.
La salida SQW genera una onda cuadrada, cuya frecuencia es programable (útil
para alarmas). La línea -IRQ seencarga de solicitar las interrupciones periódicas si
están habilitadas. La línea de entrada -RESET reinicializa el integrado asignando
valores por defecto a ciertos bits de los registros B y C, aunque no afecta a la
fecha/hora ni a la memoria. La entrada PS debe mantenerse a nivel bajo cuando se
alimenta el chip hasta que la tensión se estabilice, poniéndose después en alto; esta
entrada está asociada al bit VRT del registro D que indica si el integrado está en
condiciones de operar. El bus bidireccional de direcciones y datos está multiplexado
(líneas AD0..AD7): en los flancos de bajada de la entrada de validación de direcciones
(línea AS) contiene direcciones, y datos en los flancos de subida de la entrada de
validación de datos (línea DS). La línea -R/-W indica si la operación es de entrada o
salida; -CE permite habilitar el chip o desconectarlo de los buses.
El cuadro de la derecha refleja la estructura de la memoria del MC146818. Los primeros 14 bytes son
empleados para la fecha y hora.
1 de 5 12/10/00 19:23
EL RELOJ DE TIEMPO REAL DEL AT: MOTOROLA MC146818 file:///C|/librosVirtuales/UniversoDigital/1212.html
El bit UIP (Update In Progress), de sólo lectura, se pone a 1 mientras se actualizan los primeros 14 bytes
de la memoria y poco tiempo antes de que comience dicha actualización. Antes de acceder a estos bytes, hay
que esperar a que el bit UIP se ponga a cero (si no lo estaba ya): con el bit UIP a 0, es seguro que en un
intervalo de al menos 244 microsegundos no se va a producir ninguna actualización, por lo que hay tiempo
suficiente para acceder (sin prisas, pero tampoco con pausas). La actualización dura 248 microsegundos
(1984 con relojes de 32768 Hz).
Los bits RS0..RS3, de selección de velocidad, definen la frecuencia de la onda cuadrada generada en
SQW y/o la de la interrupción periódica, como indica esta tabla:
+--------------------------------+--------------------------------+
| Reloj 1,048576 ó 4,194304 Mhz | Reloj de 32768 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| RS3 | RS2 | RS1 | RS0 | Velocidad INT | Frecuencia SQW | Velocidad INT | Frecuencia SQW |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 0 | 0 | 0 | 0 | (no actúa) | (nula) | (no actúa) | (nula) |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 0 | 0 | 0 | 1 | 30,517 µs | 32768 Hz | 3,90625 ms | 256 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 0 | 0 | 1 | 0 | 61,035 µs | 16384 Hz | 7,81250 ms | 128 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 0 | 0 | 1 | 1 | 122,070 µs | 8192 Hz | 122,070 µs | 8192 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 0 | 1 | 0 | 0 | 244,141 µs | 4096 Hz | 244,141 µs | 4096 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 0 | 1 | 0 | 1 | 488,281 µs | 2048 Hz | 488,281 µs | 2048 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 0 | 1 | 1 | 0 | 976,562 µs | 1024 Hz | 976,562 µs | 1024 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 0 | 1 | 1 | 1 | 1,953125 ms | 512 Hz | 1,953125 ms | 512 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 1 | 0 | 0 | 0 | 3,90625 ms | 256 Hz | 3,90625 ms | 256 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 1 | 0 | 0 | 1 | 7,8125 ms | 128 Hz | 7,8125 ms | 128 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 1 | 0 | 1 | 0 | 15,625 ms | 64 Hz | 15,625 ms | 64 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 1 | 0 | 1 | 1 | 31,25 ms | 32 Hz | 31,25 ms | 32 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 1 | 1 | 0 | 0 | 62,5 ms | 16 Hz | 62,5 ms | 16 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 1 | 1 | 0 | 1 | 125 ms | 8 Hz | 125 ms | 8 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 1 | 1 | 1 | 0 | 250 ms | 4 Hz | 250 ms | 4 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
| 1 | 1 | 1 | 1 | 500 ms | 2 Hz | 500 ms | 2 Hz |
+-----+-----+-----+-----+---------------+----------------+---------------+----------------+
REGISTRO B (lectura/escritura).
En este registro hay bits útiles, entre otros, para controlar la inicialización de la fecha y hora, para habilitar
o inhibir las diversas interrupciones y para establecer ciertas características de operación.
2 de 5 12/10/00 19:23
EL RELOJ DE TIEMPO REAL DEL AT: MOTOROLA MC146818 file:///C|/librosVirtuales/UniversoDigital/1212.html
El bit SET puede ser establecido a 1, con lo que cualquier ciclo de actualización de los primeros 14 bytes
de la RAM resulta abortado: de este modo, es factible proceder a inicializar la fecha y la hora sin el riesgo de
que se produzca en medio una actualización. Este bit no se ve afectado por la señal -RESET.
El bit PIE (Periodic Interrupt Enable) sirve para permitir la interrupción periódica cuando es puesto a 1;
tras una señal -RESET es puesto a 0. El bit AIE (Alarm Interrupt Enable) ha de estar a 1 para habilitar la
interrupción de alarma; también es puesto a cero tras un -RESET. El bit UIE (Update Interrupt Enable) sirve
para habilitar o inhibir la interrupción de fin de actualización, que se produciría tras cada actualización del
reloj; la señal -RESET baja el bit UIE. Por último, el bit SQWE (Square Wave Enable) permite habilitar o
inhibir la señal de onda cuadrada de la salida SQW; también es borrado ante una señal -RESET.
El bit DM (Data Mode) permite seleccionar datos en binario (1) o BCD (0) en los bytes de fecha y hora;
la señal -RESET no afecta a este bit. El bit 24/12 sirve para elegir entre el modo 12 horas del reloj (bit a 0) o
el de 24 (bit a 1): en el modo de 12 horas, el bit más significativo del byte de la hora estará activo para indicar
"PM". Si bit DSE está activo, el último domingo de abril la hora pasa de 1:59:59 AM a 3:00:00 AM; en el
último domingo de octubre pasa de 1:59:59 AM a 1:00:00 AM (sólo la primera vez, claro) para ajustarse al
cambio de hora oficial; este bit no es afectado por -RESET.
Este registro contiene bits que informan de las interrupciones que se producen. Permite identificar al
ordenador qué o cuáles interrupción(es) se ha(n) producido.
El bit IRQF (Interrupt ReQuest Flag) se activa cuando el bit PF y el PIE (registro B) están activos, o bien
cuando el bit AF y el AIE (registro B) están activos, o bien cuando UF y el bit UIE (registro B) están activos.
Es decir, IRQF se pone en alto cuando es necesario que se produzca una interrupción: la línea -IRQ se
encarga de pedirla entonces. Por su parte: PF (Periodic Flag), AF (Alarm Flag) y UF (Update Flag) indican si
es necesario que se produzca la interrupción correspondiente. Todos los bits de este registro son borrados
ante una señal -RESET, pero también ante una lectura por software del registro C.
Este registro contiene sólo el bit VRT (Valid RAM and Time). Este bit está a cero cuando la patilla PS
está a cero (PS se eleva a 1 cuando la tensión de alimentación es correcta). Por software, el bit VRT puede
ser puesto a 1 mediante una simple lectura del registro D (si la patilla PS=1), con objeto de indicar que la
fecha y hora establecidas son correctas; si fallara la alimentación, al caer la tensión en la patilla PS este bit
pasaría de nuevo a cero. VRT no es afectado por -RESET.
3 de 5 12/10/00 19:23
EL RELOJ DE TIEMPO REAL DEL AT: MOTOROLA MC146818 file:///C|/librosVirtuales/UniversoDigital/1212.html
FUNCIONAMIENTO DE LA ALARMA
La interrupción de alarma se produce todos los días cuando llega la hora en que ha sido programada y el
bit que permite esta interrupción está habilitado. Existe un método alternativo para programar la alarma,
basado en los códigos indiferentes almacenables en los bytes de la alarma. Un código indiferente es
cualquier valor comprendido entre 0C0h y 0FFh. Si la hora de alarma es un código indiferente, la alarma se
producirá cada hora. Si la hora y minuto de alarma son códigos indiferentes, ésta se producirá cada minuto.
Si tanto la hora como el minuto y segundo de la alarma son códigos indiferentes, la alarma se producirá cada
segundo.
Por defecto, la BIOS inicializa el chip para trabajar con un reloj de 32768 Hz y a un ritmo de 1024
interrupciones periódicas por segundo (cuando están habilitadas), al escribir el valor 26h en el registro A. De
la misma manera, el registro B se carga con 2 (modo 24 horas, datos en BCD y sin horario verano/invierno).
El MC146818 está diseñado para ser conectado a un bus multiplexado, por lo que la circuitería de apoyo
de los AT se encarga de gestionar la comunicación con el microprocesador, estableciendo dos puertos de
entrada/salida en las direcciones 70h y 71h. Para leer o escribir cualquier registro de la RAM CMOS, basta
con enviar al puerto 70h el número de registro y, a continuación, leer o escribir del puerto 71h. Entre los
accesos a ambos puertos debe mediar un tiempo mínimo; de lo contrario la operación fallará. En particular,
las últimas versiones de los compiladores de Borland no permiten acceder al reloj de tiempo real en la mayoría
de las máquinas a través de las funciones outportb() e inportb(). La razón es que esas funciones están en una
librería y es preciso llamarlas con paso de parámetros a través de la pila, lo que ralentiza excesivamente el
proceso. Desde el lenguaje ensamblador, nunca hay problemas, aunque como es costumbre es conveniente
insertar algún estado de espera (JMP SHORT $+2) entre dos operaciones E/S consecutivas, precaución
necesaria en los ordenadores más antiguos.
A nivel de interrupciones, la salida -IRQ del MC146818 está conectada a IRQ8 (INT 70h) a través del
segundo controlador de interrupciones (véase la documentación del mismo).
Desde la interrupción 1Ah, la BIOS implementa una serie de servicios para acceder al reloj de tiempo real,
incluyendo la posibilidad de programar la alarma (que invoque una INT 4Ah cuando llegue la hora). Las
funciones de retardo de la INT 15h se apoyan también en el reloj de tiempo real.
Conviene tener presente que es de vital importancia acceder a los primeros 14 bytes de la CMOS sólo si
el bit UIP del registro A (bit 7) está a cero. También es necesario poner a 1 el bit SET del registro B (bit 7)
antes de modificar dichos bytes, devolviéndolo a 1 después. No respetar este principio puede provocar la
lectura de fechas u horas incorrectas o una errónea asignación de valores. Para los demás bytes de la CMOS
no es necesario tomar esta precaución.
Como se dijo antes, los AT y superiores almacenan en los 50 ó 114 últimos bytes de RAM libres de la
CMOS información relativa a la configuración del sistema. Los bytes más importantes y comunes a todas las
máquinas se muestran a continuación.
4 de 5 12/10/00 19:23
EL RELOJ DE TIEMPO REAL DEL AT: MOTOROLA MC146818 file:///C|/librosVirtuales/UniversoDigital/1212.html
Diagnostics Status Byte. El bit 7 indica (si vale 1) que el MC146818 tiene un déficit de
corriente eléctrica. El bit 6 indica (si es 1) que el chechsum o suma de comprobación de la
CMOS ha fallado. El bit 5 indica (si vale 1) que la configuración del sistema es incorrecta
(no hay al menos una disquetera presente o el modo de vídeo de la configuración no
Byte 0Eh:
coincide con el detectado en el hardware). El bit 4 es puesto a 1 si el tamaño de la memoria
detectado no coincide con el indicado en la configuración. El bit 3 activo indica que el
adaptador o el disco fijo C: falló en la inicialización, siendo imposible botar desde él. El bit
2 activo indica que la hora del reloj es incorrecta. Los bits 1 y 0 están reservados.
Shutdown Status Byte. Los bits de este byte son asignados durante la inicialización del
Byte 0Fh:
sistema por parte de la BIOS, informando de su desarrollo (véase listado de la BIOS).
Diskette Drive Type Byte. Los bits 7..4 indican el tipo de la disquetera A y los bits 3..0
el tipo de la disquetera B. Los valores posibles son 0 (no existe esa disquetera), 1
Byte 10h:
(5¼-360K), 2 (5¼-1.2M), 3 (3½-720K), 4 (3½-1.44M) y 5 (3½-2.88M en BIOS AMI)
ó6 (3½-2.88M en BIOS IBM).
Byte 11h: Reservado.
Fixed Disk Type Byte. Los bits 7..4 indican el tipo del primer disco fijo y los bits 3..0 el
tipo del segundo. Existe una tabla definida por IBM cuando lanzó el AT con 14 tipos de
Byte 12h: disco; ninguno que se vende hoy en dia está en la tabla, por lo que es frecuente que estos
campos estén inicializados con el valor 1111b (ó 0 si no hay disco duro instalado) para
indicar simplemente la presencia de disco duro.
Byte 13h: Reservado.
Equipment Byte. Los bits 7 y 6 indican el número de disquetes instalados; los bits 5 y 4 el
tipo de adaptador de vídeo primario (00: EGA/VGA, 01: CGA-80, 10: CGA-40, 11:
Byte 14h:
MDA); los bits 3 y 2 no se emplean. El bit 1 indica si hay coprocesador aritmético y el bit 0
está activo para confirmar que hay disqueteras.
Low and High Base Memory Bytes. El 15h es el bajo y el 16h el alto. Entre ambos
Byte 15h-16h: forman una palabra de 16 bits que indica la cantidad de memoria convencional (típicamente
640 Kb).
Low and High Memory Expansion Bytes. El 17h es el bajo y el 18h el alto. Entre
Byte 17h-18h: ambos forman una palabra de 16 bits que indica la cantidad de memoria extendida, en
Kbytes.
Número del primer disco duro. Número de identificación que la BIOS asigna al primer
Byte 19h:
disco duro instalado.
Byte 1Ah-2Dh: Reservados.
Checksum. El 2Eh es el alto y el 2Fh el bajo. Entre ambos forman una palabra de 16 bytes
Byte 2Eh-2Fh:
que constituye el checksum o suma de comprobación de los bytes 10h-20h.
Low and High Memory Expansion Bytes. Habitualmente es el mismo valor que el
Byte 30h-31h: almacenado en los bytes 17h y 18h; esta variable refleja sólo la memoria extendida ubicada
por encima del primer megabyte que detecta la BIOS en el momento de arrancar.
Byte 32h: Date Century Byte. Valor BCD del siglo actual-1. Para 1992, por ejemplo, es 19h.
Information Flag. El bit 7 indica si está instalada la vieja opción de ampliación de 128 Kb
(hasta los 640 Kb) del IBM AT original: hoy en día suele estar siempre activo. El bit 6 es
Byte 33h:
empleado por el programa SETUP para eliminar el mensaje inicial al usuario tras el primer
SETUP. Los demás bits están reservados.
Byte 34h-3Fh: Reservados.
5 de 5 12/10/00 19:23
EL ENSAMBLADOR Y EL LENGUAJE C file:///C|/librosVirtuales/UniversoDigital/13.html
El lenguaje C es sin duda el más apropiado para la programación de sistemas, pudiendo sustituir al
ensamblador en muchos casos. Sin embargo, hay ocasiones en que es necesario acceder a un nivel más bajo
por razones de operatividad e incluso de necesidad (programas residentes que economicen memoria,
algoritmos rápidos para operaciones críticas, etc.). Es entonces cuando resulta evidente la necesidad de poder
emplear el ensamblador y el C a la vez.
Para comprender este capítulo, basta tener unos conocimientos razonables de C estándar. Aquí se
explicarán las funciones de librería necesarias para acceder al más bajo nivel, así como la manera de integrar
el ensamblador y el C.
A continuación veremos algunas funciones, macros y estructuras de la librería DOS.H del Turbo C.
int inp (int puerto); /* leer del puerto E/S una palabra (16 bits) */
int inport (int puerto); /* leer del puerto E/S una palabra (16 bits) */
unsigned char inportb (int puerto); /* leer del puerto E/S un byte (8 bits) */
int outp (int puerto, int valor); /* enviar al puerto E/S una palabra (16 bits) */
void outport (int puerto, int valor); /* enviar al puerto E/S una palabra (16 bits) */
void outportb (int puerto, unsigned char valor); /* enviar al puerto E/S un byte (8 bits) */
Aunque pueden parecer demasiadas, algunas son idénticas (caso de inp() e inport()) y otras se diferencian
sólo ligeramente en el tipo de los datos devueltos, lo cual es irrelevante si se tiene en cuenta que el dato
devuelto es descartado (caso de outp() y outport()). En general, lo normal es emplear inport() e inportb() para
la entrada, así como outport() y outportb() para la salida. Por ejemplo, para enviar el EOI al final de una
interrupción hardware se puede ejecutar: outportb(0x20, 0x20);
Las funciones peek(), peekb(), poke() y pokeb() tienen una utilidad evidente de cara a consultar y
modificar las posiciones de memoria. Cuando se necesita saber el segmento y/o el offset de una variable del
programa, las macros FP_OFF y FP_SEG devuelven dicha información. Por último, con MK_FP es posible
asignar una dirección de memoria absoluta a un puntero far. Por ejemplo, si se declara una variable:
se puede hacer que apunte a la memoria de vídeo del modo texto de los adaptadores de color con:
y después se podría limpiar la pantalla con un bucle: for (i=0; i<4000; i++) *pantalla_color++=0;
1 de 8 12/10/00 19:23
EL ENSAMBLADOR Y EL LENGUAJE C file:///C|/librosVirtuales/UniversoDigital/13.html
Para llamar a las interrupciones es conveniente conocer antes ciertas estructuras y uniones.
struct WORDREGS {
unsigned int ax, bx, cx, dx, si, di, cflag, flags;
};
struct BYTEREGS {
unsigned char al, ah, bl, bh, cl, ch, dl, dh;
};
union REGS {
struct WORDREGS x;
struct BYTEREGS h;
};
struct SREGS {
unsigned int es; unsigned int cs; unsigned int ss; unsigned int ds;
};
struct REGPACK {
unsigned r_ax, r_bx, r_cx, r_dx;
unsigned r_bp, r_si, r_di, r_ds, r_es, r_flags;
};
Las dos primeras funciones se basan en la declaración de dos uniones: una para entrada y otra para salida,
que simbolizan los valores iniciales (antes de llamar a la interrupción) y finales (tras la llamada) en los registros.
Si se desea que la misma unión que indica los valores iniciales devuelva los finales, se puede indicar por
duplicado:
regs.h.ah = 0;
regs.h.al = 0x13; /* VGA 320x200 - 256 colores */
int86 (0x10, ®s, ®s); /* cambiar modo de vídeo */
La diferencia entre int86() e int86x() reside en que la última permite trabajar con los registros de segmento
(la estructura SREGS se puede inicializar con los valores que tienen que tener los registros de segmento antes
de llamar a la interrupción; a la vuelta, dicha estructura habrá sido modificada para indicar el valor devuelto en
los registros de segmento tras la interrupción).
Hay quien prefiere trabajar con REGPACK, que con una sola estructura permite también operar con los
registros de segmento y la emplea tanto para enviar como para recibir los resultados. El inconveniente, poco
relevante, es que sólo admite registros de 16 bits, lo que suele obligar a hacer desplazamientos y forzar el
empleo de máscaras para trabajar con las mitades necesarias:
2 de 8 12/10/00 19:23
EL ENSAMBLADOR Y EL LENGUAJE C file:///C|/librosVirtuales/UniversoDigital/13.html
La función getvect() devuelve un puntero con la dirección del vector de interrupción indicado. La función
setvect() permite desviar un vector hacia la rutina de tipo interrupt que se indica. Interrupt es una palabra clave
del Turbo C que será explicada en el futuro. Por ahora, baste el siguiente programa de ejemplo:
int main()
{
vieja_rutina = getvect (5); /* almacenar dirección de INT 5 (activada con Print Screen) */
setvect (5, nueva_rutina); /* desviar INT 5 a nuestra propia rutina de control */
. . .
. . . /* resto del programa */
. . .
setvect (5, vieja_rutina); /* restaurar rutina inicial de INT 5 */
}
La función anterior, basada en el servicio 31h del DOS, permite a un programa realizado en C quedar
residente en la memoria. Además del código de retorno, es preciso indicar el tamaño del área residente (en
párrafos). Es difícil determinar con precisión la memoria que ocupa un programa en C. Sin embargo, en
muchos casos la siguiente fórmula puede ser válida:
En los casos en que no lo sea, se le puede hacer que vuelva a serlo aumentando el tamaño del área de
seguridad (que en los programas menos conflictivos será 0). Tanto _psp como _SS y _SP están definidas ya
por el compilador, por lo que la línea anterior es perfectamente válida (sin más) al final de un programa.
De estas variables predefinidas, las más útiles son quizá las que devuelven la versión del DOS, lo que
ahorra el esfuerzo que supone averiguarlo llamando al DOS o empleando la función de librería
correspondiente. También es útil _psp, que permite un acceso a este área del programa de manera inmediata.
3 de 8 12/10/00 19:23
EL ENSAMBLADOR Y EL LENGUAJE C file:///C|/librosVirtuales/UniversoDigital/13.html
Por medio de _ _emit_ _() se puede colocar código máquina de manera directa dentro del programa en
C. No es conveniente hacerlo así porque así, ya que alterar directamente los registros de la CPU acabará
alterando el funcionamiento esperado del compilador y haciendo fallar el programa. Sin embargo, en un
procedimiento dedicado exclusivamente a almacenar código inline (en línea), es seguro este método, sobre
todo si se tiene cuidado de no alterar los registros SI y DI (empleados muy a menudo por el compilador como
variables de tipo register). Por medio de geninterrupt() se puede llamar directamente a una interrupción:
geninterrupt (interr) es exactamente lo mismo que _ _emit_ _(0xCD, interr) ya que 0xCD es el código de
operación de INT. Por ejemplo, para volcar la pantalla por impresora se puede ejecutar geninterrupt(5). Con
los símbolos _AX, _AL, _AH, _BX, _BL, _BH, _CX, _CL, _CH, _DX, _DL, _DH, _SI, _DI, _BP, _SP,
_CS, _DS, _ES, _SS y _FLAGS se puede acceder directamente a los registros de la CPU. Hay que tomar
también precauciones para evitar efectos laterales (una asignación tipo _DS=0x40 no afectará sólo a DS).
Los modelos de memoria constituyen las diversas maneras de acceder a la memoria por parte de los
compiladores de C. En el caso del Turbo C se pueden distinguir los siguientes:
TINY: Se emplea en los programas donde es preciso apurar el consumo de memoria hasta el último byte.
Los 4 registros de segmento (CS, DS, ES, SS) están asignados a la misma dirección, por lo que existe un
total de 64 Kb donde se mezclan código, datos y pila. Los programas de este tipo pueden convertirse a
formato COM.
SMALL: Se utiliza en aplicaciones pequeñas. Los segmentos de código y datos son diferentes y no se
solapan. Por ello, hay 64 kb para código y otros 64 Kb a repartir entre datos y pila.
Segmentos Punteros
Modelo Código Datos Pila Código Datos
Tiny 64 Kb near near
Small 64 Kb 64 Kb near near
Medium 1 Mb 64 Kb far near
Compact 64 Kb 1 Mb near far
Large 1 Mb 1 Mb far far
1 Mb
Huge 1 Mb far far
(Bloques > 64 Kb)
MEDIUM: Este modelo es ideal para programas largos que no manejan demasiados datos. Se utilizan
punteros largos para el código (que puede extenderse hasta 1 Mb) y cortos para los datos: la pila y los datos
juntos no pueden exceder de 64 Kb.
4 de 8 12/10/00 19:23
EL ENSAMBLADOR Y EL LENGUAJE C file:///C|/librosVirtuales/UniversoDigital/13.html
COMPACT: Al contrario que el anterior, este modelo es el apropiado para los programas pequeños que
emplean muchos datos. Por ello, el programa no puede exceder de 64 Kb aunque los datos que controla
pueden alcanzar el Mb, ya que los punteros de datos son de tipo far por defecto.
LARGE: Empleado en las aplicaciones grandes y también por los programadores de sistemas que no tienen
paciencia para andar forzando continuamente el tipo de los punteros (para rebasar el límite de 64 Kb). Tanto
los datos como el código pueden alcanzar el Mb, aunque no se admite que los datos estáticos ocupen más de
64 Kb. Este modo es el que menos problemas da para manejar la memoria, no siendo quizá tan lento y
pesado como indica el fabricante.
HUGE: Similar al anterior, pero con algunas ventajas: por un lado, todos los punteros son normalizados
automáticamente y se admiten datos estáticos de más de 64 Kb. Por otro, y gracias a esto último, es factible
manipular bloques de datos de más de 64 Kb cada uno, ya que los segmentos de los punteros se actualizan
correctamente. Sin embargo, este modelo es el más costoso en tiempo de ejecución de los programas.
LA SENTENCIA ASM
La sentencia asm permite incluir código ensamblador dentro del programa C, utilizando los mnemónicos
normales del ensamblador. Sin embargo, el uso de esta posibilidad está más o menos limitado según la versión
del compilador. En Turbo C 2.0, los programas que utilizan este método es necesario salir a la línea de
comandos para compilarlos con el tradicional compilador de línea, lo cual resulta poco atractivo. En Turbo
C++ 1.0, se puede configurar adecuadamente el compilador para que localice el Turbo Assembler y lo utilice
automáticamente para ensamblar, sin necesidad de salir del entorno integrado. Sin embargo, es a partir del
Borland C++ cuando se puede trabajar a gusto: en concreto, la versión Borland C++ 2.0 permite ensamblar
sin rodeos código ensamblador incluido dentro del listado C. El único inconveniente es la limitación del
hardware disponible: para un PC/XT, el Turbo C 2.0 es el único compilador aceptablemente rápido. Sin
embargo, en un 286 es más recomendable el Turbo C++, mientras que en un 386 modesto (o incluso en un
286 potente) resulta más interesante emplear el Borland C++ 2.0: las versiones 3.X de este compilador son
las más adecuadas para un 486 o superior (bajo DOS).
main()
{
int dato1, dato2, resultado;
Como se ve en el ejemplo, los registros utilizados son convenientemente preservados para no alterar el
valor que puedan tener en ese momento (importante para el compilador). También puede observarse lo fácil
que resulta acceder a las variables. Ah, cuidado con BP: el registro BP es empleado mucho por el compilador
5 de 8 12/10/00 19:23
EL ENSAMBLADOR Y EL LENGUAJE C file:///C|/librosVirtuales/UniversoDigital/13.html
y no conviene tocarlo (ni siquiera guardándolo en la pila). De hecho, la instrucción MOV CX,DATO1 será
compilada como MOV CX,[BP-algo] al ser una variable local de main().
Esta es la única sintaxis soportada por el Turbo C 2.0; sin embargo, en las versiones más modernas del
compilador se admiten las llaves '{' y '}' para agrupar varias sentencias asm:
asm {
push ax; push cx;
mov cx,dato1
mov ax,0h }
mult: asm {
add ax,dato2
loop mult
mov resultado,ax
pop cx; pop ax;
}
SUBRUTINAS EN ENSAMBLADOR
Cuando las rutinas a incluir son excesivamente largas, resulta más conveniente escribirlas como ficheros
independientes y ensamblarlas por separado, incluyéndolas en un fichero de proyecto (*.PRJ) seleccionable
en los menús del compilador.
Para escribir este tipo de rutinas hay que respetar las mismas definiciones de segmentos que realiza el
compilador. Hoy en día existe algo más de flexibilidad; sin embargo, aquí se expone el método general para
mezclar código de ensamblador con C.
int variable;
extern dato;
extern funcion();
main()
{
int a=21930; char b='Z';
La variable variable es una variable global del programa a la que no se asigna valor alguno en el momento
de definirla. Tanto a como b son variables locales del procedimiento main() y son asignadas con un cierto
valor inicial; funcion() no aparece por ningún sitio, ya que será codificada en ensamblador en un fichero
independiente. A dicha función se le pasan 3 parámetros. La manera de hacerlo es colocándolos en la pila
(empezando por el último y acabando por el primero). Por ello, el compilador meterá primero en la pila el
valor 1234h y luego el 5678h (necesita dos palabras de pila porque es un dato de tipo long). Luego coloca en
la pila el carácter almacenado en la variable b: como los valores que se apilan son siempre de 16 bits, la parte
alta está a 0. Finalmente, deposita el dato entero a. Seguidamente, llama a la función funcion() con un CALL
que puede ser de dos tipos: corto (CALL/RET en el mismo segmento) o largo (CALL/RETF entre distintos
segmentos). Esta llamada a la función, por tanto, provoca un almacenamiento adicional de 2 bytes (modelos
TINY, SMALL y COMPACT) o 4 (en los restantes modelos de memoria, que podríamos llamar largos).
El esqueleto de la subrutina en ensamblador que ha de recibir esos datos y, tras procesarlos, devolver un
6 de 8 12/10/00 19:23
EL ENSAMBLADOR Y EL LENGUAJE C file:///C|/librosVirtuales/UniversoDigital/13.html
_TEXT ENDS
END
Como se puede observar, se respetan ciertas convenciones en cuanto a los nombres de los segmentos y
grupos. En el segmento _DATA se definen las variables inicializadas (las que tienen un valor inicial): _dato
podría haber sido accedida perfectamente desde el programa en C, ya que es declarada como pública. Por
otro lado, en el segmento _BSS se definen o declaran las variables que no son inicializadas con un valor inicial
(como es el caso de la variable _variable del programa C, que fue definida simplemente como int variable:
en el listado ensamblador se la declara como externa ya que está definida en el programa C). El compilador
de C precede siempre de un subrayado a todas las variables y funciones cuando compila, motivo por el cual
hay que hacer lo propio en el listado ensamblador. Al tratarse de un modelo de memoria pequeño, _BSS y
_DATA están agrupados. En el segmento _TEXT se almacena el código, es decir, las funciones definidas: en
nuestro caso, sólo una (el procedimiento _funcion). Como es de tipo NEAR, sólo se podrá emplear con
programas C compilados en un modelo de memoria TINY, SMALL o COMPACT (para los demás modelos
hay que poner FAR en lugar de NEAR). Esta función de ejemplo en ensamblador no utiliza ninguna variable,
pero tanto _variable (la variable del programa C) como, por supuesto, _info o _dato son plenamente
7 de 8 12/10/00 19:23
EL ENSAMBLADOR Y EL LENGUAJE C file:///C|/librosVirtuales/UniversoDigital/13.html
accesibles.
A la hora de acceder a las variables, hay que tener en cuenta el modelo de memoria: como no emplea más
de 64 Kb para código (modelos TINY, SMALL o COMPACT), el compilador sólo ha colocado en la pila
el offset de la dirección de retorno (registro IP). Nosotros apilamos después BP (ya que lo vamos a manchar)
por lo que el último dato que apiló el programa C antes de llamar a la rutina en ensamblador habrá de ser
accedido en [BP+4]. La ventaja de inicializar BP es que luego se pueden introducir datos en la pila sin perder
la posibilidad de acceder a los parámetros de la rutina que llama. Si el procedimiento fuera de tipo FAR
(modelos MEDIUM, LARGE y HUGE), todos los accesos indexados sobre la pila se incrementarían en dos
unidades (por ejemplo, [BP+6] en vez de [BP+4] para acceder a la variable a) debido a que también se
habría almacenado CS en la llamada. Como se puede observar, la rutina no preserva ni restaura todos los
registros que va a emplear: sólo es necesario devolver intactos DS, SS, BP y (por si se emplean variables
register) SI y DI; los demás registros pueden ser libremente alterados. Como la función es de tipo entero,
devuelve el resultado en AX; si fuera de tipo long lo devolvería en DX:AX.
El modelo de memoria también cuenta en los parámetros que son pasados a la rutina en ensamblador
cuando no son pasados por valor (es decir, cuando se pasan punteros). En el ejemplo, podríamos haber
pasado un puntero que podría ser de tipo corto (para cargarlo en BX, por ejemplo, y efectuar operaciones
tipo [BX]). Sin embargo, si se pasan punteros a variables de tipo far (o si se emplea un modelo de memoria
COMPACT, LARGE o HUGE) es necesario cargar la dirección con una instrucción LES de 32 bits.
Esta rutina de ejemplo en ensamblador es sólo demostrativa, por lo que no debe el lector intentar
encontrar alguna utilidad práctica, de ahí que incluso ni siquiera emplee todas las variables que define.
Evidentemente, cuando el programa C retome el control, habrá de equilibrar la pila sumando 8 unidades a
SP (para compensar las 4 palabras que apiló antes de llamar a la función en ensamblador). En general, el
funcionamiento general del C en las llamadas a procedimientos se basa en apilar los parámetros empezando
por el último y llamar al procedimiento: éste, a su vez, preserva BP y lo hace apuntar a dichos parámetros (a
los que accederá con [BP+desp]); a continuación, le resta a SP una cantidad suficiente para que quepan en la
pila todas las variables locales (a las que accederá con [BP-desp]); antes de retornar restaura el valor inicial
de SP y recupera BP de la pila. Es entonces cuando el procedimiento que llamó, al recuperar el control, se
encarga de sumar el valor adecuado a SP para equilibrar la pila (devolverla al estado previo a la introducción
de los parámetros).
Desde las rutinas en ensamblador también se puede llamar a las funciones del compilador, apilando
adecuadamente los parámetros en la pila (empezando por el último) y haciendo un CALL al nombre de la
función precedido de un subrayado: no olvidar nunca al final sumar a SP la cantidad necesaria para
reequilibrar la pila.
AVISO IMPORTANTE: Algo a tener en cuenta es que el compilador de C es sensible a las mayúsculas:
funcion() no es lo mismo que FUNCION(). Por ello, al ensamblar, es obligatorio emplear como mínimo el
parámetro /mx del ensamblador con objeto de que no ponga todos los símbolos automáticamente en
mayúsculas (con /mx se respetan las minúsculas en los símbolos globales y con /ml en todos los símbolos). En
MASM 6.0, el equivalente a /mx es /Cx y la opción /Cp se corresponde con /ml.
8 de 8 12/10/00 19:23
Apéndice I - MAPA DE MEMORIA file:///C|/librosVirtuales/UniversoDigital/ap01.html
La memoria convencional en las máquinas más potentes está casi enteramente a disposición del usuario,
aunque en los PC/XT el núcleo del sistema operativo ocupa un buen fragmento de la misma (unos 45 Kb). En
los 286 y superiores, el núcleo del sistema se ubica en el HMA (primeros 64 Kb de la memoria extendida).
La memoria de vídeo está dividida en dos bloques de 64 Kb: el ubicado entre A0000-AFFFF lo emplean la
EGA, VGA y SuperVga en modo gráfico. El segundo, entre B0000-BFFFF es usado por la CGA y la
Hércules, también en modo gráfico. En modo de texto, el adaptador monocromo de IBM (primeros PC sin
gráficos) emplea 4 Kb a partir de B0000; el adaptador de color utiliza 16 kb a partir de B8000. Las
EGA/VGA soportan ambos tipos de pantallas de texto; las tarjetas «bifrecuencia» también. Entre C0000 y
CFFFF puede estar ubicada la BIOS de la VGA (normalmente entre C0000 y C7FFF) o las BIOS de discos
duros de XT, el resto de este segmento (en 386) es memoria superior donde cargar los programas residentes
con HILOAD (o LOADHIGH en MS-DOS) que así no ocupan memoria convencional. Los segmentos de
64 Kb que comienzan en D0000 y E0000 pueden contener extensiones de la BIOS (normalmente discos
duros de XT) o también memoria superior. Uno de los dos puede ser empleado para la «ventana» de
memoria expandida EMS (PC/XT/AT), normalmente el primero. En F0000 está colocada la ROM BIOS
(aunque en PC/XT es frecuente que sólo estén ocupados los últimos 8 Kb; en los AT suele ubicarse un
programa SETUP que permite al usuario definir la configuración de la máquina). Por encima, los primeros 64
Kb de memoria extendida son accesibles incluso desde el modo real del 286 y 386, siempre que la línea de
direcciones A20 esté habilitada (lo que sucede a partir del DR-DOS y del MS-DOS 5.0). Para ello, con
CS=FFFF se puede acceder a 65520 bytes (casi 64Kb) de RAM adicionales donde se puede cargar el
núcleo del sistema operativo y quizá algún que otro programa residente (DR-DOS 6.0). El resto de la
memoria en máquinas 286/386 es memoria extendida, que puede ser direccionada por controladores de disco
virtual o cachés de disco duro, e incluso -en 386- puede ser convertida por software en memoria expandida
paginable en el segmento (dentro del primer mega) habilitado al efecto.
1 de 1 12/10/00 19:24
Apéndice II - TABLA DE INTERRUPCIONES DEL SISTEMA file:///C|/librosVirtuales/UniversoDigital/ap02.html
1 de 2 12/10/00 19:25
Apéndice II - TABLA DE INTERRUPCIONES DEL SISTEMA file:///C|/librosVirtuales/UniversoDigital/ap02.html
2 de 2 12/10/00 19:25
Apéndice III - TABLA DE VARIABLES DE LA BIOS file:///C|/librosVirtuales/UniversoDigital/ap03.html
La siguiente información procede del fichero MEMORY.LST de Robin Walker, incluido en el mismo
paquete del INTERRUP.LST. La información está actualizada mayoritariamente al 24/8/92. Se han eliminado
aspectos demasiado técnicos sobre las tarjetas EGA/VGA y alguna información sobre hardware no estándar.
Las variables de la BIOS comienzan en el segmento de memoria 40h, justo después de la tabla de
vectores de interrupción. Son empleadas por los programas de control ubicados en las memorias ROM del
ordenador. En general, siempre es preferible utilizar una función de la BIOS que modificar directamente sus
variables, aunque a veces ello no es posible o puede no resultar conveniente. Los campos colocados entre
llaves ('{' y '}') no están documentados por IBM y podrían cambiar en el futuro. Los códigos entre corchetes
indican a qué máquinas o configuraciones, en exclusiva, se aplica la información.
1 de 5 12/10/00 19:26
Apéndice III - TABLA DE VARIABLES DE LA BIOS file:///C|/librosVirtuales/UniversoDigital/ap03.html
2 de 5 12/10/00 19:26
Apéndice III - TABLA DE VARIABLES DE LA BIOS file:///C|/librosVirtuales/UniversoDigital/ap03.html
50h 16 BYTEs Posición del cursor (columna, fila) para las 8 páginas
60h WORD Tipo de cursor, compatible 6845, byte alto=línea inicial, bajo=final
62h BYTE Página activa
63h WORD Dirección E/S base del controlador de vídeo: color=03D4h, mono=03B4h
65h BYTE Valor actual del registro de selección de modo 03D8h/03B8h
66h BYTE Valor actual almacenado en el registro de paleta de la CGA 03D9h
67h DWORD Punto de retorno al modo real tras ciertos resets del POST
6Bh BYTE Ultima interrupción no esperada por el POST
6Ch DWORD Tics de reloj (1/18,2 segundos) ocurridos desde medianoche
70h BYTE Flag de medianoche, <> 0 si el contador pasa de las 23:59:59.99
71h BYTE Banderín de Ctrl-Break: bit 7=1
72h WORD Banderín de reset del POST:
= 1234h si no realizar chequeo de memoria (arranque caliente)
= 4321h [solo PS/2 MCA] si preservar la memoria al arrancar
= 5678h [PC Convertible] sistema detenido
= 9ABCh [PC Convertible] test de fabricación
= ABCDh [PC Convertible] bucle del POST
= 64h modo «Burn-in»
74h BYTE Estado de la última operación del disco fijo: {salvo unidades ESDI}
00h no hubo error
01h función solicitada incorrecta
02h no encontrada marca de direcciones
03h error de protección contra escritura
04h sector no encontrado
05h fallo en el reset
07h fallo en la actividad de los parámetros del disco
08h el DMA se ha desbordado
09h alineamiento de datos incorrecto para el DMA
0Ah detectado banderín de sector erróneo
0Bh detectada pista errónea
0Dh número incorrecto de sectores para el formateo
0Eh detectada marca de direcciones de control
0Fh nivel de arbitrio del DMA fuera de rango
10h error ECC o CRC incorregible
11h error de datos ECC corregido
20h fallo general del controlador
40h fallo en el posicionamiento del cabezal
80h fuera de tiempo, no responde
AAh disco no preparado
BBh error indefinido
CCh fallo de escritura en el disco seleccionado
E0h el registro de errores es cero
FFh fallo de sentido
75h BYTE Disco fijo: número de discos fijos
76h BYTE Disco fijo: byte de control {IBM lo documenta sólo en el XT}
77h BYTE Disco fijo: offset del puerto E/S {IBM lo documenta sólo en el XT}
78h 3 BYTEs Contadores de «time-out» para los puertos paralelos 1-3
7Bh BYTE Contador «time-out» para puerto paralelo 4 [máquinas no PS]
bit 5 = 1 si especificación de DMA virtual soportada [PS] (ver INT 4B)
7Ch 4 BYTEs Contadores de «time-out» para los puertos serie 1-4
80h WORD Offset de inicio del buffer del teclado respecto al segmento 40h
(normalmente 1Eh)
82h WORD Offset del fin del buffer del teclado+1 respecto al segmento 40h
(normalmente 3Eh)
3 de 5 12/10/00 19:26
Apéndice III - TABLA DE VARIABLES DE LA BIOS file:///C|/librosVirtuales/UniversoDigital/ap03.html
4 de 5 12/10/00 19:26
Apéndice III - TABLA DE VARIABLES DE LA BIOS file:///C|/librosVirtuales/UniversoDigital/ap03.html
5 de 5 12/10/00 19:26
Apéndice IV - PUERTOS DE ENTRADA Y SALIDA file:///C|/librosVirtuales/UniversoDigital/ap04.html
1 de 2 12/10/00 19:26
Apéndice IV - PUERTOS DE ENTRADA Y SALIDA file:///C|/librosVirtuales/UniversoDigital/ap04.html
2 de 2 12/10/00 19:26
Apéndice V - CÓDIGOS DE RASTREO DEL TECLADO. CÓDIGOS SECUNDARIOS. file:///C|/librosVirtuales/UniversoDigital/ap05.html
Las teclas marcadas con 'Ex' son exclusivas de teclados expandidos; generan los mismos códigos de
rastreo que sus correspondientes teclas «no expandidas», aunque precedidos de un código de rastreo
adicional 0E0h como mínimo, por lo general (consultar el apartado 5.2 del capítulo 7 para más detalles).
Códigos secundarios.
A continuación se listan los códigos secundarios. Estos se producen al pulsar ciertas combinaciones
especiales de teclas, a las que el controlador de INT 9 responde colocando un código ASCII 0 en el buffer, a
menudo junto al código de rastreo, para identificarlas; las teclas expandidas provocan frecuentemente la
inserción de un ASCII 0E0h o bien 0F0h. Estos códigos secundarios son el valor devuelto en AH por las
funciones 0, 1, 10h y 11h de la BIOS, cuando éstas devuelven un carácter ASCII 0 ó 0E0h en AL.
Ha de tenerse en cuenta que la BIOS modifica en ocasiones el valor leído del buffer del teclado, aunque
en la siguiente tabla hay pautas para detectar esta circunstancia si fuera necesario. En primer lugar, cuando se
invoca a la BIOS con las funciones 0 y 1, éste se encarga de simular las teclas normales con las expandidas,
así como de ocultar las combinaciones exclusivamente expandidas. Aquellos códigos precedidos de (*) en la
tabla son ocultados por la BIOS (como si no se hubiera pulsado las teclas) al emplear las funciones 0 y 1,
sacándolos del buffer e ignorándolos. En concreto, estos códigos son almacenados con un código ASCII
0F0h en el buffer del teclado. Lógicamente, para las funciones 10h y 11h sí existen, aunque la BIOS devuelve
un 0 en AL (y no un 0F0h). A los códigos precedidos por (#) les sucede lo mismo: sólo existen para las
funciones 10h y 11h, al emplear dichas funciones la BIOS devuelve en AL el valor 0 (el auténtico contenido
del buffer en esta ocasión, sin necesidad de transformarlo). Por último, los códigos precedidos por (@)
existen tanto para las funciones 0 y 1 como para la 10h y la 11h: la ventaja de usar las dos últimas es que
devuelven en AL el auténtico código ASCII del buffer (0E0h), permitiendo diferenciar entre la pulsación de
una tecla normal y su correspondiente expandida.
En general, quien no desee complicarse la vida con este galimatías (debido a una evidente falta de
previsión en el diseño del primer teclado) puede limitarse a emplear las combinaciones normales (las no
marcadas con #, # ni *). Por otra parte, para emplear las combinaciones señaladas con (#), (@) o (*) hay
que asegurarse previamente de que la BIOS soporta teclado expandido (véase capítulo 7, apartado 5.3).
1 de 2 12/10/00 19:27
Apéndice V - CÓDIGOS DE RASTREO DEL TECLADO. CÓDIGOS SECUNDARIOS. file:///C|/librosVirtuales/UniversoDigital/ap05.html
Para diferenciar las teclas repetidas, en la tabla siguiente, las teclas entrecomilladas se suponen expandidas
o, en su defecto, ubicadas en el teclado numérico. Por ejemplo: "5" es el 5 del teclado numérico, "<-" es el
cursor izquierdo expandido y <- a secas el normal (esto es, la tecla 4 del teclado numérico con Num Lock
inactivo). Se emplea la notación anglosajona: Ctrl (Control), Alt (Alt o AltGr), Shift (Mays), Ins (Insert), Del
(Supr), Home (Inicio), End (Fin), PgUp (RePág), PgDn (AvPág).
Excepciones:
Hay un par de teclas que sin tener un código ASCII 0, 0E0h ni 0F0h reciben un tratamiento especial por
parte de la BIOS, que provoca que el código secundario no sea el de rastreo acostumbrado: el Intro del
teclado numérico genera un código ASCII 0Dh, como cabría esperar, pero su código secundario es 0E0h; lo
mismo sucede con el '/' del teclado numérico. Las funciones 0 y 1 de la BIOS traducen este 0E0h al valor
correspondiente a la tecla Intro principal y al '-' del teclado principal (tecla que ocupa la posición del '/' en los
teclados norteamericanos), para compatibilizar con los teclados no expandidos.
2 de 2 12/10/00 19:27
Apéndice VI - TAMAÑOS Y TIEMPOS DE EJECUCIÓN DE LAS INSTRUCCIONES file:///C|/librosVirtuales/UniversoDigital/ap06.html
En la tabla de esta página se listan las instrucciones del ensamblador por orden alfabético, indicándose el
número de bytes consumidos al ser ensambladas así como los tiempos teóricos de ejecución en 8088, 286,
386 y 486. Estos tiempos son teóricos y no deberían ser utilizados para temporizaciones exactas. Por otra
parte son diferentes de un procesador a otro. Los tiempos se expresan en estados de máquina (1 MHz
equivale a 1.000.000 de estados o ciclos de reloj) estando la capacidad de ejecución de instrucciones
lógicamente en función de los MHz del equipo que se trate. Estos tiempos se aplican suponiendo que se
cumplen las siguientes hipótesis:
Evidentemente, es casi imposible que los tiempos teóricos sean los reales, teniendo en cuenta todos estos
factores. Cuanto menos potente es la máquina, mucho más lentos son los tiempos reales; por el contrario, en
ordenadores con caché y procesador avanzado ¡los tiempos efectivos pueden ser en ocasiones mejores que
los teóricos!. Por ejemplo, el 486 emplea ya la tecnología pipeline, lo que le permite simultanear la ejecución
de una instrucción con la decodificación de la siguiente y la lectura de memoria de la posterior así como
almacenar el resultado de la anterior. Esto, con las lógicas limitaciones de un procesador CISC, permite en la
práctica ejecutar un alto número de instrucciones en un solo ciclo (cada una de ellas, claro). Por tanto, para lo
que sí sirven las tablas es para decidir qué instrucciones emplear en ciertos procesos en que el tiempo de
ejecución o la memoria consumida son críticos, especialmente en las máquinas menos potentes. Como
muestra de lo sumamente teóricos que son estos tiempos, a continuación se listan dos rutinas con las que he
probado experimentalmente los tiempos de ejecución en diversos microprocesadores. Ambas rutinas constan
de un bucle que se repite cierto número de veces; mientras tanto las interrupciones están inhibidas, por lo que
se cronometran a mano:
Ciclos teóricos
RutinaA: CLI
MOV AX,1000h
bucle: XOR CX,CX 3 2 2 1
repite: LOOP repite 17 ó 5 8+m ó 4 (m=2) 11+m (m=2) 6 ó 2
DEC AX 2 2 2 1
JNZ bucle 16 ó 4 7+m ó 3 (m=2) 7+m ó 3 (m=2) 3 ó 1
STI
RutinaB: CLI
XOR
CX,CX
bucle1: MOV
AX,BX 2 2 2 1
bucle2: MOV
AX,BX 2 2 2 1
... . . . .
bucle16384: MOV AX,BX 2 2 2 .
DEC CX 2 2 2 1
JNZ fin 16 ó 4 7+m ó 3 7+m ó 3 (m=1) 3 ó 1
JMP bucle1 15 7+m 7+m (m=2) 3
fin: STI
Por ejemplo, la rutina B ejecuta 16384 instrucciones del tipo MOV AX,BX (2 ciclos cada una) así como
1 de 8 12/10/00 19:28
Apéndice VI - TAMAÑOS Y TIEMPOS DE EJECUCIÓN DE LAS INSTRUCCIONES file:///C|/librosVirtuales/UniversoDigital/ap06.html
un decremento (2 ciclos) un salto que no se realiza -salvo al final del todo- (4 ciclos en 8088) y otro salto
absoluto (15 ciclos en 8088). Se emplea este rodeo ya que los saltos condicionales, como conocerá el lector,
sólo pueden desviar algo más de 100 bytes el flujo del programa (y este bucle ocupa nada menos que 32
Kb). En total, 32787 ciclos que, repetidos 65536 veces, suponen 2.148.728.832 ciclos. Con un 8088
corriendo a 8 MHz (8 millones de ciclos) cabría esperar una demora de 268,59 segundos. Sin embargo, mi
reloj de pulsera dice que son nada menos que ¡1194!, unas 4,44 veces más de lo que los tiempos teóricos de
Intel sugieren. De hecho, esto implica que cada MOV tarda casi 9 ciclos reales en un 8088, y no 2. Sin
embargo, en el caso de la rutina A apenas hay diferencia entre el tiempo teórico y el real: el tiempo que
emplea la instrucción LOOP es bastante alto en comparación con lo que se tarda en traer dicha instrucción de
la memoria, por lo que la diferencia porcentual se reduce notablemente.
RUTINA A RUTINA B
Teórico Efectivo Teórico Efectivo
8088-4.77 956,71 1014,00 450,47 1946,00
V20-8 570,43 623,30 268,59 1194,00
286-12 223,70 254,00 179,02 188,25
386-25* 139,59 135,20 85,93 93,50
486-25* 64,42 75,50 42,96 69,10
El 8088, bastante menos potente que el 286, varía enormemente la velocidad de ejecución de las
instrucciones en función del modo de direccionamiento, hay que añadir además dos ciclos de reloj en este
procesador cuando se usa un prefijo de registro de segmento. En la siguiente tabla se indica el número de
ciclos de reloj adicionales que deben considerarse en el 8086/8088 para calcular la dirección de memoria
efectiva (EA, Efective Address) en la tabla de tiempos, según el tipo de direccionamiento:
Los datos entre paréntesis en el 8088 indican el tiempo empleado por las palabras de 16 bits, fuera del
paréntesis hacen referencia a 8 bits (los 8086 y superiores no son más lentos con datos de 16 que con los de
8 bits, siempre lógicamente que éstos estén en una posición de memoria par). Aunque el 286 y 386 no
penalizan tanto los modos de direccionamiento complejos, a los tiempos marcados con (#) hay que añadir un
ciclo si en el offset participan tres elementos (ej., BP+DI+desp). La letra {m} se refiere al número de bytes
totales de la siguiente instrucción que se va a ejecutar. Cuando aparecen dos opciones en las instrucciones de
salto condicional, el menor tiempo de ejecución se verifica cuando el salto no se realiza. Todas las
instrucciones específicas de 386 ocupan, bajo DOS, un byte más de lo que indican las tablas debido a que se
utiliza un prefijo para forzar el modo 32 bit en segmentos de 16. En los tiempos del 386, los datos entre
2 de 8 12/10/00 19:28
Apéndice VI - TAMAÑOS Y TIEMPOS DE EJECUCIÓN DE LAS INSTRUCCIONES file:///C|/librosVirtuales/UniversoDigital/ap06.html
paréntesis se aplican cuando la CPU está en modo virtual 86; en general, los tiempos de ejecución
corresponden al modo real (en modo protegido, podrían variar).
3 de 8 12/10/00 19:28
Apéndice VI - TAMAÑOS Y TIEMPOS DE EJECUCIÓN DE LAS INSTRUCCIONES file:///C|/librosVirtuales/UniversoDigital/ap06.html
4 de 8 12/10/00 19:28
Apéndice VI - TAMAÑOS Y TIEMPOS DE EJECUCIÓN DE LAS INSTRUCCIONES file:///C|/librosVirtuales/UniversoDigital/ap06.html
5 de 8 12/10/00 19:28
Apéndice VI - TAMAÑOS Y TIEMPOS DE EJECUCIÓN DE LAS INSTRUCCIONES file:///C|/librosVirtuales/UniversoDigital/ap06.html
6 de 8 12/10/00 19:28
Apéndice VI - TAMAÑOS Y TIEMPOS DE EJECUCIÓN DE LAS INSTRUCCIONES file:///C|/librosVirtuales/UniversoDigital/ap06.html
7 de 8 12/10/00 19:28
Apéndice VI - TAMAÑOS Y TIEMPOS DE EJECUCIÓN DE LAS INSTRUCCIONES file:///C|/librosVirtuales/UniversoDigital/ap06.html
8 de 8 12/10/00 19:28
Apéndice VII - SEÑALES DEL SLOT DE EXPANSIÓN ISA file:///C|/librosVirtuales/UniversoDigital/ap07.html
El slot de expansión del XT, de 8 bits, consta de 62 terminales en un conector hembra, 31 por cada cara.
La cara A es la de los componentes; por la B sólo hay pistas. Viendo las tarjetas por arriba (por la cara de
componentes) y con los conectores exteriores a la derecha, la numeración comienza de derecha a izquierda.
En los AT el slot de 16 bits consta de 36 terminales más, distribuidos en grupos de 18 en dos nuevas caras (C
y D). La mayoría de las máquinas AT poseen slots de 8 y 16 bits, aunque lo ideal sería que todos fueran de
16 (en los de 16 bits se pueden insertar también tarjetas de 8 bits, dejando la otra mitad al aire).
Las señales en la parte de 8 bits son idénticas en XT y AT, si se exceptúa la línea IRQ2 que en los AT es
realmente IRQ9 (IRQ2 es empleada en la placa base para conectar en cascada el segundo controlador de
interrupciones; por compatibilidad con los XT, cuando se produce una IRQ9 -normalmente una INT 71h- se
invoca por software la INT 0Ah).
En el siguiente esquema, las líneas activas en alto van precedidas de un signo (+); las activas en estado
lógico bajo (-). Los símbolos I (Input) y O (Output) indican si las líneas son de entrada, salida o
bidireccionales.
1 de 3 12/10/00 19:28
Apéndice VII - SEÑALES DEL SLOT DE EXPANSIÓN ISA file:///C|/librosVirtuales/UniversoDigital/ap07.html
El slot de expansión de los PC contiene básicamente las principales señales del 8086 demultiplexadas, así
como otras de interrupciones, DMA, control de E/S, etc. Las señales presentes en el slot de expansión de 8
bits son:
(Oscilator) Señal de reloj de casi 70 ns (14,31818 MHz) que está la mitad del período
OSC:
en estado alto y la otra mitad en estado bajo.
(Address Latch Enable) Indica en su flanco de bajada que el latch de direcciones se ha
ALE:
cargado con una dirección válida procedente del microprocesador.
TC: (Terminal Count) Indica el final de la cuenta en algún canal de DMA.
(DMA Request) Líneas asíncronas de petición de DMA (1 mayor prioridad, 3 menor).
DRQ1-DRQ3:
Esta línea debe activarse hasta que DACK (activo a nivel bajo) suba.
(DMA Acknowledge) Indica que ha sido atendida la petición de DMA y que debe
DACK1-DACK3:
bajarse el correspondiente DRQ.
(Interrupt request) Indica una petición de interrupción (2 mayor prioridad, 7 menor). La
IRQ2-IRQ7:
señal debe mantenerse activa hasta que la interrupción acabe de ser procesada.
(Input/Output Read) Señala al dispositivo de E/S que se va a leer el bus de datos; esta
IOR:
línea la controla la CPU o el DMA.
(Input/Output Write) Señala al dispositivo de E/S que se va a escribir en el bus de
IOW:
datos; esta línea la controla también la CPU o el DMA.
(Memory Read) Indica que se va a efectuar una lectura de la memoria en la dirección
MEMR:
contenida en el bus de direcciones. La activa la CPU o el DMA.
(Memory Write) Indica que se va a efectuar una escritura en memoria en la dirección
MEMW:
contenida en el bus de direcciones. La activa la CPU o el DMA.
(Reset drive) Avisa de que el sistema está en proceso de reinicialización, para que
RESET DRV: todos los dispositivos conectados se inicialicen. Se activa en el flanco de bajada de la
señal del reloj.
(Address) Bus de direcciones común a la memoria y a la E/S, controlado por la CPU o
A0-A19:
el DMA.
D0-D7: (Data) Bus de datos que conecta el microprocesador y los demás componentes.
(Address Enable) Valida la dirección almacenada en A0-A19. Esto permite inhibir la
AEN: CPU y los demás dispositivos, pudiendo el DMA tomar el control. Los periféricos
deben decodificar la dirección comprobando que AEN está en estado bajo.
(I/O Channel Ready) Esta línea se pone momentáneamente en estado bajo por los
periféricos lentos (no durante más de 10 ciclos de reloj) cuando detectan una dirección
I/O CH RDY:
válida en una operación de E/S, con objeto de poder sincronizarse con la CPU, que
genera estados de espera.
(I/O Channel Check) Indica si se ha producido un error de paridad en la memoria o en
I/O CH CK:
los dispositivos E/S.
En los AT, las líneas adicionales completan fundamentalmente la nueva longitud de los buses de datos y
direcciones, permitiendo acceder también al resto del nuevo hardware:
2 de 3 12/10/00 19:28
Apéndice VII - SEÑALES DEL SLOT DE EXPANSIÓN ISA file:///C|/librosVirtuales/UniversoDigital/ap07.html
3 de 3 12/10/00 19:28
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
Lógicamente, las funciones del DOS y la BIOS podrían llenar varios libros de mayor tamaño que éste. Por
ello, se listarán exclusivamente las funciones que se utilizan en los programas ejemplo y en las explicaciones.
Toda la información ha sido obtenida del INTERRUPT.LST, en su mayoría de la versión 39 del mismo (ver
bibliografía), en este libro se recoge menos de un 8% de las líneas de dicho fichero. Todas las funciones
recogidas en el INTERRUPT tienen el siguiente formato:
--------V-1000-------------------------------
INT 10 - VIDEO - SET VIDEO MODE
AH = 00h
AL = mode (see below)
Return: AL = video mode flag (Phoenix BIOS)
20h mode > 7
30h modes 0-5 and 7
3Fh mode 6
AL = CRT controller mode byte (Phoenix 386 BIOS v1.10)
Al principio de la función, en la línea de guiones, suele haber uno, dos o tres números hexadecimales de 8
bits (pegados unos a otros) que indican, por orden de aparición: número de la interrupción, valor de llamada
en AH, valor de llamada en AL. En el ejemplo superior se trata de la INT 10h, a la que hay que llamar con
AH=0. Si fueran necesarios más valores en otros registros normalmente se indicará de manera explícita en la
cabecera. Esta cabecera es útil, ya que un fichero de varios megas no es operativo consultarlo con TYPE (y
muchos editores de texto no pueden cargarlo): lo normal es emplear una de esas pequeñas utilidades para ver
ficheros de texto, que permiten moverse arriba y abajo con las teclas de los cursores (como README.COM
que acompaña a los compiladores de Borland): esos programas suelen tener opciones de búsqueda de texto;
de esta manera, buscando la cadena "-210A" se podría encontrar rápidamente la función 0Ah del DOS (INT
21h).
--------!---FLAGS-------------------------------------------------
The use of -> instead of = signifies that the indicated register or register
pair contains a pointer to the specified item, rather than the item itself.
One or more letters may follow the interrupt number; they have the following
meanings: U - undocumented function, u - partially documented function,
P - available only in protected mode, R - available only in real or V86 mode,
C - callout or callback (usually hooked rather than called),
O - obsolete (no longer present in current versions)
--------!---CATEGORIES--------------------------------------------
The ninth column of the divider line preceding an entry usually contains a
classification code (the entry has not been classified if that character is
a dash). The codes currently in use are:
A - applications, a - access software (screen readers, etc),
B - BIOS, b - vendor-specific BIOS extensions,
C - CPU-generated, c - caches/spoolers,
D - DOS kernel, d - disk I/O enhancements,
E - DOS extenders, e - electronic mail, F - FAX,
f - file manipulation, G - debuggers/debugging tools,
H - hardware, h - vendor-specific hardware,
I - IBM workstation/terminal emulators, i - system info/monitoring
J - Japanese, j - joke programs,
K - keyboard enhancers, k - file compression,
1 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
l - shells/command interpreters,
M - mouse/pointing device, m - memory management,
N - network, n - non-traditional input devices,
O - other operating systems,
P - printer enhancements, p - power management,
Q - DESQview/TopView and Quarterdeck programs,
R - remote control/file access, r - runtime support,
S - serial I/O, s - sound/speech,
T - DOS-based task switchers/multitaskers, t - TSR libraries
U - resident utilities, u - emulators,
V - video, v - virus/antivirus,
W - MS Windows, X - expansion bus BIOSes,
y - security, * - reserved (and not otherwise classified)
--------C-00------------------------------------------------------
INT 00 - CPU-generated - DIVIDE ERROR
Desc: generated if the divisor of a DIV or IDIV instruction is zero or the
quotient overflows the result register; DX and AX will be unchanged.
Notes: on an 8086/8088, the return address points to the following instruction
on an 80286+, the return address points to the divide instruction
an 8086/8088 will generate this interrupt if the result of a division
is 80h (byte) or 8000h (word)
SeeAlso: INT 04
--------C-01------------------------------------------------------
INT 01 - CPU-generated - SINGLE STEP
Desc: generated after each instruction if TF (trap flag) is set; TF is
cleared on invoking the single-step interrupt handler
Notes: interrupts are prioritized such that external interrupts are invoked
after the INT 01 pushes CS:IP/FLAGS and clears TF, but before the
first instruction of the handler executes
used by debuggers for single-instruction execution tracing, such as
MS-DOS DEBUG's T command
SeeAlso: INT 03
--------H-02------------------------------------------------------
INT 02 - external hardware - NON-MASKABLE INTERRUPT
Desc: generated by the CPU when the input to the NMI pin is asserted
Notes: return address points to start of interrupted instruction on 80286+
on the 80286+, further NMIs are disabled until the next IRET
instruction, but one additional NMI is remembered by the hardware
and will be serviced after the IRET instruction reenables NMIs
maskable interrupts may interrupt the NMI handler if interrupts are
enabled
although the Intel documentation states that this interrupt is
typically used for power-failure procedures, it has many other uses
on IBM-compatible machines:
Memory parity error: all except Jr, CONV, and some machines
without memory parity
Breakout switch on hardware debuggers
Coprocessor interrupt: all except Jr and CONV
Keyboard interrupt: Jr, CONV
I/O channel check: CONV, PS50+
Disk-controller power-on request: CONV
System suspend: CONV
Real-time clock: CONV
System watch-dog timer, time-out interrupt: PS50+
DMA timer time-out interrupt: PS50+
Low battery: HP 95LX
Module pulled: HP 95LX
--------C-03------------------------------------------------------
INT 03 - CPU-generated - BREAKPOINT
Desc: generated by the one-byte breakpoint instruction (opcode CCh)
Notes: used by debuggers to implement breakpoints, such as MS-DOS DEBUG's G
2 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
command
also used by Turbo Pascal versions 1,2,3 when {$U+} specified
return address points to byte following the breakpoint instruction
SeeAlso: INT 01
--------C-04------------------------------------------------------
INT 04 - CPU-generated - INTO DETECTED OVERFLOW
Desc: the INTO instruction will generate this interrupt if OF (Overflow Flag)
is set; otherwise, INTO is effectively a NOP
Note: may be used for convenient overflow testing (to prevent errors from
propagating) instead of JO or a JNO/JMP combination
SeeAlso: INT 00
--------B-05------------------------------------------------------
INT 05 - PRINT SCREEN
Desc: dump the current text screen to the first printer
Notes: normally invoked by the INT 09 handler when PrtSc key is pressed, but
may be invoked directly by applications
byte at 0050h:0000h contains status used by default handler
00h not active
01h PrtSc in progress
FFh last PrtSc encountered error
default handler is at F000h:FF54h in IBM PC and 100%-compatible BIOSes
SeeAlso: INT 10/AH=12h/BL=20h
--------C-05------------------------------------------------------
INT 05 - CPU-generated (80186+) - BOUND RANGE EXCEEDED
Desc: generated by BOUND instruction when the value to be tested is less than
the indicated lower bound or greater than the indicated upper bound.
Note: returning from this interrupt re-executes the failing BOUND instruction
--------C-06------------------------------------------------------
INT 06 - CPU-generated (80286+) - INVALID OPCODE
Desc: this interrupt is generated when the CPU attempts to execute an
invalid opcode (most protected-mode instructions are considered
invalid in real mode) or a BOUND, LDS, LES, or LIDT instruction
which specifies a register rather than a memory address
Notes: return address points to beginning of invalid instruction
with proper programming, this interrupt may be used to emulate
instructions which do not exist; many 386 BIOSes emulate the 80286
undocumented LOADALL instruction which was removed from the 80386+
generated by the 80386+ when the LOCK prefix is used with instructions
other than BTS, BTR, BTC, XCHG, XADD (486), CMPXCHG (486), INC, DEC,
NOT, NEG, ADD, ADC, SUB, SBB, AND, OR, or XOR, or any instruction
not accessing memory.
SeeAlso: INT 0C"CPU",INT 0D"CPU"
--------C-07------------------------------------------------------
INT 07 - CPU-generated (80286+) - PROCESSOR EXTENSION NOT AVAILABLE
Desc: this interrupt is automatically called if a coprocessor instruction is
encountered when no coprocessor is installed
Note: can be used to emulate a numeric coprocessor in software
SeeAlso: INT 09"MATH UNIT PROTECTION"
--------H-08------------------------------------------------------
INT 08 - IRQ0 - SYSTEM TIMER
Desc: generated 18.2 times per second by channel 0 of the 8254 system timer,
this interrupt is used to keep the time-of-day clock updated
Notes: programs which need to be invoked regularly should use INT 1C unless
they need to reprogram the timer while still keeping the time-of-day
clock running at the proper rate
default handler is at F000h:FEA5h in IBM PC and 100%-compatible BIOSes
may be masked by setting bit 0 on I/O port 21h
SeeAlso: INT 1C,INT 4A,INT 50"DESQview",INT 58"DoubleDOS",INT 70,INT 78"GO32"
SeeAlso: INT D8"Screen Thief"
--------C-08------------------------------------------------------
3 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
4 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
5 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
Notes: the TOPS and PCnet adapters use this interrupt request line as an
alternate
on PS/2s, COM2 through COM8 share this interrupt; on many PC's, COM4
shares this interrupt
may be masked by setting bit 3 on I/O port 21h
SeeAlso: INT 0C"COM1",INT 53"DESQview",INT 5B"DoubleDOS",INT 7B"GO32"
--------H-0C------------------------------------------------------
INT 0C - IRQ4 - SERIAL COMMUNICATIONS (COM1)
Desc: automatically asserted by the UART when COM1 needs attention, if the
UART has been programmed to generate interrupts
BUG: this vector is modified but not restored by Direct Access v4.0, and
may be left dangling by other programs written with the same version
of compiled BASIC
Notes: on many PC's, COM3 shares this interrupt
may be masked by setting bit 4 on I/O port 21h
SeeAlso: INT 0B"COM2",INT 54"DESQview",INT 5C"DoubleDOS",INT 7C"GO32"
--------H-0D------------------------------------------------------
INT 0D - IRQ5 - FIXED DISK (PC,XT), LPT2 (AT), reserved (PS/2)
Notes: under DESQview, only the INT 15h vector and BASIC segment address (the
word at 0000h:0510h) may be assumed to be valid for the handler's
process
the Tandy 1000, 1000A, and 1000HD use IRQ2 for the hard disk; the
1000EX, HX, RLX, RLX-HD, RLX-B, RLX-HD-B use IRQ5 instead; the
1000RL, RL-HD, SL, SL/2, TL, TL/2, and TL/3 are jumper-selectable
for either IRQ2 or IRQ5 (default IRQ5); the 1000SX and TX are
DIP-switch selectable for IRQ2 or IRQ5 (default IRQ2); the RSX and
RSX-HD use IRQ14. Tandy systems which use IRQ2 for the hard disk
interrupt use IRQ5 for vertical retrace.
may be masked by setting bit 5 on I/O port 21h
SeeAlso: INT 0E"IRQ6",INT 0F"IRQ7",INT 55"DESQview",INT 5D"DoubleDOS"
SeeAlso: INT 7D"GO32"
--------H-0E------------------------------------------------------
INT 0E - IRQ6 - DISKETTE CONTROLLER
Desc: this interrupt is generated by the floppy disk controller on
completion of an operation
Notes: default handler is at F000h:EF57h in IBM PC and 100%-compatible BIOSes
may be masked by setting bit 6 on I/O port 21h
SeeAlso: INT 0D"IRQ5",INT 56"DESQview",INT 5E"DoubleDOS",INT 7E"GO32"
--------H-0F------------------------------------------------------
INT 0F - IRQ7 - PARALLEL PRINTER
Desc: this interrupt is generated by the LPT1 printer adapter when the
printer becomes ready
Notes: most printer adapters do not reliably generate this interrupt
the 8259 interrupt controller generates an interrupt corresponding to
IRQ7 when an error condition occurs
SeeAlso: INT 0D"LPT2",INT 57"DESQview",INT 5F"DoubleDOS",INT 7F"GO32"
--------V-1000----------------------------------------------------
INT 10 - VIDEO - SET VIDEO MODE
AH = 00h
AL = mode (see below)
Return: AL = video mode flag (Phoenix BIOS)
20h mode > 7
30h modes 0-5 and 7
3Fh mode 6
AL = CRT controller mode byte (Phoenix 386 BIOS v1.10)
Desc: specify the display mode for the currently active display adapter
Notes: IBM standard modes do not clear the screen if the high bit of AL is set
(EGA or higher only)
Values for video mode:
text/ text pixel pixel colors disply scrn system
grph resol box resoltn pages addr
6 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
7 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
8 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
AH
09h =
AL =
character to display
BH =
page number (00h to number of pages - 1) (see AH=00h)
BL =
attribute (text mode) or color (graphics mode)
if bit 7 set in graphics mode, character is xor'ed onto screen
CX = number of times to write character
Notes: all characters are displayed, including CR, LF, and BS
replication count in CX may produce an unpredictable result in graphics
modes if it is greater than the number of positions remaining in the
current row
SeeAlso: AH=08h,AH=0Ah,AH=4Bh"GRAFIX",INT 17/AH=60h,INT 1F,INT 43,INT 44
--------V-100C----------------------------------------------------
INT 10 - VIDEO - WRITE GRAPHICS PIXEL
AH = 0Ch
BH = page number
AL = pixel color (if bit 7 set, value is xor'ed onto screen)
CX = column
DX = row
Desc: set a single pixel on the display in graphics modes
Notes: valid only in graphics modes
BH is ignored if the current video mode supports only one page
SeeAlso: AH=0Dh,AH=46h
--------V-100E----------------------------------------------------
INT 10 - VIDEO - TELETYPE OUTPUT
AH = 0Eh
AL = character to write
BH = page number
BL = foreground color (graphics modes only)
Desc: display a character on the screen, advancing the cursor and scrolling
the screen as necessary
Notes: characters 07h (BEL), 08h (BS), 0Ah (LF), and 0Dh (CR) are interpreted
and do the expected things
IBM PC ROMs dated 4/24/81 and 10/19/81 require that BH be the same as
the current active page
SeeAlso: AH=02h,AH=0Ah
--------V-100F----------------------------------------------------
INT 10 - VIDEO - GET CURRENT VIDEO MODE
AH = 0Fh
Return: AH = number of character columns
AL = display mode (see AH=00h)
BH = active page (see AH=05h)
Notes: if mode was set with bit 7 set ("no blanking"), the returned mode will
also have bit 7 set
EGA, VGA, and UltraVision return either AL=03h (color) or AL=07h
(monochrome) in all extended-row text modes
SeeAlso: AH=00h,AH=05h,AX=10F2h/BL=00h,AX=1130h,AX=CD04h
--------V-101002--------------------------------------------------
INT 10 - VIDEO - SET ALL PALETTE REGISTERS (PCjr,Tandy,EGA,VGA)
AX = 1002h
ES:DX -> palette register list
Note: under UltraVision, the palette locking status (see AX=CD01h)
determines the outcome
SeeAlso: AX=1000h,AX=1001h,AX=1009h,AX=CD01h
9 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
10 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
11 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
(Table 0067)
Values for VESA SuperVGA memory model type:
00h text
01h CGA graphics
02h HGC graphics
03h 16-color (EGA) graphics
04h packed pixel graphics
05h "sequ 256" (non-chain 4) graphics
06h direct color (HiColor, 24-bit color)
07h YUV (luminance-chrominance, also called YIQ)
08h-0Fh reserved for VESA
10h-FFh OEM memory models
--------V-104F02-----------------------------
INT 10 - VESA SuperVGA BIOS - SET SuperVGA VIDEO MODE
AX = 4F02h
BX = mode
bit 15 set means don't clear video memory
Return: AL = 4Fh function supported
AH = status
00h successful
01h failed
SeeAlso: AX=4E03h,AX=4F01h,AX=4F03h
(Table 0068)
Values for VESA video mode:
00h-FFh OEM video modes (see #0009 at AH=00h)
100h 640x400x256
101h 640x480x256
102h 800x600x16
103h 800x600x256
104h 1024x768x16
105h 1024x768x256
106h 1280x1024x16
107h 1280x1024x256
108h 80x60 text
109h 132x25 text
10Ah 132x43 text
10Bh 132x50 text
10Ch 132x60 text
---VBE v1.2---
10Dh 320x200x32K
10Eh 320x200x64K
10Fh 320x200x16M
110h 640x480x32K
111h 640x480x64K
112h 640x480x16M
113h 800x600x32K
114h 800x600x64K
115h 800x600x16M
116h 1024x768x32K
117h 1024x768x64K
118h 1024x768x16M
12 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
119h 1280x1024x32K
11Ah 1280x1024x64K
11Bh 1280x1024x16M
Index: video modes
(Table 0069)
Values for S3 OEM video mode:
201h 640x480x256
202h 800x600x16
203h 800x600x256
204h 1024x768x16
205h 1024x768x256
206h 1280x960x16
208h 1280x1024x16
211h 640x480x64K (Diamond Stealth 24)
212h 640x480x16M (Diamond Stealth 24)
301h 640x480x32K
Note: these modes are only available on video cards using S3's VESA driver
Index: video modes
--------V-104F03-----------------------------
INT 10 - VESA SuperVGA BIOS - GET CURRENT VIDEO MODE
AX = 4F03h
Return: AL = 4Fh function supported
AH = status
00h successful
BX = video mode (see #0068,#0069)
01h failed
SeeAlso: AH=0Fh,AX=4E04h,AX=4F02h
--------V-104F04-----------------------------
INT 10 - VESA SuperVGA BIOS - SAVE/RESTORE SuperVGA VIDEO STATE
AX = 4F04h
DL = subfunction
00h get state buffer size
Return: BX = number of 64-byte blocks needed
01h save video states
ES:BX -> buffer
02h restore video states
ES:BX -> buffer
CX = states to save/restore (see #0070)
Return: AL = 4Fh function supported
AH = status
00h successful
01h failed
13 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
14 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
15 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
16 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
DH = head number
DL = drive number (bit 7 set for hard disk)
ES:BX -> data buffer
Return: CF set on error
CF clear if successful
AH = status (see AH=01h)
AL = number of sectors transferred
Notes: errors on a floppy may be due to the motor failing to spin up quickly
enough; the write should be retried at least three times, resetting
the disk with AH=00h between attempts
the IBM AT BIOS and many other BIOSes use only the low four bits of
DH (head number) since the WD-1003 controller which is the standard
AT controller (and the controller that IDE emulates) only supports
16 heads
AWARD AT BIOS and AMI 386sx BIOS have been extended to handle more
than 1024 cylinders by placing bits 10 and 11 of the cylinder number
into bits 6 and 7 of DH
SeeAlso: AH=02h,AH=0Bh
--------B-1304----------------------------------------------------
INT 13 - DISK - VERIFY DISK SECTOR(S)
AH
04h =
AL =
number of sectors to verify (must be nonzero)
CH =
low eight bits of cylinder number
CL =
sector number 1-63 (bits 0-5)
high two bits of cylinder (bits 6-7, hard disk only)
DH = head number
DL = drive number (bit 7 set for hard disk)
ES:BX -> data buffer (PC,XT,AT with BIOS prior to 11/15/85)
Return: CF set on error
CF clear if successful
AH = status (see AH=01h)
AL = number of sectors verified
Notes: errors on a floppy may be due to the motor failing to spin up quickly
enough; the write should be retried at least three times, resetting
the disk with AH=00h between attempts
this function does not compare the disk with memory, it merely
checks whether the sector's stored CRC matches the data's actual CRC
the IBM AT BIOS and many other BIOSes use only the low four bits of
DH (head number) since the WD-1003 controller which is the standard
AT controller (and the controller that IDE emulates) only supports
16 heads
AWARD AT BIOS and AMI 386sx BIOS have been extended to handle more
than 1024 cylinders by placing bits 10 and 11 of the cylinder number
into bits 6 and 7 of DH
SeeAlso: AH=02h
--------B-1305----------------------------------------------------
INT 13 - FLOPPY - FORMAT TRACK
AH = 05h
AL = number of sectors to format
CH = track number
DH = head number
DL = drive number
ES:BX -> address field buffer (see below)
Return: CF set on error
CF clear if successful
AH = status (see AH=01h)
Notes: on AT or higher, call AH=17h first
the number of sectors per track is read from the diskette parameter
table pointed at by INT 1E
SeeAlso: AH=05h"FIXED",AH=17h,AH=18h,INT 1E
17 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
18 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
Notes: may return successful even though specified drive is greater than the
number of attached drives of that type (floppy/hard); check DL to
ensure validity
for systems predating the IBM AT, this call is only valid for hard
disks, as it is implemented by the hard disk BIOS rather than the
ROM BIOS
Toshiba laptops with HardRAM return DL=02h when called with DL=80h,
but fail on DL=81h. The BIOS data at 40h:75h correctly reports 01h.
SeeAlso: AH=06h"Adaptec",AH=15h,INT 1E,INT 41
19 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
--------B-1586----------------------------------------------------
INT 15 - BIOS - WAIT (AT,PS)
AH = 86h
CX:DX = interval in microseconds
Return: CF clear if successful (wait interval elapsed)
CF set on error or AH=83h wait already in progress
AH = status (see AH=84h)
Note: the resolution of the wait period is 977 microseconds on most systems
because most BIOSes use the 1/1024 second fast interrupt from the AT
real-time clock chip which is available on INT 70
SeeAlso: AH=41h,AH=83h,INT 1A/AX=FF01h,INT 70
--------B-1590----------------------------------------------------
INT 15 - OS HOOK - DEVICE BUSY (AT,PS)
AH = 90h
AL = device type (see below)
ES:BX -> request block for type codes 80h through BFh
CF clear
Return: CF set if wait time satisfied
CF clear if driver must perform wait
AH = 00h
Notes: type codes are allocated as follows:
00-7F non-reentrant devices; OS must arbitrate access
80-BF reentrant devices; ES:BX points to a unique control block
C0-FF wait-only calls, no complementary INT 15/AH=91h call
floppy and hard disk BIOS code uses this call to implement a timeout;
for device types 00h and 01h, a return of CF set means that the
timeout expired before the disk responded.
this function should be hooked by a multitasker to allow other tasks
to execute while the BIOS is waiting for I/O completion; the default
handler merely returns with AH=00h and CF clear
SeeAlso: AH=91h,INT 13/AH=00h,INT 17/AH=00h,INT 1A/AH=83h
20 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
AH = status
00h successful
86h unsupported function
Notes: the 1/10/86 XT BIOS returns an incorrect value for the feature byte
the configuration table is at F000h:E6F5h in 100% compatible BIOSes
Dell machines contain the signature "DELL" or "Dell" at absolute FE076h
and a model byte at absolute address FE845h
Hewlett-Packard machines contain the signature "HP" at F000h:00F8h and
a product identifier at F000h:00FAh (see below)
Compaq machines can be identified by the signature string "COMPAQ" at
F000h:FFEAh, and is preceded by additional information (see below)
Tandy 1000 machines contain 21h in the byte at F000h:C000h and FFh in
the byte at FFFFh:000Eh; Tandy 1000SL/TL machines only provide the
first three data bytes (model/submodel/revision) in the returned
table
some AST machines contain the string "COPYRIGHT AST RESEARCH" one byte
past the end of the configuration table
the Phoenix 386 BIOS contains a second version and date string
(presumably the last modification for that OEM version) beginning at
F000h:FFD8h, with each byte doubled (so that both ROM chips contain
the complete information)
SeeAlso: AH=C7h,AH=C9h,AH=D1h
21 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
bit 7 reserved
bit 6 INT 16/AH=09h (keyboard functionality) supported
bit 5 INT 15/AH=C6h (get POS data) supported
bit 4 INT 15/AH=C7h (return memory map info) supported
bit 3 INT 15/AH=C8h (en/disable CPU functions) supported
bit 2 non-8042 keyboard controller
bit 1 data streaming supported
bit 0 reserved
22 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
FCh 0Bh 00h 02/16/90 PS/1 Model 2011 (10 MHz 286)
FCh 20h 00h 02/18/93 Compaq ProLinea
FCh 30h *** ??? Epson, unknown model
FCh 31h *** ??? Epson, unknown model
FCh 33h *** ??? Epson, unknown model
FCh 42h *** ??? Olivetti M280
FCh 45h *** ??? Olivetti M380 (XP 1, XP3, XP 5)
FCh 48h *** ??? Olivetti M290
FCh 4Fh *** ??? Olivetti M250
FCh 50h *** ??? Olivetti M380 (XP 7)
FCh 51h *** ??? Olivetti PCS286
FCh 52h *** ??? Olivetti M300
FCh 81h 00h 01/15/88 Phoenix 386 BIOS v1.10 10a
FCh 81h 01h ??? "OEM machine"
FCh 82h 01h ??? "OEM machine"
FCh 94h 00h ??? Zenith 386
FBh 00h 01h 01/10/86 PC XT-089, Enh Keyb, 3.5" support
FBh 00h 02h 05/09/86 PC XT
FBh 4Ch *** ??? Olivetti M200
FAh 00h 00h 09/02/86 PS/2 Model 30 (8 MHz 8086)
FAh 00h 01h 12/12/86 PS/2 Model 30
FAh 01h 00h ??? PS/2 Model 25/25L (8 MHz 8086)
FAh 30h 00h ??? IBM Restaurant Terminal
FAh 4Eh *** ??? Olivetti M111
FAh FEh 00h ??? IBM PCradio 9075
F9h 00h 00h 09/13/85 PC Convertible
F9h FFh 00h ??? PC Convertible
F8h 00h 00h 03/30/87 ** PS/2 Model 80 (16MHz 386)
F8h 01h 00h 10/07/87 PS/2 Model 80 (20MHz 386)
F8h 02h 00h ??? PS/2 Model 55-5571
F8h 04h 00h ??? PS/2 Model 70
F8h 04h 02h 04/11/88 PS/2 Model 70 20MHz, type 2 system brd
F8h 04h 03h 03/17/89 PS/2 Model 70 20MHz, type 2 system brd
F8h 05h 00h ??? IBM PC 7568
F8h 06h 00h ??? PS/2 Model 55-5571
F8h 07h 00h ??? IBM PC 7561/2
F8h 07h 01h ??? PS/2 Model 55-5551
F8h 07h 02h ??? IBM PC 7561/2
F8h 07h 03h ??? PS/2 Model 55-5551
F8h 09h 00h ??? PS/2 Model 70 16MHz, type 1 system brd
F8h 09h 02h 04/11/88 PS/2 Model 70 some models
F8h 09h 03h 03/17/89 PS/2 Model 70 some models
F8h 0Bh 00h 01/18/89 PS/2 Model P70 (8573-121) typ 2 sys brd
F8h 0Bh 02h 12/16/89 PS/2 Model P70 ??
F8h 0Ch 00h 11/02/88 PS/2 Model 55SX (16 MHz 386SX)
F8h 0Dh 00h ??? PS/2 Model 70 25MHz, type 3 system brd
F8h 0Eh 00h ??? PS/1 486SX
F8h 0Fh 00h ??? PS/1 486DX
F8h 10h 00h ??? PS/2 Model 55-5551
F8h 11h 00h 10/01/90 PS/2 Model 90 XP (25 MHz 486)
F8h 12h 00h ??? PS/2 Model 95 XP
F8h 13h 00h 10/01/90 PS/2 Model 90 XP (33 MHz 486)
F8h 14h 00h 10/01/90 PS/2 Model 90-AK9 (25 MHz 486), 95 XP
F8h 15h 00h ??? PS/2 Model 90 XP
F8h 16h 00h 10/01/90 PS/2 Model 90-AKD (33 MHz 486)
F8h 17h 00h ??? PS/2 Model 90 XP
F8h 19h 05h ??? PS/2 Model 35/35LS or 40 (20 MHz 386SX)
F8h 1Ah 00h ??? PS/2 Model 95 XP
F8h 1Bh 00h 10/02/89 PS/2 Model 70-486 (25 MHz 486)
F8h 1Ch 00h 02/08/90 PS/2 Model 65-121 (16 MHz 386SX)
F8h 1Eh 00h 02/08/90 PS/2 Model 55LS (16 MHz 386SX)
F8h 23h 00h ??? PS/2 Model L40 SX
23 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
F8h 23h 01h ??? PS/2 Model L40 SX (20 MHz 386SX)
F8h 25h 00h ??? PS/2 Model 57 SLC
F8h 25h 06h ??? PS/2 Model M57 (20 MHz 386SLC)
F8h 26h 00h ??? PS/2 Model 57 SX
F8h 26h 01h ??? PS/2 Model 57 (20 MHz 386SX)
F8h 28h 00h ??? PS/2 Model 95 XP
F8h 29h 00h ??? PS/2 Model 90 XP
F8h 2Ah 00h ??? PS/2 Model 95 XP (50 MHz 486)
F8h 2Bh 00h ??? PS/2 Model 90 (50 MHz 486)
F8h 2Ch 00h ??? PS/2 Model 95 XP
F8h 2Ch 01h ??? PS/2 Model 95 (20 MHz 486SX)
F8h 2Dh 00h ??? PS/2 Model 90 XP (20 MHz 486SX)
F8h 2Eh 00h ??? PS/2 Model 95 XP
F8h 2Eh 01h ??? PS/2 Model 95 (20 MHz 486SX + 487SX)
F8h 2Fh 00h ??? PS/2 Model 90 XP (20 MHz 486SX + 487SX)
F8h 30h 00h ??? PS/1 Model 2121 (16 MHz 386SX)
F8h 33h 00h ??? PS/2 Model 30-386
F8h 34h 00h ??? PS/2 Model 25-386
F8h 36h 00h ??? PS/2 Model 95 XP
F8h 37h 00h ??? PS/2 Model 90 XP
F8h 38h 00h ??? PS/2 Model 57
F8h 39h 00h ??? PS/2 Model 95 XP
F8h 3Fh 00h ??? PS/2 Model 90 XP
F8h 40h 00h ??? PS/2 Model 95 XP
F8h 41h 00h ??? PS/2 Model 77
F8h 45h 00h ??? PS/2 Model 90 XP (Pentium)
F8h 46h 00h ??? PS/2 Model 95 XP (Pentium)
F8h 47h 00h ??? PS/2 Model 90/95 E (Pentium)
F8h 48h 00h ??? PS/2 Model 85
F8h 49h 00h ??? PS/ValuePoint 325T
F8h 4Ah 00h ??? PS/ValuePoint 425SX
F8h 4Bh 00h ??? PS/ValuePoint 433DX
F8h 4Eh 00h ??? PS/2 Model 295
F8h 50h 00h ??? PS/2 Model P70 (8573) (16 MHz 386)
F8h 50h 01h 12/16/89 PS/2 Model P70 (8570-031)
F8h 52h 00h ??? PS/2 Model P75 (33 MHz 486)
F8h 56h 00h ??? PS/2 Model CL57 SX
F8h 57h 00h ??? PS/2 Model 90 XP
F8h 58h 00h ??? PS/2 Model 95 XP
F8h 59h 00h ??? PS/2 Model 90 XP
F8h 5Ah 00h ??? PS/2 Model 95 XP
F8h 5Bh 00h ??? PS/2 Model 90 XP
F8h 5Ch 00h ??? PS/2 Model 95 XP
F8h 5Dh 00h ??? PS/2 Model N51 SLC
F8h 5Eh 00h ??? IBM ThinkPad 700
F8h 61h *** ??? Olivetti P500
F8h 62h *** ??? Olivetti P800
F8h 80h 00h ??? PS/2 Model 80 (25 MHz 386)
F8h 80h 01h 11/21/89 PS/2 Model 80-A21
F8h 81h 00h ??? PS/2 Model 55-5502
F8h 87h 00h ??? PS/2 Model N33SX
F8h 88h 00h ??? PS/2 Model 55-5530T
F8h 97h 00h ??? PS/2 Model 55 Note N23SX
F8h 99h 00h ??? PS/2 Model N51 SX
F8h F2h 30h ??? Reply Model 32
F8h F6h 30h ??? Memorex Telex
F8h FDh 00h ??? IBM Processor Complex (with VPD)
F8h ??? ??? ??? PS/2 Model 90 (25 MHz 486SX)
F8h ??? ??? ??? PS/2 Model 95 (25 MHz 486SX)
F8h ??? ??? ??? PS/2 Model 90 (25 MHz 486SX + 487SX)
F8h ??? ??? ??? PS/2 Model 95 (25 MHz 486SX + 487SX)
E1h ??? ??? ??? ??? (checked for by DOS4GW.EXE)
24 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
25 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
26 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
27 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
Notes: Usually, the BIOS will try to read sector 1, head 0, track 0 from drive
A: to 0000h:7C00h. If this fails, and a hard disk is installed, the
BIOS will read sector 1, head 0, track 0 of the first hard disk.
This sector should contain a master bootstrap loader and a partition
table. After loading the master boot sector at 0000h:7C00h, the
master bootstrap loader is given control. It will scan the partition
table for an active partition, and will then load the operating
system's bootstrap loader (contained in the first sector of the
active partition) and give it control.
true IBM PCs and most clones issue an INT 18 if neither floppy nor hard
disk have a valid boot sector
to accomplish a warm boot equivalent to Ctrl-Alt-Del, store 1234h in
0040h:0072h and jump to FFFFh:0000h. For a cold boot equivalent to
a reset, store 0000h at 0040h:0072h before jumping.
VDISK.SYS hooks this interrupt to allow applications to find out how
much extended memory has been used by VDISKs (see below). DOS 3.3+
PRINT hooks INT 19 but does not set up a correct VDISK header block
at the beginning of its INT 19 handler segment, thus causing some
programs to overwrite extended memory which is already in use.
the default handler is at F000h:E6F2h for 100% compatible BIOSes
MS-DOS 3.2+ hangs on booting (even from floppy) if the hard disk
contains extended partitions which point at each other in a loop,
since it will never find the end of the linked list of extended
partitions
SeeAlso: INT 14/AH=17h,INT 18
28 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
29 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
30 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
31 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
32 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
--------D-2131----------------------------------------------------
INT 21 - DOS 2+ - TERMINATE AND STAY RESIDENT
AH = 31h
AL = return code
DX = number of paragraphs to keep resident
Return: never
Notes: the value in DX only affects the memory block containing the PSP;
additional memory allocated via AH=48h is not affected
the minimum number of paragraphs which will remain resident is 11h
for DOS 2.x and 06h for DOS 3+
most TSRs can save some memory by releasing their environment block
before terminating (see AH=26h,AH=49h)
SeeAlso: AH=00h,AH=4Ch,AH=4Dh,INT 20,INT 22,INT 27
--------D-2134----------------------------------------------------
INT 21 - DOS 2+ - GET ADDRESS OF INDOS FLAG
AH = 34h
Return: ES:BX -> one-byte InDOS flag
Notes: the value of InDOS is incremented whenever an INT 21 function begins
and decremented whenever one completes
during an INT 28 call, it is safe to call some INT 21 functions even
though InDOS may be 01h instead of zero
InDOS alone is not sufficient for determining when it is safe to
enter DOS, as the critical error handling decrements InDOS and
increments the critical error flag for the duration of the critical
error. Thus, it is possible for InDOS to be zero even if DOS is
busy.
SMARTDRV 4.0 sets the InDOS flag while flushing its buffers to disk,
then zeros it on completion
the critical error flag is the byte immediately following InDOS in
DOS 2.x, and the byte BEFORE the InDOS flag in DOS 3+ and
DR-DOS 3.41+ (except COMPAQ DOS 3.0, where the critical error flag
is located 1AAh bytes BEFORE the critical section flag)
for DOS 3.1+, an undocumented call exists to get the address of the
critical error flag (see AX=5D06h)
this function was undocumented prior to the release of DOS 5.0.
SeeAlso: AX=5D06h,AX=5D0Bh,INT 15/AX=DE1Fh,INT 28
--------D-2135----------------------------------------------------
INT 21 - DOS 2+ - GET INTERRUPT VECTOR
AH = 35h
AL = interrupt number
Return: ES:BX -> current interrupt handler
Note: under DR-DOS 5.0+, this function does not use any of the DOS-internal
stacks and may thus be called at any time
SeeAlso: AH=25h,AX=2503h
--------D-2136----------------------------------------------------
INT 21 - DOS 2+ - GET FREE DISK SPACE
AH = 36h
DL = drive number (00h = default, 01h = A:, etc)
Return: AX = FFFFh if invalid drive
else
AX = sectors per cluster
BX = number of free clusters
CX = bytes per sector
DX = total clusters on drive
Notes: free space on drive in bytes is AX * BX * CX
total space on drive in bytes is AX * CX * DX
"lost clusters" are considered to be in use
according to Dave Williams' MS-DOS reference, the value in DX is
incorrect for non-default drives after ASSIGN is run
SeeAlso: AH=1Bh,AH=1Ch
--------D-2138----------------------------------------------------
33 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
34 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
35 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
36 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
37 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
AH = single-user/multiuser nature
10h single-user
AL = operating system version ID (see below)
14h multiuser
AL = operating system version ID (see AX=4451h)
Notes: the DR-DOS version is stored in the environment variable VER
use this function if looking for single-user capabilities, AX=4451h
if looking for multiuser; this call should never return multiuser
values
SeeAlso: AX=4412h,AX=4451h,AX=4459h
38 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
37h 5 BYTEs far jump to kernel entry point for INT 2B (IRET)
3Ch 5 BYTEs far jump to kernel entry point for INT 2C (IRET)
41h 5 BYTEs far jump to kernel entry point for INT 2D (IRET)
46h 5 BYTEs far jump to kernel entry point for INT 2E (IRET)
4Bh 5 BYTEs far jump to kernel entry point for INT 2F
Notes: all of these entry points are indirected through this jump table
to allow the kernel to be relocated into high memory while leaving
the actual entry addresses in low memory for maximum compatibility
some of these entry points (22h,23h,24h,2Eh,2Fh) are replaced as soon
as COMMAND.COM is loaded, and return immediately to the caller, some
returning an error code (the original handler for INT 2F returns
AL=03h [fail]).
39 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
40 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
environments:
MZ old-style DOS executable
NE Windows or OS/2 1.x segmented ("new") executable
LE Windows virtual device driver (VxD) linear executable
LX variant of LE used in OS/2 2.x
W3 Windows WIN386.EXE file; a collection of LE files
PE Win32 (Windows NT and Win32s) portable executable based on
Unix COFF
BUGS: DOS 2.00 assumes that DS points at the current program's PSP
Load Overlay (subfunction 03h) loads up to 512 bytes too many if the
file contains additional data after the actual overlay
SeeAlso: AX=4B05h,AH=4Ch,AH=4Dh,AH=64h"OS/2",AH=8Ah,INT 2E
41 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
42 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
43 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
44 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
45 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
46 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
47 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
48 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
bit 0: exported
bits 7-3: number of stack parameters
05h DWORD offset of entry point in object
---bundle type 04h---
02h WORD reserved
04h BYTE forwarder flags
bit 0: import by ordinal
bits 7-1 reserved
05h WORD module ordinal
(forwarder's index into Import Module Name table)
07h DWORD procedure name offset or import ordinal number
Note: all fields after the first two bytes are repeated N times
49 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
50 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
51 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
52 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
53 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
20h BYTE
number of block devices installed
21h BYTE
number of available drive letters (largest of 5, installed
block devices, and CONFIG.SYS LASTDRIVE=). Also size of
current directory structure array.
22h 18 BYTEs actual NUL device driver header (not a pointer!)
NUL is always the first device on DOS's linked list of device
drivers. (see below)
34h BYTE number of JOIN'ed drives
35h WORD pointer within IBMDOS code segment to list of special program
names (see below)
(always 0000h for DOS 5.0)
37h DWORD pointer to FAR routine for resident IFS utility functions
(see below)
may be called by any IFS driver which does not wish to
service functions 20h or 24h-28h itself
3Bh DWORD pointer to chain of IFS (installable file system) drivers
3Fh WORD the x in BUFFERS x,y (rounded up to multiple of 30 if in EMS)
41h WORD number of lookahead buffers (the y in BUFFERS x,y)
43h BYTE boot drive (1=A:)
44h BYTE flag: 01h to use DWORD moves (80386+), 00h otherwise
45h WORD extended memory size in KB
---DOS 5.0-6.0---
10h 39 BYTEs as for DOS 4.x (see above)
37h DWORD pointer to SETVER program list or 0000h:0000h
3Bh WORD (DOS=HIGH) offset in DOS CS of function to fix A20 control
when executing special .COM format
3Dh WORD PSP of most-recently EXECed program if DOS in HMA, 0000h if low
3Fh 8 BYTEs as for DOS 4.x (see above)
54 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
55 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
56 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
call with ES:DI -> system file table entry for file
CX = length of region from current position in file
Return: CF set if any portion of region locked
AX = 0021h
-18h DWORD pointer to FAR routine to get open file list entry
(called by AX=5D05h)
call with DS:SI -> DOS parameter list (see AX=5D00h)
DPL's BX = index of sharing record
DPL's CX = index of SFT in SFT chain of sharing rec
Return: CF set on error or not loaded
AX = DOS error code (12h) (see AH=59h)
CF clear if successful
ES:DI -> filename
CX = number of locks owned by specified SFT
BX = network machine number
DX destroyed
-14h DWORD pointer to FAR routine for updating FCB from SFT???
call with DS:SI -> unopened FCB
ES:DI -> system file table entry
Return: BL = C0h???
Note: copies following fields from SFT to FCB:
starting cluster of file 0Bh 1Ah
sharing record offset 33h 1Ch
file attribute 04h 1Eh
-10h DWORD pointer to FAR routine to get first cluster of FCB file ???
call with ES:DI -> system file table entry
DS:SI -> FCB
Return: CF set if SFT closed or sharing record offsets
mismatched
CF clear if successful
BX = starting cluster number from FCB
-0Ch DWORD pointer to FAR routine to close file if duplicate for process
DS:SI -> system file table
Return: AX = number of handle in JFT which already uses SFT
Note: called during open/create of a file
Note: if SFT was opened with inheritance enabled and sharing
mode 111, does something to all other SFTs owned by
same process which have the same file open mode and
sharing record
-08h DWORD pointer to FAR routine for closing file
Note: closes various handles referring to file most-recently
opened
-04h DWORD pointer to FAR routine to update directory info in related SFT
entries
call with ES:DI -> system file table entry for file (see below)
AX = subfunction (apply to each related SFT)
00h: update time stamp (offset 0Dh) and date
stamp (offset 0Fh)
01h: update file size (offset 11h) and starting
cluster (offset 0Bh). Sets last-accessed
cluster fields to start of file if file
never accessed
02h: as function 01h, but last-accessed fields
always changed
03h: do both functions 00h and 02h
Note: follows ptr at offset 2Bh in system file table entries
Note: NOP if opened with no-inherit or via FCB
Notes: most of the above hooks (except -04h, -14h, -18h, and -3Ch) assume
either that SS=DOS DS or SS=DS=DOS DS and directly access
DOS-internal data
sharing hooks are not supported by DR-DOS 5-6; will reportedly be
supported by Novell DOS 7
57 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
58 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
Format of DOS 3.1-3.3x, DR-DOS 5.0-6.0 system file tables and FCB tables:
Offset Size Description
00h DWORD pointer to next file table (offset FFFFh if last)
04h WORD number of files in this table
06h 35h bytes per file
Offset Size Description
00h WORD number of file handles referring to this file
02h WORD file open mode (see AH=3Dh)
bit 15 set if this file opened via FCB
04h BYTE file attribute (see AX=4301h)
05h WORD device info word (see AX=4400h)
bit 15 set if remote file
bit 14 set means do not set file date/time on closing
bit 12 set means don't inherit on EXEC
bits 5-0 drive number for disk files
07h DWORD pointer to device driver header if character device
else pointer to DOS Drive Parameter Block (see AH=32h)
0Bh WORD starting cluster of file
0Dh WORD file time in packed format (see AX=5700h)
not used for character devices in DR-DOS
0Fh WORD file date in packed format (see AX=5700h)
not used for character devices in DR-DOS
11h DWORD file size
---system file table---
15h DWORD current offset in file (may be larger than size of
file; INT 21/AH=42h does not check new position)
---FCB table---
15h WORD counter for last I/O to FCB
17h WORD counter for last open of FCB
(these are separate to determine the times of the
latest I/O and open)
---
19h WORD relative cluster within file of last cluster accessed
1Bh WORD absolute cluster number of last cluster accessed
0000h if file never read or written???
1Dh WORD number of sector containing directory entry
1Fh BYTE number of dir entry within sector (byte offset/32)
20h 11 BYTEs filename in FCB format (no path/period, blank-padded)
2Bh DWORD (SHARE.EXE) pointer to previous SFT sharing same file
2Fh WORD (SHARE.EXE) network machine number which opened file
(Windows Enhanced mode DOSMGR uses the virtual machine
ID as the machine number; see INT 2F/AX=1683h)
31h WORD PSP segment of file's owner (see AH=26h) (first three
entries for AUX/CON/PRN contain segment of IO.SYS
startup code)
33h WORD offset within SHARE.EXE code segment of
sharing record (see above) 0000h = none
59 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
60 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
49h WORD
starting cluster of current directory
0000h = root, FFFFh = never accessed
4Bh WORD ??? seems to be FFFFh always
4Dh WORD ??? seems to be FFFFh always
---network drives---
49h DWORD pointer to redirector or REDIRIFS record, or FFFFh:FFFFh
(DOS 4 only) available for use by IFS driver
4Dh WORD stored user data from INT 21/AX=5F03h
------
4Fh WORD offset in current directory path of backslash corresponding to
root directory for drive
this value specifies how many characters to hide from the
"CHDIR" and "GETDIR" calls; normally set to 2 to hide the
drive letter and colon, SUBST, JOIN, and networks change it
so that only the appropriate portion of the true path is
visible to the user
---DOS 4+ ---
51h BYTE (DOS 4 only, remote drives) device type
04h network drive
52h DWORD pointer to IFS driver (DOS 4) or redirector block (DOS 5+) for
this drive, 00000000h if native DOS
56h WORD available for use by IFS driver
Notes: the path for invalid drives is normally set to X:\, but may be empty
after JOIN x: /D in DR-DOS 5.0 or NET USE x: /D in older LAN versions
normally, only one of bits 13&12 may be set together with bit 14, but
DR-DOS 5.0 uses other combinations for bits 15-12: 0111 JOIN,
0001 SUBST, 0101 ASSIGN (see below)
61 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
bit 5
reserved
bit 4
device is special (use INT 29 "fast console output")
bit 3
device is CLOCK$ (all reads/writes use transfer
record described below)
bit 2 device is NUL
bit 1 device is standard output
bit 0 device is standard input
Block device:
bit 15 clear (indicates block device)
bit 14 IOCTL supported
bit 13 non-IBM format
bit 12 network device (device is remote)
bit 11 (DOS 3+) OPEN/CLOSE/RemMedia calls supported
bit 10 reserved
bit 9 direct I/O not allowed???
(set by DOS 3.3 DRIVER.SYS for "new" drives)
bit 8 ??? set by DOS 3.3 DRIVER.SYS for "new" drives
bit 7 (DOS 5+) Generic IOCTL check call supported (cmd 19h)
(see AX=4410h,AX=4411h)
bit 6 (DOS 3.2+) Generic IOCTL call supported (command 13h)
implies support for commands 17h and 18h
(see AX=440Ch,AX=440Dh,AX=440Eh,AX=440Fh)
bits 5-2 reserved
bit 1 driver supports 32-bit sector addressing (DOS 3.31+)
bit 0 reserved
Note: for European MS-DOS 4.0, bit 11 also indicates that bits
8-6 contain a version code (000 = DOS 3.0,3.1;
001 = DOS 3.2, 010 = European DOS 4.0)
06h WORD device strategy entry point
call with ES:BX -> request header (see INT 2F/AX=0802h)
08h WORD device interrupt entry point
---character device---
0Ah 8 BYTEs blank-padded character device name
---block device---
0Ah BYTE number of subunits (drives) supported by driver
0Bh 7 BYTEs unused
---
12h WORD (CD-ROM driver) reserved, must be 0000h
appears to be another device chain
14h BYTE (CD-ROM driver) drive letter (must initially be 00h)
15h BYTE (CD-ROM driver) number of units
16h 6 BYTEs (CD-ROM driver) signature 'MSCDnn' where 'nn' is version
(currently '00')
62 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
Format of DOS 4.01 (from UR 25066 Corrctive Services Disk on) disk buffer info:
Offset Size Description
00h DWORD pointer to array of disk buffer hash chain heads (see below)
04h WORD number of disk buffer hash chains (referred to as NDBCH below)
06h DWORD pointer to lookahead buffer, zero if not present
0Ah WORD number of lookahead sectors, else zero (the y in BUFFERS=x,y)
0Ch BYTE 01h, possibly to distinguish from pre-UR 25066 format
0Dh WORD ??? EMS segment for BUFFERS (only with /XD)
0Fh WORD ??? EMS physical page number of EMS seg above (only with /XD)
11h WORD ??? EMS segment for ??? (only with /XD)
13h WORD ??? EMS physical page number of above (only with /XD)
15h BYTE ??? number of EMS page frames present (only with /XD)
16h WORD segment of one-sector workspace buffer allocated in main memory
if BUFFERS/XS or /XD options in effect, possibly to avoid DMA
into EMS
18h WORD EMS handle for buffers, zero if not in EMS
1Ah WORD EMS physical page number used for buffers (usually 255)
1Ch WORD ??? appears always to be 0001h
1Eh WORD segment of EMS physical page frame
20h WORD ??? appears always to be zero
22h BYTE 00h if /XS, 01h if /XD, FFh if BUFFERS not in EMS
Format of DOS 4.x disk buffer hash chain head (array, one entry per chain):
Offset Size Description
00h WORD EMS logical page number in which chain is resident, -1 if not
63 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
in EMS
02h DWORD pointer to least recently used buffer header. All buffers on
this chain are in the same segment.
06h BYTE number of dirty buffers on this chain
07h BYTE reserved (00h)
Notes: buffered disk sectors are assigned to chain N where N is the sector's
address modulo NDBCH, 0 <= N <= NDBCH-1
each chain resides completely within one EMS page
this structure is in main memory even if buffers are in EMS
64 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
65 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
66 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
67 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
68 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
69 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
AH = 58h
AL = subfunction
00h get allocation strategy
Return: AX = current strategy
00h low memory first fit
01h low memory best fit
02h low memory last fit
---DOS 5+ ---
40h high memory first fit
41h high memory best fit
42h high memory last fit
80h first fit, try high then low memory
81h best fit, try high then low memory
82h last fit, try high then low memory
01h set allocation strategy
BL = new allocation strategy (see above)
BH = 00h (DOS 5+)
Return: CF clear if successful
CF set on error
AX = error code (01h) (see AH=59h)
Notes: the Set subfunction accepts any value in BL for DOS 3.x and 4.x;
2 or greater means last fit
the Get subfunction returns the last value set
setting an allocation strategy involving high memory does not
automatically link in the UMB memory chain; this must be done
explicitly with AX=5803h in order to actually allocate high memory
a program which changes the allocation strategy should restore it
before terminating
Toshiba MS-DOS 2.11 supports subfunctions 00h and 01h
DR-DOS 3.41 reportedly reverses subfunctions 00h and 01h
SeeAlso: AH=48h,AH=49h,AH=4Ah,INT 2F/AX=4310h,INT 67/AH=3Fh
--------D-2158----------------------------------------------------
INT 21 - DOS 5+ - GET OR SET UMB LINK STATE
AH = 58h
AL = subfunction
02h get UMB link state
Return: AL = 00h UMBs not part of DOS memory chain
= 01h UMBs in DOS memory chain
03h set UMB link state
BX = 0000h remove UMBs from DOS memory chain
= 0001h add UMBs to DOS memory chain
Return: CF clear if successful
CF set on error
AX = error code (01h) (see AH=59h)
Note: a program which changes the UMB link state should restore it before
terminating
--------D-2159--BX0000--------------------------------------------
INT 21 - DOS 3+ - GET EXTENDED ERROR INFORMATION
AH = 59h
BX = 0000h
Return: AX = extended error code (see below)
BH = error class (see below)
BL = recommended action (see below)
CH = error locus (see below)
ES:DI may be pointer (see error code list below)
CL, DX, SI, BP, and DS destroyed
Notes: functions available under DOS 2.x map the true DOS 3+ error code into
one supported under DOS 2.x
you should call this function to retrieve the true error code when an
FCB or DOS 2.x call returns an error
under DR-DOS 5.0, this function does not use any of the DOS-internal
70 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
71 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
72 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
73 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
37h BYTE flag: if nonzero, INT 24 Abort turned into INT 24 Fail
(set only during process termination)
38h 26 BYTEs device driver request header (see INT 2F/AX=0802h)
52h DWORD pointer to device driver entry point (used in calling driver)
56h 22 BYTEs device driver request header for I/O calls
6Ch 14 BYTEs device driver request header for disk status check
7Ah DWORD pointer to device I/O buffer???
7Eh WORD ???
80h WORD ???
82h BYTE type of PSP copy (00h=simple for INT 21/AH=26h, FFh=make child)
83h BYTE padding (unused)
84h 3 BYTEs 24-bit user number (see AH=30h)
87h BYTE OEM number (see AH=30h)
88h WORD offset to error code conversion table for INT 25/INT 26
8Ah 6 BYTEs CLOCK$ transfer record (see AH=52h)
90h BYTE device I/O buffer for single-byte I/O functions
91h BYTE padding??? (unused)
92h 128 BYTEs buffer for filename
112h 128 BYTEs buffer for filename
192h 21 BYTEs findfirst/findnext search data block (see AH=4Eh)
1A7h 32 BYTEs directory entry for found file (see AH=11h)
1C7h 81 BYTEs copy of current directory structure for drive being accessed
218h 11 BYTEs FCB-format filename for device name comparison
223h BYTE terminating NUL for above filename
224h 11 BYTEs wildcard destination specification for rename (FCB format)
22Fh BYTE terminating NUL for above spec
230h BYTE ???
231h WORD destination file/directory starting sector
233h 5 BYTEs ???
238h BYTE extended FCB file attribute
239h BYTE type of FCB (00h regular, FFh extended)
23Ah BYTE directory search attributes
23Bh BYTE file open/access mode
23Ch BYTE file found/delete flag
bit 0: file found
bit 4: file deleted
23Dh BYTE flag: device name found on rename, or file not found
23Eh BYTE splice flag (file name and directory name together)
23Fh BYTE flag indicating how DOS function was invoked
(00h = direct INT 20/INT 21, FFh = server call AX=5D00h)
240h BYTE sector position within cluster
241h BYTE flag: translate sector/cluster (00h no, 01h yes)
242h BYTE flag: 00h if read, 01h if write
243h BYTE current working drive number
244h BYTE cluster factor
245h BYTE flag: cluster split mode
246h BYTE line edit (AH=0Ah) insert mode flag (nonzero = on)
247h BYTE canonicalized filename referred to existing file/dir if FFh
74 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
248h BYTE
volume ID flag
249h BYTE
type of process termination (00h-03h) (see AH=4Dh)
24Ah BYTE
file create flag (00h = no)
24Bh BYTE
value with which to replace first byte of deleted file's name
(normally E5h, but 00h as described under INT 21/AH=13h)
24Ch DWORD pointer to Drive Parameter Block for critical error invocation
temp: used during process termination
250h DWORD pointer to stack frame containing user registers on INT 21
254h WORD stores SP across INT 24
256h DWORD pointer to DOS Drive Parameter Block for ???
25Ah WORD saving partial cluster number
25Ch WORD temp: sector of work current cluster
25Eh WORD high part of cluster number (only low byte referenced)
260h WORD ??? temp
262h BYTE Media ID byte returned by AH=1Bh,1Ch
263h BYTE padding (unused)
264h DWORD pointer to device header
268h DWORD pointer to current SFT
26Ch DWORD pointer to current directory structure for drive being accessed
270h DWORD pointer to caller's FCB
274h WORD number of SFT to which file being opened will refer
276h WORD temporary storage for file handle
278h DWORD pointer to a JFT entry in process handle table (see AH=26h)
27Ch WORD offset in DOS DS of first filename argument
27Eh WORD offset in DOS DS of second filename argument
280h WORD offset of last component in pathname or FFFFh
282h WORD offset of transfer address to add
284h WORD last relative cluster within file being accessed
286h WORD temp: absolute cluster number being accessed
288h WORD directory sector number
28Ah WORD ??? current cluster number
28Ch WORD ??? current offset in file DIV bytes per sector
28Eh WORD current sector number
290h WORD current byte offset within sector
292h DWORD current offset in file
296h DWORD temp: file byte count
29Ah WORD temp: file byte count
29Ch WORD free file cluster entry
29Eh WORD last file cluster entry
2A0h WORD next file cluster number
2A2h DWORD number of bytes appended to file
2A6h DWORD pointer to current work disk buffer
2AAh DWORD pointer to working SFT
2AEh WORD used by INT 21 dispatcher to store caller's BX
2B0h WORD used by INT 21 dispatcher to store caller's DS
2B2h WORD temporary storage while saving/restoring caller's registers
2B4h DWORD pointer to prev call frame (offset 250h) if INT 21 reentered
also switched to for duration of INT 24
2B8h 21 BYTEs FindFirst search data for source file(s) of a rename operation
(see AH=4Eh)
2CDh 32 BYTEs directory entry for file being renamed (see AH=11h for format)
2EDh 331 BYTEs critical error stack
403h 35 BYTEs scratch SFT
438h 384 BYTEs disk stack (functions greater than 0Ch, INT 25,INT 26)
5B8h 384 BYTEs character I/O stack (functions 01h through 0Ch)
---DOS 3.2,3.3x only---
738h BYTE device driver lookahead flag (usually printer) (see AH=64h)
739h BYTE volume change flag
73Ah BYTE flag: virtual open
73Bh BYTE ???
--------D-215D0A--------------------------------------------------
INT 21 - DOS 3.1+ - SET EXTENDED ERROR INFORMATION
75 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
AX = 5D0Ah
DS:DX -> 11-word DOS parameter list (see AX=5D00h)
Return: nothing. next call to AH=59h will return values from fields AX,BX,CX,
DX,DI, and ES in corresponding registers
Notes: documented for DOS 5+, but undocumented in earlier versions
the MS-DOS Programmer's Reference incorrectly states that this call was
introduced in DOS 4, and fails to mention that the ERROR structure
passed to this function is a DOS parameter list.
BUG: DR-DOS 3.41 and 5.0 read the value for ES from the DS field of the DPL;
fortunately, MS-DOS ignores the DS field, allowing a generic routine
which sets both DS and ES fields to the same value
SeeAlso: AH=59h
--------D-215D0B--------------------------------------------------
INT 21 OU - DOS 4.x only internal - GET DOS SWAPPABLE DATA AREAS
AX = 5D0Bh
Return: CF set on error
AX = error code (see AH=59h)
CF clear if successful
DS:SI -> swappable data area list (see below)
Notes: copying and restoring the swappable data areas allows DOS to be
reentered unless it is in a critical section delimited by calls to
INT 2A/AH=80h and INT 2A/AH=81h,82h
SHARE and other DOS utilities consult the byte at offset 04h in the
DOS data segment (see INT 2F/AX=1203h) to determine the SDA format
in use: 00h = DOS 3.x, 01h = DOS 4.0-6.0, other = error.
DOS 5+ use the SDA format listed below, but revert back to the DOS 3.x
call for finding the SDA (see AX=5D06h)
SeeAlso: AX=5D06h,INT 2A/AH=80h,INT 2A/AH=81h,INT 2A/AH=82h,INT 2F/AX=1203h
76 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
14h WORD
return code from last process termination (zerod after reading
with AH=4Dh)
16h BYTE current drive
17h BYTE extended break flag
18h BYTE flag: code page switching
19h BYTE flag: copy of previous byte in case of INT 24 Abort
---remainder need only be swapped if in DOS---
1Ah WORD value of AX on call to INT 21
1Ch WORD PSP segment for sharing/network
1Eh WORD network machine number for sharing/network (0000h = us)
20h WORD first usable memory block found when allocating memory
22h WORD best usable memory block found when allocating memory
24h WORD last usable memory block found when allocating memory
26h WORD memory size in paragraphs (used only during initialization)
28h WORD last entry checked during directory search
2Ah BYTE flag: nonzero if INT 24 Fail
2Bh BYTE flags: allowable INT 24 responses (passed to INT 24 in AH)
2Ch BYTE flag: do not set directory if nonzero
2Dh BYTE flag: program aborted by ^C
2Eh BYTE flag: allow embedded blanks in FCB
2Fh BYTE padding (unused)
30h BYTE day of month
31h BYTE month
32h WORD year - 1980
34h WORD number of days since 1-1-1980
36h BYTE day of week (0 = Sunday)
37h BYTE flag: console swapped during read from device
38h BYTE flag: safe to call INT 28 if nonzero
39h BYTE flag: abort currently in progress, turn INT 24 Abort into Fail
3Ah 30 BYTEs device driver request header (see INT 2F/AX=0802h) for
device calls
58h DWORD pointer to device driver entry point (used in calling driver)
5Ch 22 BYTEs device driver request header for I/O calls
72h 14 BYTEs device driver request header for disk status check
80h DWORD pointer to device I/O buffer
84h WORD ???
86h WORD ??? (0)
88h BYTE type of PSP copy (00h=simple for INT 21/AH=26h, FFh=make child)
89h DWORD start offset of file region to lock/unlock
8Dh DWORD length of file region to lock/unlock
91h BYTE padding (unused)
92h 3 BYTEs 24-bit user number (see AH=30h)
95h BYTE OEM number (see AH=30h)
96h 6 BYTEs CLOCK$ transfer record (see AH=52h)
9Ch BYTE device I/O buffer for single-byte I/O functions???
9Dh BYTE padding???
9Eh 128 BYTEs buffer for filename
11Eh 128 BYTEs buffer for filename
19Eh 21 BYTEs findfirst/findnext search data block (see AH=4Eh)
1B3h 32 BYTEs directory entry for found file (see AH=11h)
1D3h 88 BYTEs copy of current directory structure for drive being accessed
22Bh 11 BYTEs FCB-format filename for device name comparison
236h BYTE terminating NUL for above filename
237h 11 BYTEs wildcard destination specification for rename (FCB format)
242h BYTE terminating NUL for above spec
243h BYTE ???
244h WORD ???
246h 5 BYTEs ???
24Bh BYTE extended FCB file attributes
24Ch BYTE type of FCB (00h regular, FFh extended)
24Dh BYTE directory search attributes
24Eh BYTE file open/access mode
77 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
78 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
79 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
CF clear
Return: all registers preserved
return via RETF or RETF 2 with CF set
DOS will abort program with errorlevel 0
else (RETF/RETF 2 with CF clear or IRET)
interrupted DOS call is restarted
Notes: this interrupt is invoked whenever DOS detects a ^C or ^Break; it
should never be called directly
MS-DOS 1.25 also invokes INT 23 on a divide overflow (INT 00)
DOS remembers the stack pointer before calling INT 23, and if it is
not the same on return, pops and discards the top word; this is what
permits a return with RETF as well as IRET or RETF 2
any DOS call may safely be made within the INT 23 handler, although
the handler must check for a recursive invocation if it does
call DOS
SeeAlso: INT 1B
--------D-24------------------------------------------------------
INT 24 - DOS 1+ - CRITICAL ERROR HANDLER
Note: invoked when a critical (usually hardware) error is encountered; should
never be called directly
SeeAlso: INT 21/AH=95h
80 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
WORD CX
WORD DX
WORD SI
WORD DI
WORD BP
WORD DS
WORD ES
DWORD return address for INT 21 call
WORD flags pushed by INT 21
Handler must return:
AL = action code
00h ignore error and continue processing request
01h retry operation
02h terminate program through the equivalent of INT 21/AH=4Ch
(INT 20h for DOS 1.x)
03h fail system call in progress
SS,SP,DS,ES,BX,CX,DX preserved
Notes: the only DOS calls the handler may make are INT 21/AH=01h-0Ch,30h,59h
if the handler returns to the application by popping the stack, DOS
will be in an unstable state until the first call with AH > 0Ch
for DOS 3.1+, IGNORE (AL=00h) is turned into FAIL (AL=03h) on network
critical errors
if IGNORE specified but not allowed, it is turned into FAIL
if RETRY specified but not allowed, it is turned into FAIL
if FAIL specified but not allowed, it is turned into ABORT
(DOS 3+) if a critical error occurs inside the critical error handler,
the DOS call is automatically failed
--------D-25------------------------------------------------------
INT 25 - DOS 1+ - ABSOLUTE DISK READ (except partitions > 32M)
AL = drive number (00h = A:, 01h = B:, etc)
CX = number of sectors to read
DX = starting logical sector number (0000h - highest sector on drive)
DS:BX -> buffer for data
Return: CF clear if successful
CF set on error
AH = status
80h device failed to respond (timeout)
40h seek operation failed
20h controller failed
10h data error (bad CRC)
08h DMA failure
04h requested sector not found
03h write-protected disk (INT 26 only)
02h bad address mark
01h bad command
AL = error code (same as passed to INT 24 in DI)
AX = 0207h if more than 64K sectors on drive -- use new-style call
may destroy all other registers except segment registers
Notes: original flags are left on stack, and must be popped by caller
this call bypasses the DOS filesystem
examination of CPWIN386.CPL indicates that if this call fails with
error 0408h on an old-style (<32M) call, one should retry the
call with the high bit of the drive number in AL set
BUGS: DOS 3.1 through 3.3 set the word at ES:[BP+1Eh] to FFFFh if AL is an
invalid drive number
DR-DOS 3.41 will return with a jump instead of RETF, leaving the
wrong number of bytes on the stack; use the huge-partition version
(INT 25/CX=FFFFh) for all partition sizes under DR-DOS 3.41
SeeAlso: INT 13/AH=02h,INT 25/CX=FFFFh,INT 26
--------D-25----CXFFFF--------------------------------------------
INT 25 - DOS 3.31+ - ABSOLUTE DISK READ (>32M hard-disk partition)
81 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
CX = FFFFh
AL = drive number (0=A, 1=B, etc)
DS:BX -> disk read packet (see below)
Return: same as above
Notes: partition is potentially >32M (and requires this form of the call) if
bit 1 of device attribute word in device driver is set
original flags are left on stack, and must be removed by caller
this call bypasses the DOS filesystem
SeeAlso: INT 13/AH=02h,INT 25,INT 26/CX=FFFFh
82 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
83 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
01h
can not pop up at this time, try again later
02h
can not pop up yet, will do so when able
03h
already popped up
04h
unable to pop up, user intervention required
BX = standard reason code
0000h unknown failure
0001h interrupt chain passes through memory
which must be swapped out to pop up
0002h swap-in failed
CX = application's reason code if nonzero
FFh TSR popped up and was exited by user
BX = return value
0000h no return value
0001h TSR unloaded
0002h-00FFh reserved
0100h-FFFFh application-dependent
04h determine chained interrupts
BL = interrupt number (except 2Dh)
Return: AL = status
00h not implemented
01h (obsolete) unable to determine
02h (obsolete) interrupt hooked
03h (obsolete) interrupt hooked, address returned
DX:BX -> TSR's interrupt BL handler
04h list of hooked interrupts returned
DX:BX -> interrupt hook list (see below)
FFh interrupt not hooked
Notes: since INT 2D is known to be hooked, the resident code
need not test for BL=2Dh (to minimize its size), and
the return value is therefore undefined in that case.
BL is ignored if the TSR returns AL=04h; in that case,
the caller needs to scan the return list rather than
84 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
85 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
86 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
87 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
88 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
89 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
90 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
91 de 92 12/10/00 19:29
Apéndice VIII - FUNCIONES DEL SISTEMA, LA BIOS Y EL DOS ALUDIDAS EN ESTE LIBRO file:///C|/librosVirtuales/UniversoDigital/ap08.html
Nots: many BIOSes turn off the periodic interrupt in the INT 70h handler
unless in an event wait (see INT 15/AH=83h or INT 15/AH=86h).
may be masked by setting bit 0 on I/O port A1h
SeeAlso: INT 08,INT 0F"HP 95LX",INT 15/AH=01h"Amstrad",INT 15/AH=83h
SeeAlso: INT 15/AH=86h,INT 1A/AH=02h,INT 58"DESQview"
--------H-71------------------------------------------------------
INT 71 - IRQ9 - REDIRECTED TO INT 0A BY BIOS
Notes: may be masked by setting bit 1 on I/O port A1h
the default BIOS handler invokes INT 0A for compatibility, since the
pin for IRQ2 on the PC expansion bus became the pin for IRQ9 on the
AT expansion bus.
under DESQview, only the INT 15h vector and BASIC segment address (the
word at 0000h:0510h) may be assumed to be valid for the handler's
process
SeeAlso: INT 0A,INT 59
92 de 92 12/10/00 19:29
Apéndice IX - ESPECIFICACIONES XMS Y EMS: TODAS SUS FUNCIONES file:///C|/librosVirtuales/UniversoDigital/ap09.html
--------m-2F4300--------------------------------------------------
INT 2F - EXTENDED MEMORY SPECIFICATION (XMS) v2+ - INSTALLATION
CHECK
AX = 4300h
Return: AL = 80h XMS driver installed
AL <> 80h no driver
Notes: XMS gives access to extended memory and noncontiguous/nonEMS memory
above 640K
this installation check DOES NOT follow the format used by other
software
SeeAlso: AX=4310h
Index: installation check;XMS version 2+
--------m-2F4310--------------------------------------------------
INT 2F - EXTENDED MEMORY SPECIFICATION (XMS) v2+ - GET DRIVER
ADDRESS
AX = 4310h
Return: ES:BX -> driver entry point
Note: HIMEM.SYS v2.77 chains to previous handler if AH is not 00h or 10h
SeeAlso: AX=4300h
Perform a FAR call to the driver entry point with AH set to the function code
AH function
00h Get XMS version number
Return: AX = XMS version (in BCD, AH=major, AL=minor)
BX = internal revision number
DX = 0001h if HMA (1M to 1M + 64K) exists
0000h if HMA does not exist
01h Request High Memory Area (1M to 1M + 64K)
DX = memory in bytes (for TSR or device drivers)
FFFFh if application program
Return: AX = 0001h success
= 0000h failure
BL = error code (80h,81h,90h,91h,92h) (see below)
02h Release High Memory Area
Return: AX = 0001h success
= 0000h failure
BL = error code (80h,81h,90h,93h) (see below)
03h Global enable A20, for using the HMA
Return: AX = 0001h success
= 0000h failure
BL = error code (80h,81h,82h) (see below)
04h Global disable A20
Return: AX = 0001h success
= 0000h failure
BL = error code (80h,81h,82h,94h) (see below)
05h Local enable A20, for direct access to extended memory
Return: AX = 0001h success
= 0000h failure
BL = error code (80h,81h,82h) (see below)
06h Local disable A20
Return: AX = 0001h success
= 0000h failure
BL = error code (80h,81h,82h,94h) (see below)
07h Query A20 state
Return: AX = 0001h enabled
= 0000h disabled
1 de 13 12/10/00 19:30
Apéndice IX - ESPECIFICACIONES XMS Y EMS: TODAS SUS FUNCIONES file:///C|/librosVirtuales/UniversoDigital/ap09.html
2 de 13 12/10/00 19:30
Apéndice IX - ESPECIFICACIONES XMS Y EMS: TODAS SUS FUNCIONES file:///C|/librosVirtuales/UniversoDigital/ap09.html
3 de 13 12/10/00 19:30
Apéndice IX - ESPECIFICACIONES XMS Y EMS: TODAS SUS FUNCIONES file:///C|/librosVirtuales/UniversoDigital/ap09.html
4 de 13 12/10/00 19:30
Apéndice IX - ESPECIFICACIONES XMS Y EMS: TODAS SUS FUNCIONES file:///C|/librosVirtuales/UniversoDigital/ap09.html
by the processor
5 de 13 12/10/00 19:30
Apéndice IX - ESPECIFICACIONES XMS Y EMS: TODAS SUS FUNCIONES file:///C|/librosVirtuales/UniversoDigital/ap09.html
--------m-6742----------------------------------------------------
INT 67 - LIM EMS - GET NUMBER OF PAGES
AH = 42h
Return: AH = status (see also AH=40h)
00h function successful
BX = number of unallocated pages
DX = total number of pages
BUG: DOS 6.0 EMM386.EXE causes a system lock-up or reboot if in AUTO mode
when this call is made; use AH=46h to ensure that EMM386 is ON
before making this call
SeeAlso: INT 2F/AX=2702h
--------m-6743----------------------------------------------------
INT 67 - LIM EMS - GET HANDLE AND ALLOCATE MEMORY
AH = 43h
BX = number of logical pages to allocate
Return: AH = status (00h,80h,81h,84h,85h,87h,88h,89h) (see AH=40h)
DX = handle if AH=00h
SeeAlso: AH=45h
--------m-6744----------------------------------------------------
INT 67 - LIM EMS - MAP MEMORY
AH = 44h
AL = physical page number (0-3)
BX = logical page number
or FFFFh to unmap (QEMM)
DX = handle
Return: AH = status (00h,80h,81h,83h,84h,8Ah,8Bh) (see AH=40h)
SeeAlso: AH=69h
--------m-6745----------------------------------------------------
INT 67 - LIM EMS - RELEASE HANDLE AND MEMORY
AH = 45h
DX = EMM handle
Return: AH = status (00h,80h,81h,83h,84h,86h) (see AH=40h)
SeeAlso: AH=43h
--------m-6746----------------------------------------------------
INT 67 - LIM EMS - GET EMM VERSION
AH = 46h
Return: AH = status (00h,80h,81h,84h) (see AH=40h)
AL = EMM version number if AH=00h
--------m-6747----------------------------------------------------
INT 67 - LIM EMS - SAVE MAPPING CONTEXT
AH = 47h
DX = handle
Return: AH = status (see below)
SeeAlso: AH=48h
6 de 13 12/10/00 19:30
Apéndice IX - ESPECIFICACIONES XMS Y EMS: TODAS SUS FUNCIONES file:///C|/librosVirtuales/UniversoDigital/ap09.html
--------m-6749----------------------------------------------------
INT 67 - LIM EMS - reserved - GET I/O PORT ADDRESSES
AH = 49h
Note: defined in EMS 3.0, but undocumented in EMS 3.2
--------m-674A----------------------------------------------------
INT 67 - LIM EMS - reserved - GET TRANSLATION ARRAY
AH = 4Ah
Note: defined in EMS 3.0, but undocumented in EMS 3.2
--------m-674B----------------------------------------------------
INT 67 - LIM EMS - GET NUMBER OF EMM HANDLES
AH = 4Bh
Return: AH = status (see below)
BX = number of EMM handles if AH=00h
7 de 13 12/10/00 19:30
Apéndice IX - ESPECIFICACIONES XMS Y EMS: TODAS SUS FUNCIONES file:///C|/librosVirtuales/UniversoDigital/ap09.html
8 de 13 12/10/00 19:30
Apéndice IX - ESPECIFICACIONES XMS Y EMS: TODAS SUS FUNCIONES file:///C|/librosVirtuales/UniversoDigital/ap09.html
9 de 13 12/10/00 19:30
Apéndice IX - ESPECIFICACIONES XMS Y EMS: TODAS SUS FUNCIONES file:///C|/librosVirtuales/UniversoDigital/ap09.html
10 de 13 12/10/00 19:30
Apéndice IX - ESPECIFICACIONES XMS Y EMS: TODAS SUS FUNCIONES file:///C|/librosVirtuales/UniversoDigital/ap09.html
11 de 13 12/10/00 19:30
Apéndice IX - ESPECIFICACIONES XMS Y EMS: TODAS SUS FUNCIONES file:///C|/librosVirtuales/UniversoDigital/ap09.html
00h successful
80h internal error
81h hardware failure
84h undefined function requested
85h no more handles available
87h insufficient memory pages in system
88h insufficient memory pages available
8Fh undefined subfunction
--------m-675B----------------------------------------------------
INT 67 - LIM EMS 4.0 - ALTERNATE MAP REGISTER SET
AH = 5Bh
AL = subfunction
00h get alternate map register set
Return: BL = current active alternate map register set number
ES:DI -> map register context save area if BL=00h
01h set alternate map register set
BL = new alternate map register set number
ES:DI -> map register context save area if BL=0
02h get alternate map save array size
Return: DX = array size in bytes
03h allocate alternate map register set
Return: BL = number of map register set; 00h = not supported
04h deallocate alternate map register set
BL = number of alternate map register set
Return: AH = status (00h,80h,81h,84h,8Fh,9Ah-9Dh,A3h,A4h) (see below)
Note: this function is for use by operating systems only, and can be
enabled or disabled at any time by the operating system
12 de 13 12/10/00 19:30
Apéndice IX - ESPECIFICACIONES XMS Y EMS: TODAS SUS FUNCIONES file:///C|/librosVirtuales/UniversoDigital/ap09.html
AH = 5Ch
Return: AH = status (see below)
13 de 13 12/10/00 19:30
Apéndice X - JUEGO DE CARACTERES ASCII EXTENDIDO file:///C|/librosVirtuales/UniversoDigital/ap10.html
1 de 2 12/10/00 19:31
Apéndice X - JUEGO DE CARACTERES ASCII EXTENDIDO file:///C|/librosVirtuales/UniversoDigital/ap10.html
2 de 2 12/10/00 19:31
2MF.C file:///C|/librosVirtuales/UniversoDigital/Listados.html
Listado 2MF.C
/*-------------------------------------------------------------------\
| |
| ||||| | | |==== |
| | || || | |
| ||||| | | | |== |
| | | | | |
| ||||| | | | |
| |
| 2MF.C 3.0 - UTILIDAD DE FORMATEO DE DISQUETES 2M |
| |
| (C) 1994 Ciriaco García de Celis. |
| |
| - Para cualquier Turbo C o Borland C en modelo de memoria LARGE. |
| - Este programa se compila abriendo un proyecto e introduciendo |
| en él 2MF.C y 2MFKIT.OBJ |
| - Importante: no activar ciertas optimizaciones que no lo están |
| por defecto (como la de alineamiento a palabra o la de salto). |
| |
| - NOTA: Las funciones de bajo nivel que acceden directamente a |
| la controladora de disquetes no son indispensables, tan |
| sólo se emplean para producir menos ruido al detectar |
| la introducción de un nuevo disquete en la unidad. |
| |
| Este programa detecta además la presencia de una posible |
| utilidad de intercambio de unidades A:-B: llamada FDSWAP |
| para que en caso de estar activado dicho intercambio sea |
| posible acceder a la unidad física correspondiente. |
| |
\-------------------------------------------------------------------*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dos.h>
#include <bios.h>
#include <time.h>
#include <alloc.h>
#include <conio.h>
#include <io.h>
#include <fcntl.h>
char NumFats;
short FichRaiz, NumSect;
char MediaId;
short SectFat, SectPista, Caras;
long Especiales, Sect32;
char Unidad, Reservado, Flag;
long NumSerie;
char Titulo[11], TipoFat[8];
char Flags;
char CheckSum;
char VersionFmt, FlagWr, VelPista0, VelPistaX;
short OffsetJmp, OffsetPista0, OffsetPistaX, OffsetListaTam;
short FechaF;
short HoraF;
char Resto[512-BOOT2M]; /* depende del tamaño de lo anterior */
} Boot;
if (!Hay2m())
if (!Hay2mBoot()) {
if (sp)
printf(" 2M ó 2MX 3.0 no está instalado, imposible
formatear.\n");
else
printf(" 2M or 2MX 3.0 is not installed, impossible to
format.\n");
exit(128);
}
else {
if (sp)
printf(" Modo SuperBOOT: instale 2M para dar formato.\n");
else
printf(" SuperBOOT mode: needed to install 2M to
format.\n");
exit(127);
}
if (!cmd.NoPausa) {
if (sp)
printf(" Pulsa una tecla para formatear en");
else
printf(" Press any key to format on");
printf(" %c:", cmd.Unidad+'A');
salir=getch()==27;
}
else
salir=0;
detectar = (cmd.HD==-1);
while (!salir) {
if (detectar) DetectaMedio (&cmd, §or0);
CrearSector0 (§or0, cmd);
if (!cmd.Silencioso) SonidoSube();
switch (result=FormatearDisco (§or0, fat, buffer, &cmd,
&bytes_err, &sg)) {
case 0: InformeDisco (§or0, &cmd, bytes_err, sg);
if (!cmd.Silencioso) SonidoBaja();
if (cmd.Tipoetiq==2) IncrementarEtiqueta (&cmd);
disquetes++;
break;
case 1: DiagnosticoError (result);
break;
default: DiagnosticoError (result);
if (!cmd.Silencioso) SonidoError(); break;
}
if (cmd.NoTecla)
salir=1;
else {
if (sp)
printf("\n Introduce otro disquete para formatear en");
else
printf("\n Please insert another disk to format in");
printf(" %c:", cmd.Unidad+'A');
ViejaInt24=getvect(0x24);
setvect (0x24, NuevaInt24); /* evitar error crítico */
CardWare (argv[0], disquetes); /* intentar actualizar 2MF.EXE */
setvect (0x24, ViejaInt24);
}
cmd->Unidad=cmd->TipoFmt=cmd->ED=cmd->NoVerify=cmd->MarcaPoco=0;
cmd->HD=-1; cmd->Pistas=82;
cmd->FichRaiz=cmd->Silencioso=cmd->NoFlash=cmd->NoPausa=\
cmd->NoTecla=cmd->Tipoetiq=0;
cmd->X=cmd->Y=cmd->G=-1;
if (!error) {
for (pm=1; pm<argc; pm++) {
if ((strstr(argv[pm],"/L")!=NULL) || (strstr(argv[pm],"/l")!=NULL))
{
strncpy (cmd->Volumen, &argv[pm][3], 11);
cmd->Volumen[11]=0;
while (strlen(cmd->Volumen)<11) strcat(cmd->Volumen, " ");
cmd->Tipoetiq=1;
continue;
}
else if ((strstr(argv[pm],"/V")!=NULL) ||
(strstr(argv[pm],"/v")!=NULL)) {
strncpy (cmd->Volumen, &argv[pm][3], 11);
cmd->Volumen[11]=0;
while (strlen(cmd->Volumen)<11) strcat(cmd->Volumen, " ");
cmd->Tipoetiq=2;
continue;
}
strupr (argv[pm]);
if (strstr(argv[pm],"/?")!=NULL) hlp++;
else if ((strstr(argv[pm],"/H")!=NULL) && (strlen(argv[pm])==2))
hlp++;
else if ((strstr(argv[pm],"A:")!=NULL) ||
(strstr(argv[pm],"B:")!=NULL)) cmd->Unidad=*argv[pm]-'A';
else if (strstr(argv[pm],"/HD")!=NULL) cmd->HD=1;
else if (strstr(argv[pm],"/DD")!=NULL) cmd->HD=0;
else if (strstr(argv[pm],"/D0")!=NULL) cmd->HD=2;
else if (strstr(argv[pm],"/D1")!=NULL) cmd->HD=3;
else if (strstr(argv[pm],"/F")!=NULL) cmd->TipoFmt=0;
else if (strstr(argv[pm],"/M")!=NULL) cmd->TipoFmt=1;
else if (strstr(argv[pm],"/ED")!=NULL) cmd->ED=1;
else if (strstr(argv[pm],"/N")!=NULL) cmd->NoVerify=1;
else if (strstr(argv[pm],"/W")!=NULL) cmd->MarcaPoco=1;
else if (strstr(argv[pm],"/T")!=NULL)
cmd->Pistas = atoi (&argv[pm][3]);
else if (strstr(argv[pm],"/R")!=NULL)
cmd->FichRaiz = atoi (&argv[pm][3]);
else if (strstr(argv[pm],"/S")!=NULL) { cmd->Silencioso=1; id++;
}
else if (strstr(argv[pm],"/K")!=NULL) cmd->NoPausa=1;
else if (strstr(argv[pm],"/J")!=NULL) cmd->NoTecla=1;
else if (strstr(argv[pm],"/Z")!=NULL) cmd->NoFlash=1;
else if (strstr(argv[pm],"/X")!=NULL) cmd->X=atoi(&argv[pm][3]);
else if (strstr(argv[pm],"/Y")!=NULL) cmd->Y=atoi(&argv[pm][3]);
else if (strstr(argv[pm],"/G")!=NULL) cmd->G=atoi(&argv[pm][3]);
else if (strstr(argv[pm],"/I")!=NULL) { sp^=1; id++; }
else error=1;
}
}
if (hlp) Ayuda();
if (sp)
printf("\n2MF 3.0 - Utilidad de formateo de disquetes 2M
(ESC Salir)\n");
else
printf("\n2MF 3.0 - Format utility program for 2M diskettes
(ESC Aborts)\n");
if (error==1) {
if (sp)
printf(" Error de sintaxis. Ejecute 2MF /?.\n");
else
printf(" Incorrect parameter(s). Execute 2MF /?.\n");
exit (2);
}
if (error==-1) {
if (sp)
printf(" Error: Los parámetros deben separarse por
espacios.\n");
else
printf(" Error: Parameters must be separated by blank
spaces.\n");
exit (2);
}
if (TipoDrive(cmd->Unidad)==0) {
if (sp)
printf(" La unidad física indicada no existe.\n");
else
printf(" Physical drive indicated does not exist.\n");
exit (2);
}
if ((TipoDrive(cmd->Unidad)!=2) && (TipoDrive(cmd->Unidad)<4)) {
if (sp)
printf(" La unidad indicada no es de alta densidad.\n");
else
printf(" Drive indicated it is not high density one.\n");
exit (2);
}
if ((TipoDrive(cmd->Unidad)<5) && (cmd->ED==1)) {
if (sp)
printf(" Necesaria unidad de 2.88M para formato ED.\n");
else
printf(" Needs a 2.88M drive to perform ED format.\n");
exit (2);
}
if ((cmd->Pistas<80) || (cmd->Pistas>86)) {
if (sp)
printf(" Error: Número de pistas incorrecto.\n");
else
printf(" Error: Incorrect number of tracks.\n");
exit (2);
}
if (cmd->FichRaiz && ((cmd->FichRaiz<1) || (cmd->FichRaiz>240))) {
if (sp)
printf(" Error: Nº de ficheros en directorio raiz erróneo.\n");
else
printf(" Error: Bad number of files in root directory.\n");
exit (2);
}
}
void Ayuda()
{
if (sp) {
printf("\n\n"
" 2MF 3.0 - UTILIDAD ESTANDAR DE FORMATEO DE DISQUETES
PARA 2M\n"
" (C) 1994 Ciriaco García de Celis - Grupo Universitario de
Informática\n"
" C/Renedo, 2, 4-C; 47005 Valladolid (España) - ciri@gui.uva.es
- 2:341/21.8\n\n"
" 2MF U: [/HD|DD|ED] [/F|M] [/N] [/L|V=etiq] [/S] [/Z] [/R=nn]
[/T=nn] [/K] [/J]\n\n"
" Este programa formatea disquetes a una mayor capacidad y/o
velocidad de la\n"
" normal. Para que estos nuevos disquetes funcionen debe estar
instalado 2M en\n"
" memoria. Alternativamente, si son de alta densidad se pueden
dejar dentro de\n"
" la unidad A: y reinicializar el ordenador, que botará pese
a todo del disco\n"
" duro y podrá acceder a los disquetes 2M sin problemas en
lectura/escritura.\n\n"
" /HD Formateo en alta densidad (por defecto si 2MF no detecta
la densidad).\n"
" /DD Fuerza el formateo en doble densidad (aunque 2MF quizá
la detecte).\n"
" /ED Formatear disquetes de 3½-ED (3608K por defecto o 3772K
indicando /M).\n"
" /F Disquetes rápidos y seguros -por defecto- (5¼:820-1476K,
3½:984-1804K).\n"
" /M Formatear disquetes a la máxima capacidad (5¼:902-1558K,
3½:1066-1886K).\n"
" /N No verificar el disquete destino (peligroso en modo
/M).\n"
" /L Poner etiqueta de volúmen al disco destino (minúsculas
permitidas).\n"
" /V Etiqueta incremental en series de discos (si termina en
número).\n"
" /S Funcionamiento silencioso /Z Evitar parpadeo de
led de disco.\n"
" /R Elegir nº ficheros raíz (1-240) /T Cambiar número de
pistas (80-86).\n"
" /K No realizar pausa inicial /J No realizar pausa
final.\n");
}
else {
printf("\n\n"
" 2MF 3.0 - STANDARD FORMAT UTILITY FOR 2M
DISKETTES\n"
" (C) 1994 Ciriaco García de Celis - Grupo Universitario de
Informática\n"
" C/Renedo, 2, 4-C; 47005 Valladolid (Spain) - ciri@gui.uva.es
- 2:341/21.8\n\n"
" 2MF U: [/HD|DD|ED] [/F|M] [/N] [/L|V=label] [/S][/Z] [/R=nn]
[/T=nn] [/K][/J]\n\n"
" This program formats diskettes at a higher capacity and/or
speed than the\n"
" normal ones. 2M must be installed on memory to provide
support for the new\n"
" diskettes. Also, high-density diskettes can be left into A:
if (r.x.ax==0xFFFF)
if ((peek(s.es,r.x.di-4)==9002) && (peek(s.es,r.x.di-2)==10787))
if (strstr (MK_FP(s.es, r.x.di),":FDSWAP:")) instalado=1;
}
return ((instalado) && (peekb(s.es, peek(s.es,r.x.di-6)-1)==1));
}
if (cmd.HD>1) cmd.HD=0;
tabla=cmd.HD+cmd.ED; /* seleccionar tabla de datos */
if (TipoDrive(cmd.Unidad)<3)
tipo=0; /* 5¼ */
else
tipo=1; /* 3½ */
}
ch=1+cmd.HD;
if (TipoDrive(cmd.Unidad)>2) ch+=2; if (!cmd.TipoFmt) ch+=4;
if (cmd.ED) ch=10-cmd.TipoFmt;
id[6]=(ch/10)+'0'; id[7]=(ch % 10)+'0'; strncpy (s0->IdSis, id, 8);
s0->BytesSect=512;
s0->SectCluster = s0->SectReserv = 1; s0->NumFats=2;
if (cmd.ED) s0->SectCluster=2;
if (!cmd.FichRaiz)
s0->FichRaiz=infofis[tipo][tabla][cmd.TipoFmt][0][1];
else
if (cmd.FichRaiz % 16)
s0->FichRaiz=((cmd.FichRaiz >> 4) + 1) << 4;
else
s0->FichRaiz=cmd.FichRaiz;
if (ch==6)
s0->MediaId=0xF0; /* compatible SCANDISK */
else
s0->MediaId=0xFA; /* compatible SCANDISK */
s0->SectPista=infofis[tipo][tabla][cmd.TipoFmt][0][0];
s0->Caras=2;
s0->NumSect=cmd.Pistas*s0->Caras*s0->SectPista;
if (cmd.Tipoetiq)
strncpy (s0->Titulo, cmd.Volumen, 11);
else
strncpy (s0->Titulo, "NO NAME ", 11);
s0->VersionFmt=infofis[tipo][tabla][cmd.TipoFmt][0][2];
s0->FlagWr=infofis[tipo][tabla][cmd.TipoFmt][0][3];
s0->VelPista0=infofis[tipo][tabla][cmd.TipoFmt][0][4];
s0->VelPistaX=infofis[tipo][tabla][cmd.TipoFmt][0][5];
s0->Resto[0]=infofis[tipo][tabla][cmd.TipoFmt][1][0];
s0->Resto[1]=infofis[tipo][tabla][cmd.TipoFmt][1][1];
ch=infofis[tipo][tabla][cmd.TipoFmt][1][2];
inc=infofis[tipo][tabla][cmd.TipoFmt][1][3];
ini=tam+2; fin=ini+s0->Resto[0]; k=0;
for (i=j=0; j<s0->Resto[0]; j++) {
s0->Salto[ini+i]=ch++; if (ch>s0->Resto[0]) ch=1;
i+=inc; if (ini+i>=fin) i=++k;
}
ini=fin; s0->OffsetPistaX=ini;
if (!s0->FlagWr) {
k=infofis[tipo][tabla][cmd.TipoFmt][2][0]; j=5;
for (i=0; i<j; i++)
s0->Salto[ini+i]=infofis[tipo][tabla][cmd.TipoFmt][2][i];
if (cmd.X!=-1) s0->Salto[ini+3]=cmd.X;
if (cmd.Y!=-1) s0->Salto[ini+4]=cmd.Y;
}
else {
k=infofis[tipo][tabla][cmd.TipoFmt][2][2]; j=(k+1)*3;
for (i=0; i<3; i++)
s0->Salto[ini+i]=infofis[tipo][tabla][cmd.TipoFmt][2][i];
m=129;
for (i=3; i<=k*3; i+=3) {
s0->Salto[ini+i]=m;
s=infofis[tipo][tabla][cmd.TipoFmt][2][i/3+2];
s0->Salto[ini+i+1]=s;
t=infofis[tipo][tabla][cmd.TipoFmt][3][s-1];
switch (t) {
case 0: m+=1; break; case 1: m+=2; break;
case 2: m+=3; break; case 3: m+=6; break;
case 4: m+=11; break; case 5: m+=22; break;
}
s0->Salto[ini+i+2]=t;
}
}
if (cmd.G!=-1) s0->Salto[ini+1]=cmd.G;
fin=ini+j;
ini=fin; s0->OffsetListaTam=ini;
if (!s0->FlagWr)
for (i=0; i<k; i++)
s0->Salto[ini+i]=infofis[tipo][tabla][cmd.TipoFmt][2][2];
else
for (i=0; i<k; i++)
s0->Salto[ini+i]=infofis[tipo][tabla][cmd.TipoFmt][3][i];
fin=ini+k;
ini=fin; s0->OffsetJmp=ini;
s0->Salto[0]=0xE9;
s0->Salto[1]=(ini-3) % 256; s0->Salto[2]=(ini-3) >> 8;
if (cmd.HD == 0) {
p=(char far *) &BootDDPrg; k=BootDDPrgLong; }
else {
p=(char far *) &BootHDPrg; k=BootHDPrgLong; }
/* hacer reset */
cmd->ED=0; cmd->HD=1;
if ((sg=ValeDensidad (sector0, cmd))==0) break; /* vale HD */
if (kbhit()) if (getch()==27) break;
if ((sg==3) || (sg==6) || (sg==128)) break; /* error */
cmd->HD=0;
if (!ValeDensidad (sector0, cmd)) break; /* vale DD */
cmd->HD=1; if (kbhit()) if (getch()==27) break;
cmd->HD=2;
if (!ValeDensidad (sector0, cmd)) break; /* vale D0 */
cmd->HD=1; if (kbhit()) if (getch()==27) break;
cmd->ED=1;
if (!ValeDensidad (sector0, cmd)) break; /* vale ED */
cmd->HD=1; cmd->ED=0; if (kbhit()) if (getch()==27) break;
}
}
if (cmd->G!=-1)
if (sp)
printf("\r AVISO: ¡Valor de GAP alterado con opción /G!\n");
else
printf("\r WARNING: GAP value modified with /G switch!\n");
if (cmd->HD>1)
if (sp)
printf("\r AVISO: ¡Parámetro indocumentado /D%d activo!\n",
cmd->HD-2);
else
printf("\r WARNING: Undocumented /D%d switch activated!\n",
cmd->HD-2);
if (cmd->MarcaPoco)
if (sp)
printf("\r AVISO: ¡Parámetro indocumentado /W activo!\n");
else
printf("\r WARNING: Undocumented /W switch activated!\n");
if ((cmd->X!=-1) || (cmd->Y!=-1))
if (sp)
printf("\r AVISO: ¡Parámetro indocumentado /X ó /Y activo!\n");
else
printf("\r WARNING: Undocumented /X or /Y switch activated!\n");
if (sp)
printf("\r Formateo de disquete ");
else
printf("\r Formatting ");
if (sp)
printf(" en %c: con %dK \n",
cmd->Unidad+'A', sector0->NumSect>>1);
else
printf(" diskette on %c: with %dK \n",
cmd->Unidad+'A', sector0->NumSect>>1);
cilindros=sector0->NumSect/(sector0->SectPista*sector0->Caras);
spista=sector0->SectPista; *bytes_def=0L;
fases=1L*cilindros*sector0->Caras*(1+(1-cmd->NoVerify)+sector0->FlagWr);
fase=0L;
tini=*cbios;
for (cilindro=0; cilindro < cilindros ; cilindro++) {
for (cabezal=0; cabezal<sector0->Caras; cabezal++) {
for (intento=0; intento<3; intento++) {
if (sp)
printf("\r Cilindro %2d - Cara %d [F-] %3lu%%",
cilindro, cabezal, fase*100/fases);
else
printf("\r Cylinder %2d - Side %d [F-] %3lu%%",
cilindro, cabezal, fase*100/fases);
if (error) biosdsk (0, cmd->Unidad, 0, 0, 0, 0, NULL);
t=0; while (bioskey(1)) t=bioskey(0);
if ((t & 0xFF)==0x1B) { error=1; goto AbortFormat; }
else if ((t==0x1000) && (cilindro>1)) goto FinFormat;
error=biosdsk (5, cmd->Unidad, cabezal,
cilindro, 0, 0x7F, (unsigned char far *) sector0);
if (sector0->FlagWr==1) if (!error && (cilindro | cabezal)) {
printf ("\b\b\b\b\b\b\b\b\bI-] %3lu%%",(fase+1)*100/fases);
error=biosdsk (3, cmd->Unidad, cabezal | 0x80,
cilindro, 1, spista, buffer);
}
if (!error&&(!cmd->NoVerify||(cmd->NoVerify && cilindro<2))) {
printf ("\b\b\b\b\b\b\b\b\b-V] %3lu%%",
(fase+1+sector0->FlagWr)*100/fases);
error=biosdsk (2, cmd->Unidad, cabezal,
cilindro, 1, spista, buffer);
}
if (!error) break;
}
if (error)
if ((error==128) || (error==3) || (error==6))
goto AbortFormat; /* error fatal */
else
if (!MarcaFat(cmd->Unidad, cmd->MarcaPoco, sector0,
cilindro, cabezal, fat, buffer, bytes_def))
goto AbortFormat; /* error en áreas del sistema */
fase+=(1+(1-cmd->NoVerify)+sector0->FlagWr);
}
hist[cilindro]=*cbios;
tiempo=(*cbios-tini)*10/182;
printf(" [%2lu:%02lu ]", tiempo/60, tiempo % 60);
if (cilindro>5) {
rest=(*cbios-hist[cilindro-5])*(cilindros-cilindro)*10/910;
printf("\b+%2lu:%02lu =%2lu:%02lu ]", rest/60, rest % 60,
(tiempo+rest)/60, (tiempo+rest) % 60);
}
if (!error && (cilindro>79)) /* verificar siempre aquí */ {
error=biosdsk (2, cmd->Unidad, 0, cilindro-1, 1, spista, buffer);
if (error) { /* no soportadas tantas pistas */
cilindros=cilindro; cilindro-=2;
biosdsk (0, cmd->Unidad, 0, 0, 0, 0, NULL);
}
}
*segundos=(*cbios-tini)*10/182;
return (error);
}
cilindros=s0->NumSect/(s0->SectPista*s0->Caras);
if (sp) {
printf ("\r Tiempo transcurrido formateando %2d:%02d\n",
tiempo/60, tiempo % 60);
printf (" Volúmen con número de serie %04X-%04X",
(int) (s0->NumSerie >> 16), (int) s0->NumSerie);
if (strstr(label, "NO NAME ")==NULL)
printf (" y etiqueta %11s\n", label);
else
printf("\n");
printf ("%9d ficheros permitidos en el raíz.\n",
s0->FichRaiz);
printf ("%9d unidades de asignación.\n", ua);
printf ("%9d bytes por unidad de asignación.\n",
s0->SectCluster*512);
printf ("%9lu bytes totales en el disco.\n", bt);
printf ("%9lu bytes en sectores defectuosos.\n", bd);
printf ("%9lu bytes disponibles en el disco.\n", bt-bd);
if (cilindros!=cmd->Pistas)
printf(" Aviso: formateado con %dK (esta unidad sólo"
" soporta %d pistas).\n", s0->NumSect>>1, cilindros);
}
else {
printf ("\r Time elapsed in the process %2d:%02d\n",
tiempo/60, tiempo % 60);
printf (" Volume serial number is %04X-%04X",
while (j)
if ((cmd->Volumen[j] >= '0') && (cmd->Volumen[j] <= '8')) {
cmd->Volumen[j]++;
break;
}
else if (cmd->Volumen[j] == '9') {
cmd->Volumen[j]='0';
j--;
}
else break;
}
tamsys = sector0->NumFats*sector0->SectFat+(sector0->FichRaiz>>4)+1;
r.h.ah=8; r.h.dl=unidad;
int86 (0x13, &r, &r);
int unidad;
Boot *sector0;
unsigned char far *fat1;
unsigned char far *buffer;
{
unsigned char far *p;
int sectpista0=sector0->Salto[sector0->OffsetPista0],
spraiz=sector0->SectFat*2+1,
error;
Root raiz;
struct time h;
struct date f;
p=buffer;
memcpy (p, sector0, 512); /* BOOT físico */
p+=512;
memcpy (p, fat1, sector0->SectFat*512); /* FAT1 (la 2 emulada) */
p+=sector0->SectFat<<9;
memcpy (p, sector0, 512); /* BOOT virtual */
if (sector0->SectPista>=15) /* HD */ {
p+=512;
memcpy (p, &Boot2mCode, Boot2mLong); /* código SuperBOOT */
}
p=buffer+(spraiz<<9);
memcpy (p, &raiz, sizeof(raiz)); /* 1ª entrada ROOT */
void SonidoSube()
{
int frec=50;
SonidoOn();
while (frec<5000) {
Sonido (frec); PicoRetardo(); Sonido (frec+1000); PicoRetardo();
frec+=10;
}
SonidoOff();
}
void SonidoBaja()
{
int frec=6000;
SonidoOn();
while (frec>1050) {
Sonido (frec); PicoRetardo(); Sonido (frec-1000); PicoRetardo();
frec-=10;
}
SonidoOff();
}
void SonidoError()
{
int frec1=50, frec2=6000;
SonidoOn();
while (frec1<5000) {
Sonido (frec1); PicoRetardo(); Sonido (frec1+1000); PicoRetardo();
Sonido (frec2); PicoRetardo(); Sonido (frec2-1000); PicoRetardo();
frec1+=10; frec2-=10;
}
SonidoOff();
}
void SonidoOn()
{
disable(); outportb (0x61, inportb (0x61) | 3); enable();
outportb (0x43, 182); /* preparar canal 2 */
}
void SonidoOff()
{
disable(); outportb (0x61, inportb (0x61) & 0xFC); enable();
}
periodo=1193180L/frecuencia;
outportb (0x42, periodo & 0xFF); outportb (0x42, periodo >> 8);
}
unidad=disquetera;
if (FdswapOn()) unidad^=1; /* unidades intercambiadas por FDSWAP */
do {
i++; t=*cbios;
while ((t==*cbios) && ((rd=inportb(FD_STATUS)>>7)==0));
} while ((i<8) && !rd);
do {
i++; t=*cbios;
while ((t==*cbios) && ((rd=inportb(FD_STATUS)>>7)==0));
} while ((i<8) && !rd);
do {
i++; t=*cbios;
while ((t==*cbios) && !(*irq6 & 0x80));
} while ((i<37) && !(*irq6 & 0x80));
lcad=strlen(cmp)+1;
if ((fich=open(nfich, O_BINARY | O_RDWR))==-1) return;
if (getftime (fich, &fechahora)==-1) { close(fich); return; }
if (lseek (fich, -lcad, SEEK_END)==-1) { close(fich); return; }
if (read (fich, chk, lcad)==-1) { close(fich); return; }
chk[lcad-1]=0;
if (strcmp(chk, cmp)) /* contador no inicializado */ {
write (fich, cmp, lcad);
if (discos) discos--;
}
if (lseek (fich, -1L, SEEK_END)==-1) { close(fich); return; }
if (read (fich, &contador, 1)==-1) { close(fich); return; }
contador+=discos;
if (contador >= CARDWARE) {
contador-=CARDWARE;
if (contador > (CARDWARE>>1)) /* posible fallo extraño */
contador=CARDWARE>>1;
aviso++; /* avisar (si se puede actualizar 2MF.EXE) */
}
if (lseek (fich, -1L, SEEK_END)==-1) { close(fich); return; }
if (write (fich, &contador, 1)==-1) { close(fich); return; }
flushall();
setftime (fich, &fechahora);
close (fich);
if (aviso)
if (sp) {
clrscr();
textcolor (LIGHTCYAN + BLINK); textbackground (BLUE);
gotoxy (27, 5);
cputs(" ¡¡AVISO MUY IMPORTANTE!! ");
textcolor (LIGHTRED); textbackground (BLACK);
gotoxy (15,7); cputs ("Esta copia de 2MF ya ha formateado ");
textcolor (YELLOW); cputs (num); textcolor
(LIGHTRED);
cputs (" disquetes.");
gotoxy (15,8); cputs ("Recuerda que 2M es un programa ");
textcolor (LIGHTGREEN); cputs ("CardWare");
textcolor (LIGHTRED);
cputs (". Si aún");
gotoxy (15,9); cputs ("no has enviado tu ");
textcolor (LIGHTMAGENTA); cputs ("tarjeta
postal"); textcolor (LIGHTRED);
cputs (" al autor, no");
gotoxy (15,10); cputs ("deberías continuar utilizando estos
discos.");
gotoxy (15,12); cputs ("Si ya la has enviado, estoy ");
textcolor (LIGHTCYAN); cputs ("muy contento");
textcolor (LIGHTRED);
cputs (" contigo");
gotoxy (15,13); cputs ("y dentro de otros "); cputs (num);
cputs(" volveré a felicitarte.");
gotoxy (15,15); textcolor (LIGHTGREEN); cputs ("¡Suerte!");
textcolor (WHITE);
gotoxy (1,16);
}
else {
clrscr();
textcolor (LIGHTCYAN + BLINK); textbackground (BLUE);
gotoxy (27, 5);
cputs(" ¡¡VERY IMPORTANT NOTICE!! ");
textcolor (LIGHTRED); textbackground (BLACK);
gotoxy (15,7); cputs ("This 2MF program has already formatted
");
textcolor (YELLOW); cputs (num); textcolor
(LIGHTRED);
cputs (" disks.");
gotoxy (15,8); cputs ("Remember that 2M is a ");
textcolor (LIGHTGREEN); cputs ("CardWare");
textcolor (LIGHTRED);
cputs (" program. If you");
gotoxy (15,9); cputs ("haven't send still your ");
textcolor (LIGHTMAGENTA); cputs ("postcard");
textcolor (LIGHTRED);
cputs (" to the author,");
gotoxy (15,10); cputs ("you musn't continue on using this
diskettes.");
gotoxy (15,12); cputs ("If you have send it yet, I'm ");
textcolor (LIGHTCYAN); cputs ("very happy");
textcolor (LIGHTRED);
cputs (" with you");
gotoxy (15,13); cputs ("and within next "); cputs (num); cputs("
ones I will thank you again.");
gotoxy (15,15); textcolor (LIGHTGREEN); cputs ("Good luck!");
textcolor (WHITE);
gotoxy (1,16);
}
}
if (_osmajor>=3) {
r.x.ax=0x3800; s.ds=FP_SEG(info); r.x.dx=FP_OFF(info);
intdosx (&r, &r, &s);
i=0; while (spl[i++]) if (spl[i-1]==r.x.bx) idioma=1;
}
return (idioma);
}
###################################################################
;+-------------------------------------------------------------------+
;| |
;| ||||| | | |==== | | ==|== ==|== |
;| | || || | | | | | |
;| ||||| | | | |== |=| | | |
;| | | | | | | | | |
;| ||||| | | | | | ==|== | |
;| |
;| FICHERO CON CODIGO ENSAMBLADOR LINKABLE CON 2MF.C |
;| |
;| Código de 2M que será almacenado en los sectores de |
;| los disquetes, sectores de arranque de los mismos y |
;| algunas funciones ASM de utilidad. |
;| |
;| Proceso: |
;| |
;| TASM 2MFKIT /m5 /mx |
;| |
;| El fichero 2MFBOOT.DB que se carga con INCLUDE debe obtenerse |
;| previamente a partir de 2MFBOOT.ASM con ayuda de 2MFBMAKE.BAS |
;| |
;+-------------------------------------------------------------------+
OR AH,70h
PUSH AX
POPF
PUSHF
POP AX
AND AH,0F0h
CMP AH,0F0h ; ¿es PC/XT?
JE xt
MOV CX,33 ; 18/1193180*33*1000 = 0.5 ms
wrf: IN AL,61h
AND AL,10h
CMP AL,AH
JE wrf ; esperar pulso refresco memoria
MOV AH,AL
LOOP wrf
xt: POP AX
RET
_PicoRetardo ENDP
_NuevaInt24 PROC
MOV AL,3 ; error en la función DOS invocada.
IRET
_NuevaInt24 ENDP
_DATA ENDS
END
/*********************************************************************
* *
* 2M-FDTR 2.2 - Cálculo de la tasa de transferencia de disquetes. *
* (C) 1994 Ciriaco García de Celis. *
* *
* Para Borland C++ 2.0 ó superior en modelo de memoria large. *
* *
*********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <dos.h>
#include <alloc.h>
#include <math.h>
#include <string.h>
int evalua_io (int, unsigned char far *, int, int, int, int),
biosdsk (int, int, int, int, int, int, unsigned char far *),
HablaSp (void);
void ayuda (void);
unsigned long tiempo (void);
if ((!strcmp(strupr(argv[1]),"/I")) || (!strcmp(strupr(argv[2]),"/I")))
sp^=1; /* parámetro /I */
unidad=(*argv[1] | 0x20)-'a';
if (dsk.df_sclus==65535) {
if (sp)
printf(" x Error de acceso a la unidad.\n");
else
printf(" x Error on drive access.\n");
exit (3);
}
if ((long) dsk.df_total*dsk.df_sclus>65535L) {
if (sp)
printf(" x Unidades de más de 32M no soportadas.\n");
else
printf(" x Drive above 32M can not be tested.\n");
exit (1);
}
sectores=sector0[24]+256*sector0[25]; cabezales=sector0[26];
cilindros=(sector0[19]+256*sector0[20])/sectores/cabezales;
if (sectores>63) {
if (sp)
printf(" x ¡No soportados más de 63 sectores por pista!.\n");
else
printf(" x Not supported more than 63 sectors per track!.\n");
exit (3);
}
if (sp) {
printf(" x Determinando tasa de transferencia BIOS a disco.\n");
printf(" + Rendimiento en lectura:\n");
}
else {
printf(" x Computing BIOS floppy data transfer rate.\n");
printf(" + Read performance:\n");
}
void ayuda()
{
printf(" (C) 1994 Ciriaco García de Celis.\n");
if (sp) {
printf(" x Indica la unidad A: o B: para medir su
velocidad.\n");
printf(" - El test se realiza accediendo a través de las
funciones BIOS.\n");
printf(" - El buffer E/S no cruza nunca una frontera de DMA
de 64K.\n");
printf(" - El acceso afecta siempre a pistas completas.\n");
printf(" - El software residente puede alterar el
resultado.\n");
printf(" - El test de escritura no se realiza si el disquete
contiene datos.\n");
}
else {
printf(" x Choose drive A: or B: to test it absolute
speed.\n");
printf(" - Test is performed always through BIOS
functions.\n");
printf(" - The I/O buffer never cross a 64K DMA
frontier.\n");
printf(" - Access is done always using the whole track.\n");
printf(" - The TSR software may alter results.\n");
printf(" - Write test is not performed if diskette contains
data.\n");
}
exit (255);
}
asm {
cli
mov al,6
out 43h,al /* enclavamiento contador 0 */
in al,40h
mov ah,al
in al,40h
xchg ah,al
neg ax /* ax = valor del contador 0 del 8254 */
push ds
mov bx,40h
mov ds,bx
mov bx,ds:[6ch] /* bx = contador hora BIOS */
sti
pop ds
mov word ptr tm,ax
mov word ptr tm+2,bx
}
return (tm);
}
tini=tiempo(); res=0;
for (cilindro=1; cilindro<cilindros; cilindro++)
bseg=(512L*nsect*(cilindros-1)*cabezales)/((tfin-tini)/1193180.0);
if (sp)
printf("\r %7.2f segundos =%7.2f Kb/seg [%7.0f bits/seg]\n",
(tfin-tini)/1193180.0, bseg/1024.0, bseg*8);
else
printf("\r %7.2f seconds =%7.2f Kb/sec [%7.0f bits/sec]\n",
(tfin-tini)/1193180.0, bseg/1024.0, bseg*8);
aborta_io:
printf("\r \r");
return (fin_io);
}
if (_osmajor>=3) {
r.x.ax=0x3800; s.ds=FP_SEG(info); r.x.dx=FP_OFF(info);
intdosx (&r, &r, &s);
i=0; while (spl[i++]) if (spl[i-1]==r.x.bx) idioma=1;
}
return (idioma);
}
Listado 2MKERNEL
;+-------------------------------------------------------------------+
;| |
;| ||||| | | | | |==== |=== | | |==== | |
;| | || || | | | | | || | | | |
;| ||||| | | | ||= ||||| |||| | | | ||||| | |
;| | | | | | | | | | || | | |
;| ||||| | | | | |==== | | | | |==== ||||| |
;| |
;| 2MKERNEL - (C) Ciriaco García de Celis. |
;| |
info_drv STRUC
maxs EQU 13 ; máximo 13 sectores físicos/pista
tipo_drv DB ? ; tipo de la disquetera (0 = no hay)
control2m_flag DB OFF ; a ON si 2M controla la unidad
cambio DB ON ; a ON indica cambio de soporte
version_fmt DB ? ; versión del formato de disco 2M
multi_io DB ? ; a 0 si posible acceso multi-sector
chk DB ? ; a 0 si checksum del sector 0 Ok
vunidad EQU THIS WORD
vunidad0 DB ? ; velocidad pista 0
vunidadx DB ? ; velocidad demás pistas
gap DB ? ; GAP entre sectores (leer/escribir)
sectpista DB ? ; sectores lógicos por pista
tabla_tsect DB maxs DUP (?) ; tamaños de sectores 1, 2, ..., N
tam_fat DB ? ; sectores/FAT en la unidad
ENDS
; ***************************************
; * *
; * C O D I G O R E S I D E N T E *
; * *
; ***************************************
ENDIF
IFNDEF SUPERBOOT
format_2m: POPF
PUSH DS ; *
XPUSHA ; **
PUSH CS
POP DS
MOV unidad,DL
CALL set_SI_drv
MOV cilindro,CH
MOV cabezal,DH
OR CH,DH
JNZ format_trx ; no es cilindro 0 y cabezal 0
INC CL
JNZ no_f_chg
MOV [SI].cambio,ON ; simular cambio de disco
JMP fmt_exit
no_f_chg: XPUSHA
MOV AL,1 ; formatear (AH=5)
MOV CH,0
MOV DH,2 ; en cabezal 2 (incorrecto)
PUSHF
CALL ant_int13 ; avisar al DOS del nuevo disco
CLD ; mantener DF=0
STC
CALL reset_drv ; asegurar aceleración motor
XPOPA
CALL set_info ; características nuevo soporte
format_trx: CALL genera_info ; tabla de información formateo
CALL motor_ok ; asegurar que está en marcha
CALL seek_drv
CALL formatea_pista
PUSHF
CLC
CALL motor_off_cnt ; cuenta normal detención motor
POPF
CALL set_err
CALL set_bios_err ; no altera flags
fmt_exit: XPOPA ; **
MOV AH,status
POP DS ; *
RET 2
ges_int13 ENDP
JNC dilucida
POP SI
POPF
STC ; hubo cambio:
MOV AX,600h
RET 2 ; retornar con error
dilucida: CMP CS:[SI].control2m_flag,OFF
JE ges13bios ; la unidad la controla la BIOS
POP SI
POPF
CALL control2m ; la controla 2M
RET 2
ges13bios: POP SI
POPF
JMP CS:ant_int13 ; saltar al gestor de INT 13h
ges_int13 ENDP
ENDIF
set_SI_drv PROC
PUSHF
PUSH BX
MOV BL,DL
MOV BH,0
SHL BX,1
MOV SI,CS:[BX+OFFSET info_ptr]
POP BX
POPF
RET
set_SI_drv ENDP
set_flag_STV PROC
XPUSHA
CALL set_SI_drv
MOV AL,ON ; indicar 2M
JC tipo_stv_ok
MOV AL,OFF ; indicar no 2M
tipo_stv_ok: MOV CS:[SI].control2m_flag,AL
XPOPA
RET
set_flag_STV ENDP
pista0? PROC
PUSH AX
MOV AL,cabezal
OR AL,cilindro
POP AX
RET
pista0? ENDP
leer_lin_camb PROC
XPUSHA ; *
PUSH DS
DDS
MOV AL,1
MOV CL,DL
SHL AL,CL ; bit de motor en 0..3
TEST DS:[3Fh],AL
JNZ rodando ; el motor ya está girando
CLC
CALL motor_off_cnt ; cuenta normal detención motor
rodando: MOV AH,DL
XSHL AH,4
OR AH,AL ; AH = byte BIOS
XSHL AL,4
OR AL,00001100b ; modo DMA, no hacer reset
OR AL,DL ; AL para reg. salida digital
MOV DX,3F2h
CLI
MOV DS:[3Fh],AH ; actualizar variable BIOS
OUT DX,AL ; arrancado motor en la unidad
ADD DX,5
DELAY
IN AL,DX ; leer línea de cambio de disco
STI
TEST AL,80h ; ZF=0 -> cambio de disco
POP DS
XPOPA ; *
RET
leer_lin_camb ENDP
detecta_cambio PROC
XPUSHA ; *
CALL set_SI_drv ; SI -> variables de la unidad
CMP CS:[SI].cambio,ON ; ¿cambio de soporte?
MOV CS:[SI].cambio,OFF
JE hubo_cambio
CALL leer_lin_camb ; leer línea de cambio de disco
JNZ hubo_cambio
XPOPA
CLC ; no hay cambio de disco
RET
hubo_cambio: CLC
CALL set_flag_STV ; CF = 0 -> supuesto no 2M
intenta_io0: PUSH CX
CMP CX,2 ; CF=1 la 3ª vez (a 0 si CX<>1)
CALL reset_drv
MOV [SI].vunidad0,0 ; empezar con 500 Kbit/seg.
intenta_io: MOV AL,0
MOV cilindro,AL
MOV cabezal,AL
MOV sector,1 ; sector de arranque
MOV seccion,AL
MOV secciones,1
MOV orden,F_READ
MOV DI,buffer
CALL direct_acceso
JNE otra_densidad ; es otra densidad de disco
POP CX
MOV BX,buffer
CALL set_info ; características nuevo soporte
CLC
JMP fin_detecta_c ; indicar cambio de disco
otra_densidad: MOV AL,[SI].vunidad0
INC AX ; próxima velocidad
CMP AL,3
JA otro_intento
MOV [SI].vunidad0,AL
JMP intenta_io ; probar otra velocidad
otro_intento: MOV [SI].vunidad0,0
POP CX
LOOP intenta_io0 ; reintento
set_info PROC
XPUSHA
CALL calc_chk
JC set_info_exit ; no es disco 2M
MOV [SI].chk,AL ; anotar checksum
MOV [SI].version_fmt,CL ; y versión del formato
MOV DL,unidad
STC
CALL set_flag_STV ; CF = 1 -> indicar disco 2M
MOV AL,ES:[BX+22] ; tamaño de FAT
MOV [SI].tam_fat,AL
MOV CL,ES:[BX+65] ; CL a 0 si acceso multi-sector
MOV [SI].multi_io,CL
MOV AX,ES:[BX+66]
MOV [SI].vunidad,AX ; velocidad pista 0 / demás
MOV AL,ES:[BX+24]
MOV [SI].sectpista,AL ; sectores/pista
MOV DI,ES:[BX+72]
MOV AL,ES:[BX+DI+1] ; GAP de formateo
MOV AH,AL
AND CL,CL ; CL a 0 si acceso multi-sector
JZ gap_rw_ok ; GAP R/W para /F
ADD AH,190
MOV AL,11
MUL AH ; AX = (190+GAP)*11
SUB AX,2048+62
gap_rw_ok: SHR AL,1 ; GAP R/W para /M
MOV [SI].gap,AL
MOV CX,maxs
MOV DI,ES:[BX+74]
ADD DI,BX
LEA BX,[SI].tabla_tsect
genera_ts: MOV AL,ES:[DI]
MOV [BX],AL
INC BX
INC DI
LOOP genera_ts ; información estructura pistas
set_info_exit: MOV AL,[SI].vunidad0
IFDEF XT
MOV CL,6
SHL AL,CL
ELSE
XSHL AL,6 ; velocidad en bits 7:6
ENDIF
OR AL,00010111b ; establecido otro medio físico
CMP [SI].tipo_drv,2
JA modo_ok ; es unidad de 3½
AND AL,11111000b
OR AL,00000101b ; 1.2 en 1.2
TEST AL,01000000b
JZ modo_ok
XOR AL,00100001b ; 360 en 1.2 y seek * 2
modo_ok: PUSH DS
MOV BX,90h
ADD BL,unidad
DDS
AND BYTE PTR DS:[BX],8 ; respetar bit de 2.88M
OR DS:[BX],AL ; actualizar variable BIOS
POP DS ; con el tipo de densidad
XPOPA
RET
set_info ENDP
calc_chk PROC
XPUSH <SI, DI>
LEA DI,[BX+3] ; DI=BX+3
LEA SI,id_sistema
MOV CX,6
REP CMPSB ; comparar identificación
STC
JNE chk_ret ; el disco no es 2M
XOR AX,AX
MOV CL,ES:[BX+64] ; versión del formateador
CMP CL,6
JB chk_ok ; no usaba este checksum
MOV DI,ES:[BX+68]
chk_sum: DEC DI
ADD AL,ES:[BX+DI]
CMP DI,63
JA chk_sum
chk_ok: CLC
chk_ret: XPOP <DI, SI>
RET
calc_chk ENDP
set_err PROC
XPUSHA
JNC err_ret ; no hay error
CMP status,0 ; ¿'status' ya asignado?
JNE err_retc ; no cambiarlo si es así
MOV AL,BYTE PTR fdc_result+1
AND AL,10110111b ; aislar condiciones de test
LEA BX,lista_errs
MOV CX,9
busca_err: MOV AH,[BX] ; código de error BIOS
SHL AL,1
JC err_ok ; es ese error
INC BX
LOOP busca_err ; buscar otro error
err_ok: OR status,AH
err_retc: STC ; condición de error
err_ret: XPOPA
RET
set_err ENDP
set_bios_err PROC
PUSHF ; *
XPUSHA ; **
PUSH ES ; ***
DES
MOV DI,41h ; bytes de resultados del 765
LEA SI,status ; variable BIOS de status y 7
MOV CX,4 ; bytes: 4 palabras
REP MOVSW
POP ES ; ***
XPOPA ; **
POPF ; *
RET
set_bios_err ENDP
control2m PROC
PUSH DS ; *
XPUSHA ; **
PUSH CS
POP DS
MOV unidad,DL
CALL set_SI_drv ; SI -> variables de la unidad
CMP [SI].chk,0
JE chk_valido ; checksum correcto en sector 0
MOV status,40h ; devolver 'Seek Error' al DOS
JMP exit_2m_ctrl
chk_valido: PUSH AX ; ***
MOV AH,0
MOV numsect,AX ; nº sectores
MOV AL,CH ; cilindro
SHL AL,1
MOV DL,DH
AND DH,01111111b
ADD AL,DH ; cabezal físico
MUL [SI].sectpista
ADD AL,CL ; sector
ADC AH,0
DEC AX ; AX = nº sector DOS
MOV sectini,AX ; 0FFFFh si sector 0 (error)
MOV DI,BX ; ES:DI -> dirección
POP BX ; ***
MOV BL,BH
MOV BH,0
fin_ctrl: CLC
CALL motor_off_cnt ; cuenta normal detención motor
CALL set_bios_err ; actualizar variables BIOS
exit_2m_ctrl: XPOPA ; **
MOV AH,status
POP DS ; *
AND AH,AH
JZ st_ok ; resultado correcto (CF=0)
STC ; error
MOV AL,0 ; 0 sectores movidos
st_ok: RET
calc_iop: SUB CX,AX
INC CX ; CX sectores
CMP CX,numsect
JBE nsect_ok
MOV CX,numsect ; sólo quedan CX
nsect_ok: SUB numsect,CX
ADD sectini,CX
RET
control2m ENDP
ejecuta_io PROC
MOV BX,AX ; AX = sector DOS inicial
CMP AH,0FFh
JE no_cabe ; (acceso a sector BIOS 0)
MOV secciones,CL ; CX sectores (CL realmente)
DIV [SI].sectpista
INC AH ; numerado desde 1...
MOV sector,AH ; ...el resto es el sector
SHR AL,1
MOV cilindro,AL ; cilindro
RCL AL,1
AND AL,1
MOV cabezal,AL ; cabezal
MOV AL,sector
ADD AL,secciones
JC no_cabe ; sector+secciones > 255
DEC AX ; DEC AX = DEC AL
CMP AL,[SI].sectpista
JBE si_cabe
no_cabe: MOV status,4 ; 'sector no encontrado'
JMP fin_io
si_cabe: MOV AL,AH ; sector en AL
CBW ; sección 0 (AH = 0)
CALL pista0?
JZ s_xx ; sector físico en pista/cara 0
LEA BX,[SI].tabla_tsect-1
DEC AX ; AH = 0
resta_secc: INC BX
INC AH
MOV CL,[BX]
SUB CL,2
MOV CH,1
SHL CH,CL
SUB AL,CH
JNC resta_secc ; en las demás pistas
ADD AL,CH
XCHG AH,AL
s_xx: MOV sector,AL ; sector lógico convertido a
MOV seccion,AH ; sector y sección físicas
direct_acceso: CALL motor_ok ; asegurar que está en marcha
MOV AH,0
MOV sector_fin,AH ; no acceder a más de 1 sector
CALL pista0? ; (al menos de momento)
JNZ decide_multi ; no es pista 0
MOV AL,secciones
MOV secciones,AH ; las que restan (AH = 0)
JMP multi_proc
decide_multi: CMP [SI].multi_io,AH ; AH = 0
JNE io_pasos ; acceso sector a sector
CMP seccion,AH
JE multi_acc
CALL acceso_secc ; no acceso a inicio sector
JC fin_io
multi_acc: CMP secciones,AH ; AH = 0
JE fin_io
CALL num_secciones
MOV CL,AL
MOV AL,secciones ; AH = 0
DIV CL
AND AL,AL
JZ io_pasos ; no quedan sectores enteros
MOV secciones,AH ; las que restan
multi_proc: CALL acceso_multi ; de AL sectores
JC fin_io
io_pasos: CMP secciones,0
JE fin_io ; no restan secciones finales
CALL acceso_secc
JNC io_pasos
fin_io: CMP status,0 ; ZF = 1 -> operación correcta
RET
acceso_secc: PUSH AX
CMP orden,F_WRITE ; acabar transferencia sector
JE escritura
CMP orden,F_VERIFY
JE verificacion
CALL leido? ; realizar lectura...
JNC ya_leido ; sector ya en el buffer
hay_que_leer: CALL acceso_sector ; efectuar E/S
JC acc_ret ; ha habido fallo
ya_leido: CALL trans_secc ; buffer -> memoria
JMP acc_ret
escritura: CMP seccion,0
JNE prelectura ; sólo parte del sector cambia
CALL num_secciones
CMP secciones,AL
JAE escribir ; Todo el sector físico cambia
prelectura: CALL leido? ; Leer el sector físico para
JNC escribir ; cambiar sólo una parte de él
MOV orden,F_READ ; de momento leer...
CALL acceso_sector ; efectuar E/S
MOV orden,F_WRITE ; ... restaurar orden original
JC acc_ret ; ha habido fallo
escribir: CALL trans_secc ; memoria -> buffer
CALL acceso_sector ; volcar buffer al disco
JMP acc_ret
verificacion: PUSH BX
MOV BL,seccion
CALL num_secciones
dec_sec_veri: DEC secciones
JZ verifica
INC BX
CMP BL,AL
JB dec_sec_veri
verifica: POP BX
CALL acceso_sector ; leer para forzar verificación
acc_ret: PUSHF
INC sector ; preparado para otro sector
MOV seccion,0 ; desde su primera sección
POPF
POP AX
RET
MOV sector_ini,AH
ADD AL,AH
DEC AX
MOV sector_fin,AL
INC AL
CALL acceso_sector ; sectores no problemáticos
MOV sector,AL
acc_mult_fin: POP AX
RET
ENDIF
ejecuta_io ENDP
trans_secc PROC
XPUSH <AX, BX, CX, SI> ; *
leido? PROC
PUSH AX
MOV AL,buf_unidad
CMP AL,unidad
JNE no_leido ; es en otra unidad
MOV AL,cilindro
MOV AH,cabezal
CMP AX,buf_cilcab
JNE no_leido ; es en otro cilindro/cabezal
MOV AL,buf_sector
CMP AL,sector
JNE no_leido ; es otro sector
POP AX
RET ; está en el buffer
no_leido: STC
POP AX
RET ; sector no leído
leido? ENDP
acceso_sector PROC
XPUSH <AX, BX>
CALL seek_drv ; posicionar el cabezal
JNC en_pista
CMP status,0 ; ¿error ya determinado?
JNE acc_fin_err
OR status,40h ; no: pues 'seek error'
acc_fin_err: STC
JMP acceso_fin
en_pista: CALL pista0?
MOV AL,2
JZ tam_acc_ok ; sectores 512 en cil./cab. 0
LEA BX,[SI].tabla_tsect
ADD BL,sector
ADC BH,0
MOV AL,[BX-1]
tam_acc_ok: MOV tsector,AL
CMP sector_fin,0 ; ¿usar buffer intermedio?
JE acceso_buffer
CALL sector_io
MOV sector_fin,0 ; no acceder a más de 1 sector
PUSHF ; **1
JMP acceso_rep ; en el futuro (por defecto)
acceso_buffer: XPUSH <ES, DI>
PUSH CS
POP ES
MOV DI,buffer ; acceso con buffer auxiliar
MOV AL,sector ; mismo sector inicial/final
MOV sector_ini,AL
MOV sector_fin,AL
CALL sector_io
MOV sector_fin,0
XPOP <DI, ES>
PUSHF ; **2
MOV AL,-1 ; invalidar contenido buffer
JC acceso_anota ; si hay error
CMP orden,F_VERIFY
JE acceso_rep ; nada leído físicamente
MOV AL,unidad
acceso_anota: MOV buf_unidad,AL
MOV AL,cilindro
MOV AH,cabezal
MOV buf_cilcab,AX
MOV AL,sector
MOV buf_sector,AL ; anotado el sector en buffer
acceso_rep: POPF ; ** mucho cuidado con la pila
CALL set_err ; ajustar variable «status»
acceso_fin: XPOP <BX, AX>
RET
acceso_sector ENDP
num_secciones PROC
CALL pista0?
MOV AL,1
JZ num_secc_ok ; sectores 512 en cil./cab. 0
XPUSH <BX, CX>
LEA BX,[SI].tabla_tsect
ADD BL,sector
ADC BH,0
MOV CL,[BX-1]
SUB CL,2
MOV AL,1
SHL AL,CL
XPOP <CX, BX>
num_secc_ok: RET ; resultado en AL
num_secciones ENDP
motor_ok PROC
XPUSHA ; *
PUSH DS ; **
MOV BX,40h
PUSH BX
POP DS
MOV CH,255-18 ; CH = 255 - 1 segundo
CLI
MOV CL,CS:unidad
MOV AL,1
SHL AL,CL
TEST [BX-1],AL ; ¿motor en marcha?
JZ arrancarlo ; arrancarlo
CMP [BX],CH ; Si encendido y acelerado...
JBE ok_motor ; ...seguir
arrancarlo: MOV AH,CL
MOV CL,4
SHL AH,CL ; unidad << 4
OR AL,AH
MOV [BX-1],AL ; nuevo estado motores
MOV BYTE PTR [BX],255 ; asegurar que no se pare
MOV DX,3F2h ; registro de salida digital
ADD CL,CS:unidad
MOV AL,1
SHL AL,CL ; colocar bit del motor
OR AL,CS:unidad ; seleccionar unidad
OR AL,00001100b ; modo DMA, no hacer reset
OUT DX,AL ; poner en marcha el motor
STI
MOV AX,90FDh
CLC
INT 15h ; permitir multitarea
JC ok_motor ; timeout
MOV AX,1000 ; 1 segundo aceleración
CALL retardo ; esperar aceleración disco
ok_motor: MOV [BX],CH ; cuenta máxima detención motor
STI ; sin forzar futura aceleración
POP DS ; **
XPOPA ; *
RET
motor_ok ENDP
reset_drv PROC
XPUSHA
CALL motor_off_cnt ; cuenta detención motor
MOV CL,unidad
MOV AL,CL
XSHL AL,4 ; unidad seleccionada
MOV AH,1 ; bit de motor
SHL AH,CL ; colocar dicho bit
OR AL,AH
PUSH DS ; *
DDS
CLI
MOV DS:[3Fh],AL
AND BYTE PTR DS:[3Eh],70h ; bit IRQ=0 y recalibrar
POP DS ; *
XSHL AL,4 ; bits motor en nibble alto
OR AL,CL ; seleccionar unidad
OR AL,00001000b ; interrupciones+DMA y reset
MOV DX,3F2h ; registro de salida digital
OUT DX,AL ; señal de reset
IFDEF XT
MOV CX,50
respiro: LOOP respiro
ELSE
CALL fdc_respiro ; tiempo reconocer reset en 486
ENDIF
OR AL,00000100b
OUT DX,AL ; fin de señal de reset
CALL espera_int ; rehabilitará interrupciones
MOV AL,8
CALL fdc_write ; comando 'leer estado int...'
CALL fdc_read
CALL fdc_read
CALL envia_specify ; comando 'specify' adecuado
XPOPA
RET
reset_drv ENDP
envia_specify PROC
PUSH AX
PUSH DS
DDS
MOV AH,DS:[8Bh]
POP DS
MOV AL,3 ; comando 'specify'
CALL fdc_write
MOV AL,0BFh ; step rate para 500 kbps
AND AH,11000000b
JZ spec1_ok
MOV AL,0AFh ; step rate para 1 Mbps
CMP AH,11000000b
JE spec1_ok
MOV AL,0DFh ; step rate para 250/300 Kbps
spec1_ok: CALL fdc_write
MOV AL,2
CALL fdc_write ; head load y modo DMA
POP AX
RET
envia_specify ENDP
motor_off_cnt PROC
XPUSHA
PUSH DS
MOV AL,0FFh ; valor máximo
JC motor_off_ok
IFDEF XT
XOR BX,BX
MOV DS,BX
ELSE
PUSH 0
POP DS
ENDIF
LDS BX,DWORD PTR DS:[1Eh*4] ; DS:BX -> INT 1Eh
MOV AL,[BX+2] ; byte 2 tabla base disco
motor_off_ok: DDS
MOV BYTE PTR DS:[40h],AL ; cuenta parada motor
POP DS
XPOPA
RET
motor_off_cnt ENDP
seek_drv PROC
XPUSHA
CALL set_rate ; velocidad / borrar resultados
CALL envia_specify ; comando 'specify' adecuado
MOV AH,1
MOV CL,unidad
SHL AH,CL ; AH = 1 (A:) ó 2 (B:)
PUSH DS
DDS
TEST AH,DS:[3Eh]
POP DS
JNZ do_seek ; la unidad ya fue recalibrada
CALL recalibrar
JC fallo_seek ; fallo al recalibrar
do_seek: MOV BX,94h
ADD BL,unidad
MOV AL,cilindro
PUSH DS ; *
DDS
OR DS:[3Eh],AH ; unidad ya recalibrada
MOV AH,DS:[41h] ; código de error previo
CMP AL,[BX]
MOV [BX],AL
POP DS ; *
JNE hacer_seek ; seek necesario
CMP AH,40h ; ¿error de seek previo?
JNE seek_ok ; no, evitar seek innecesario
hacer_seek: MOV AL,0Fh
CALL fdc_write ; comando 'seek'
JC fallo_seek
MOV AL,cabezal
XSHL AL,2
OR AL,unidad
CALL fdc_write ; enviar HD, US1, US0
MOV AL,cilindro
CALL fdc_write ; enviar cilindro
CALL espera_int ; esperar interrupción
JC fallo_seek
MOV AL,8
CALL fdc_write ; comando 'leer estado int...'
JC fallo_seek
CALL fdc_read ; leer registro de estado 0
JC fallo_seek
MOV AH,AL
CALL fdc_read ; leer cilindro actual
TEST AH,11000000b ; comprobar ST0
JNZ fallo_seek
MOV AL,15 ; estabilización para escritura
CMP orden,F_WRITE
JE rseek_ok
MOV AL,1 ; estabilización para lectura
rseek_ok: CBW ; AH = 0
CALL retardo ; esperar asentamiento cabezal
seek_ok: XPOPA
CLC ; retornar con éxito
RET
fallo_seek: XPOPA
STC ; retornar indicando fallo
RET
seek_drv ENDP
set_rate PROC
XPUSHA
CALL pista0?
MOV AX,[SI].vunidad ; velocidad pista 0 / demás
JZ vel_ok
MOV AL,AH
vel_ok: PUSH DS ; *
DDS
MOV AH,DS:[8Bh]
IFDEF XT
MOV CL,6
SHR AH,CL
ELSE
SHR AH,6 ; aislar bits de velocidad
ENDIF
CMP AL,AH
JE vel_set ; velocidad ya seleccionada
MOV DX,3F7h
OUT DX,AL ; seleccionarla
XSHL AL,6
AND BYTE PTR DS:[8Bh],00111111b
OR DS:[8Bh],AL
vel_set: POP DS ; *
LEA DI,status
MOV CX,8
borra_status: MOV [DI],CH ; borrar información de estado
INC DI
LOOP borra_status
XPOPA
RET
set_rate ENDP
recalibrar PROC
XPUSHA
MOV BX,94h
ADD BL,unidad
PUSH DS ; *
DDS
MOV [BX],BH ; pista actual = 0
POP DS ; *
MOV CX,2 ; dos veces como mucho
recalibra: MOV AL,7
CALL fdc_write ; comando de 'recalibrado'
JC fallo_recal
MOV AL,cabezal
XSHL AL,2
OR AL,unidad
CALL fdc_write ; enviar HD, US1, US0
JC fallo_recal
CALL espera_int ; esperar interrupción
JC fallo_recal
MOV AL,8
CALL fdc_write ; comando 'leer estado int...'
JC fallo_recal
CALL fdc_read ; leer registro de estado 0
JC fallo_recal
MOV AH,AL
CALL fdc_read ; leer cilindro actual
XOR AH,00100000b ; bajar bit de 'seek end'
TEST AH,11110000b ; comprobar resultado y ST0
JNZ fallo_recal ; sin 'seek end' o TRK0
MOV AX,1 ; pausa de 1 ms
CALL retardo
JMP recal_ret
fallo_recal: LOOP recalibra ; reintentar comando
STC ; condición de fallo
recal_ret: XPOPA
RET
recalibrar ENDP
sector_io PROC
XPUSH <AX, BX, CX, DX>
MOV CL,tsector
MOV CH,0
STC
RCL CH,CL
MOV CL,0 ; nº de bytes por sector
MOV AL,sector_fin
SUB AL,sector_ini
INC AX
CBW ; AX sectores (AH = 0)
MUL CX
MOV DX,AX ; bytes totales
MOV CX,AX
DEC CX ; bytes totales - 1
MOV AX,ES
CALL calc_dir_DMA ; AX:DI -> base BX y página AH
IFDEF SUPERBOOT
JC sector_io_ko ; chequear cruce frontera DMA
ENDIF
MOV AL,orden ; modo DMA necesario
CALL prepara_DMA
CMP AL,F_WRITE
MOV AL,11000101b ; comando de escritura del FDC
JE orden_io_ok
MOV AL,11100110b ; comando leer (verif.) del FDC
orden_io_ok: CALL fdc_write ; comando leer/escribir del FDC
JC sector_io_ko
MOV AL,cabezal
XSHL AL,2
OR AL,unidad
CALL fdc_write ; byte 1 de la orden
MOV AL,cilindro
CALL fdc_write ; enviar cilindro
MOV AL,cabezal
CALL fdc_write ; enviar cabezal
MOV AL,sector_ini
CALL fdc_write ; enviar nº sector
MOV AL,tsector
CALL fdc_write ; longitud sector
MOV AL,sector_fin
CALL fdc_write ; último sector
MOV AL,[SI].gap
CALL fdc_write ; GAP de lectura/escritura
MOV AL,128
CALL fdc_write ; tamaño sector si longitud=0
CALL espera_int
PUSHF ; *
LEA BX,fdc_result
MOV CX,7
sect_io_res: CALL fdc_read ; leyendo resultados
MOV [BX],AL
INC BX
LOOP sect_io_res
POPF ; *
JC sector_io_ko
TEST fdc_result,11000000b
JNZ sector_io_ko
ADD DI,DX ; actualizar dirección
CLC ; Ok
JMP sector_io_fin
sector_io_ko: STC ; indicar fallo
sector_io_fin: XPOP <DX, CX, BX, AX>
RET
sector_io ENDP
calc_dir_DMA PROC
PUSH DX
MOV BX,16
MUL BX
ADD AX,DI
ADC DX,0 ; DX:AX = dirección 20 bits
MOV BX,AX ; base en BX
MOV AH,DL ; página
IFDEF SUPERBOOT
MOV DX,CX ; comprobar cruce en SuperBOOT
ADD DX,BX
JNC dir_DMA_ok
MOV status,9 ; error de frontera de DMA
ENDIF
dir_DMA_ok: POP DX
RET
calc_dir_DMA ENDP
genera_info PROC
XPUSHA
MOV buf_unidad,-1 ; invalidar contenido buffer
MOV SI,buffer
MOV DI,BX
CALL pista0?
JNZ no_cilcab0 ; no es cilindro/cabezal 0
ADD DI,ES:[BX+70] ; DI -> datos pista 0
MOV CL,ES:[DI]
MOV CH,0 ; CX sectores en pista 0
INC DI
MOV AL,ES:[DI] ; GAP para pista 0
MOV AH,0 ; byte de relleno
INC DI
MOV BYTE PTR [SI],2 ; tamaño de sector
MOV BYTE PTR [SI+1],CL ; número de sectores
MOV [SI+2],AX ; GAP / byte de relleno
genera_0: ADD SI,4
MOV AL,cilindro
MOV AH,cabezal
MOV [SI],AX ; datos para cada sector
MOV AL,ES:[DI]
MOV AH,2 ; LOG2 (tamaño)-7
INC DI
MOV [SI+2],AX ; nº de sector / tamaño
LOOP genera_0
XPOPA
RET
no_cilcab0: ADD DI,ES:[BX+72]
CMP BYTE PTR ES:[BX+65],1
JE info_stv
MOV DL,ES:[DI+2] ; tamaño /F
MOV DH,ES:[DI] ; nº sectores
MOV [SI],DX
XCHG DH,DL ; tamaño en DH
MOV CL,DL
MOV CH,0 ; CX sectores
MOV AL,ES:[DI+1] ; GAP para formatear
formatea_pista PROC
XPUSHA
MOV BX,buffer
MOV DI,BX
MOV CL,[BX+1]
MOV CH,0 ; CX sectores
XSHL CX,2
DEC CX ; nº de bytes - 1
MOV AX,DS
CALL calc_dir_DMA ; AX:DI -> base BX y página AH
MOV AL,4Ah ; modo DMA para escribir
ADD BX,4 ; saltar primeros 4 bytes
CALL prepara_DMA
MOV BX,buffer
MOV AL,F_FORMAT
CALL fdc_write
JC fallo_fmt
MOV AL,cabezal
XSHL AL,2
OR AL,unidad
CALL fdc_write ; byte 1 de la orden
JC fallo_fmt
MOV CX,4
format_cmd: MOV AL,[BX]
CALL fdc_write
INC BX
LOOP format_cmd
CALL espera_int
fallo_fmt: PUSHF
LEA BX,fdc_result
MOV CX,7
format_res: CALL fdc_read ; leyendo resultados
MOV [BX],AL
INC BX
LOOP format_res
POPF
JC fallo_format
TEST fdc_result,11000000b
JZ format_ret
fallo_format: STC ; fallo
format_ret: XPOPA
RET
formatea_pista ENDP
ENDIF
IFNDEF XT
espera_int PROC
STI
XPUSHA
PUSH DS
DDS
MOV AX,9001h
CLC
INT 15h ; permitir multitarea
MOV DX,0280h
MOV BX,3Eh
JC timeout_int
esp_int_1s: XOR CX,CX
esp_int: TEST [BX],DL ; ¿llegó la interrupción?
JNZ fin_espera
PMICRO
LOOP esp_int ; esperar durante casi 1 seg.
DEC DH
JNZ esp_int_1s
timeout_int: OR CS:status,DL ; timeout
STC
fin_espera: PUSHF
AND BYTE PTR [BX],7Fh ; para la próxima vez
POPF
POP DS
XPOPA
RET
espera_int ENDP
ELSE ; Si es XT...
espera_int PROC
STI
XPUSHA
XPUSH <DS, 40h>
POP DS
MOV AH,0FFh
esperar_int: CMP AL,DS:[6Ch]
JE mira_int
MOV AL,DS:[6Ch]
INC AH
CMP AH,37 ; ¿más de 2 segundos?
JB mira_int
timeout_int: OR CS:status,80h ; timeout
STC
JMP fin_espera
mira_int: TEST BYTE PTR DS:[3Eh],80h
JZ esperar_int
AND BYTE PTR DS:[3Eh],7Fh ; CF=0
fin_espera: POP DS
XPOPA
RET
espera_int ENDP
ENDIF
prepara_DMA PROC
PUSH AX
CLI
OUT 0Bh,AL ; registro de modo del DMA
MOV AL,0
DELAY
OUT 0Ch,AL ; clear first/last flip-flop
MOV AL,BL
DELAY
OUT 4,AL
MOV AL,BH
DELAY
OUT 4,AL ; enviada dirección base
DELAY
MOV AL,AH
OUT 81h,AL ; registro de página del DMA
MOV AL,CL
DELAY
OUT 5,AL
MOV AL,CH
DELAY
OUT 5,AL ; enviada cuenta de bytes
STI
MOV AL,2
DELAY
OUT 0Ah,AL ; habilitar canal 2 de DMA
POP AX
RET
prepara_DMA ENDP
IFNDEF XT
fdc_read PROC
XPUSH <CX, DX, AX>
CALL fdc_respiro ; no abrasar el FDC
MOV DX,3F4h ; registro de estado del FDC
MOV CX,133 ; constante para 0,002 segundos
espera_rd: DELAY
IN AL,DX
AND AL,11000000b
CMP AL,11000000b ; ¿dato listo?
JE fdc_rd_ok
DELAY
IN AL,61h
AND AL,10h
CMP AL,AH
JE espera_rd ; reintentarlo durante 15,09 mus
MOV AH,AL
LOOP espera_rd
XPOP <AX, DX, CX>
OR status,80h ; timeout
MOV AL,0
STC ; fallo
RET
fdc_rd_ok: POP AX
INC DX ; apuntar al registro de datos
DELAY
IN AL,DX ; leer byte del FDC
XPOP <DX, CX>
CLC ; Ok
RET
fdc_read ENDP
ELSE
fdc_read PROC
XPUSH <CX, DX>
MOV DX,3F4h ; registro de estado del FDC
XOR CX,CX ; evitar cuelgue total si falla
espera_rd: IN AL,DX ; leer registro de estado
TEST AL,80h ; ¿bit 7 inactivo?
ENDIF
IFNDEF XT
fdc_write PROC
XPUSH <CX, DX, AX>
CALL fdc_respiro ; no abrasar el FDC
MOV DX,3F4h ; registro de estado del FDC
MOV CX,133 ; constante para 0,002 segundos
espera_wr: DELAY
IN AL,DX
TEST AL,80h ; ¿listo para E/S?
JNZ fdc_wr_ok
DELAY
IN AL,61h
AND AL,10h
CMP AL,AH
JE espera_wr ; reintentarlo durante 15,09 mus
MOV AH,AL
LOOP espera_wr
XPOP <AX, DX, CX>
OR status,80h ; timeout
STC ; fallo
RET
fdc_wr_ok: INC DX ; apuntar al registro de datos
POP AX
DELAY
OUT DX,AL ; enviar byte al FDC
XPOP <DX, CX>
CLC ; Ok
RET
fdc_write ENDP
ELSE
fdc_write PROC
XPUSH <AX, CX, DX>
MOV DX,3F4h ; registro de estado del FDC
XCHG AH,AL ; preservar AL en AH
XOR CX,CX ; evitar cuelgue total si falla
espera_wr: IN AL,DX ; leer registro de estado
TEST AL,80h ; ¿bit 7 inactivo?
LOOPZ espera_wr ; así es: el FDC está ocupado
JCXZ fdc_wr_nok
XCHG AH,AL ; recuperar el dato de AL
ENDIF
IFNDEF XT
fdc_respiro PROC
XPUSH <AX, CX>
MOV CX,4
fdc_ret: PMICRO
LOOP fdc_ret
XPOP <CX, AX>
RET
fdc_respiro ENDP
ENDIF
IFNDEF XT
retardo PROC
PUSHF
XPUSHA
MOV DX,16970 ; 16970 = 1193180/18*256/1000
MUL DX
MOV CL,AH ; dividir DX:AX entre 256 y
MOV CH,DL ; dejar el resultado en DX:CX
MOV DL,DH
MOV DH,0 ; DX:CX 15,09 mus-avos
retardando: PMICRO
LOOP retardando
AND DX,DX
JZ retardado
DEC DX
JMP retardando
retardado: XPOPA
POPF
RET
retardo ENDP
ELSE
retardo PROC
PUSHF
XPUSH <AX, BX, CX, DX>
retarda_mas: CMP AX,54 ; como máximo 54 ms cada vez
JBE retarda_fin
PUSH AX
MOV AX,54
CALL rt_ax
POP AX
SUB AX,54
JMP retarda_mas
retarda_fin: CALL rt_ax
XPOP <DX, CX, BX, AX>
POPF
RET
ENDIF
IFDEF SUPERBOOT
initcode: PUSH DS
PUSH SS
POP DS
MOV ES:[info_A.tipo_drv],AL ; anotar tipo de A:
MOV ES:[info_B.tipo_drv],AH ; anotar tipo de B:
LEA DI,ant_int13
MOV SI,13h*4 ; vector de INT 13h
CLD
CLI
MOVSW
MOVSW ; anotada dirección INT 13h
MOV WORD PTR [SI-4],OFFSET ges_int13
MOV [SI-2],ES
STI ; desviada INT 13h
POP DS
RETF ; volver a 2MFBOTHD
ENDIF
EVEN
buffer_io EQU $
tbuffer EQU 2048
Listado 765DEBUG
/*********************************************************************
* *
* 765DEBUG 3.1 - Programa de análisis avanzado a bajo nivel de *
* los disquetes, programando el 765 y el 8237. *
* *
* Compilar en Turbo C 2.0 o Borland C (modelo Large). *
* *
*********************************************************************/
#include <dos.h>
#include <alloc.h>
#include <conio.h>
#define SELECT 1
#define RECALIBRAR 2
#define SEEK 3
#define LEERIDS 4
#define LEER 5
#define ESCRIBIR 6
#define FORMATEAR 7
#define SALIR 8
void main()
{
unsigned char far *buffer; /* buffer para sector de hasta 4 Kb */
int unidad=0, vunidad=0, mf_mfm=1, cabezal=0, cilindro=0;
for (;;)
switch (menu (unidad, vunidad, &mf_mfm, cilindro, &cabezal)) {
if ((*buffer=farmalloc(SMAX<<1))==NULL) {
printf("\nMemoria insuficiente\n");
exit(1);
}
clrscr();
puts("765DEBUG 3.1 - UTILIDAD PARA ANALISIS AVANZADO A BAJO NIVEL DE
DISQUETES.");
puts(" Programación directa del controlador NEC765 y
el DMA 8237.");
puts(" Funcionamiento probado bajo sistemas PC XT, AT,
386 y 486.");
puts(" Soporte para disquetes de 360K, 720K, 1.2M,
1.44M y 2.88M.");
puts("");
puts(" (C) 1992, 1993, 1994 - Ciriaco García
de Celis.");
puts("");
puts("");
puts(" F2 - Seleccionar unidad/densidad y
resetear.");
puts(" F3 - Recalibrar cabezal (necesario tras
F2).");
puts("");
puts(" F4 - Cambiar de cabezal.");
puts(" F5 - Posicionar cabezal.");
puts(" F6 - Leer ID's.");
puts(" F7 - Leer sector.");
puts(" F8 - Escribir sector.");
puts(" F9 - Formatear pista.");
puts(" F10 - Conmutar MF/MFM.");
return (opcion);
}
(void) infdc();
clrscr();
printf("\n\n\n\n\n\n\n\n\n\n\n\t\t\t\tRecalibrando...");
*cilindro=0;
clrscr();
printf("\n\n\n\n\n\n\n\n\n\n\n\t\t\t Cilindro (0..N): ");
scanf("%d", cilindro);
}
else
r=1;
clrscr();
printf("Sector a leer: "); scanf("%d", §or);
printf("\n\nTamaño de sector:\n");
printf(" 0 -> 1-128 bytes\n");
printf(" 1 -> 256 bytes\n");
printf(" 2 -> 512 bytes\n");
printf(" 3 -> 1024 bytes\n");
printf(" 4 -> 2048 bytes\n");
printf(" 5 -> 4096 bytes\n");
printf("\n Elige: ");
do tsector=getch()-'0'; while ((tsector<0) || (tsector>8));
printf("%d\n", tsector);
if (tsector==0) {
printf("\n Concreta el tamaño (1-128): ");
scanf("%d", &t128);
}
motor_on (unidad);
mostrar_resultados (&r);
motor_off();
if (r & 0xC0) {
printf("Error de lectura (el sector puede estar mal leído).\n");
printf("Nota: el buffer de lectura contenía el patrón 5AA5.\n");
}
printf(" Pulsa una tecla para ver el sector [ESC=salir].");
if (getch()!=27) mostrar_sector (buffer, tsector, t128);
}
clrscr();
printf("Sector a escribir: "); scanf("%d", §or);
printf("\n\nTamaño de sector:\n");
printf(" 0 -> 1-128 bytes\n");
printf(" 1 -> 256 bytes\n");
printf(" 2 -> 512 bytes\n");
printf(" 3 -> 1024 bytes\n");
printf(" 4 -> 2048 bytes\n");
printf(" 5 -> 4096 bytes\n");
printf("\n Elige: ");
do tsector=getch()-'0'; while ((tsector<0) || (tsector>8));
printf("%d\n", tsector);
if (tsector==0) {
printf("\n Concreta el tamaño (1-128): ");
scanf("%d", &t128);
}
motor_on (unidad);
mostrar_resultados (&r);
motor_off();
clrscr();
printf("\n\nTamaño de sector:\n");
printf(" 0 -> 128 bytes\n");
printf(" 1 -> 256 bytes\n");
printf(" 2 -> 512 bytes\n");
printf(" 3 -> 1024 bytes\n");
printf(" 4 -> 2048 bytes\n");
printf(" 5 -> 4096 bytes\n");
printf("\n Elige: ");
do tsector=getch()-'0'; while ((tsector<0) || (tsector>8));
printf("%d\n", tsector);
printf("\nNúmero de sectores: "); scanf("%d", §ores);
printf("\nValor para el GAP 3: "); scanf("%d", &gap);
printf("\nByte para inicializar sectores: "); scanf("%d", &pokete);
motor_on (unidad);
mostrar_resultados (&r);
motor_off();
do {
clrscr();
printf("Puntualizaciones sobre el formateo:\n\n");
printf(" He establecido por defecto una tabla con los cuatro\n");
printf("bytes que hay que enviar al controlador, por cada uno\n");
printf("de los sectores de la pista, que están numerados:\n\n");
for (i=0; i<numsect; i++) printf ("%4d", buffer[i*4+2]);
printf("\n\n Puedes elegir lo siguiente: \n\n");
printf(" 1 - Introducir tú los 4 bytes de un sector.\n");
printf(" 2 - Modificar un cierto byte en todos los sectores.\n");
printf("ESC - Dejar las cosas como están ahora.\n");
printf("\n Elige opción.");
do {
opcion=getch(); if (!opcion) opcion=getch()<<8;
} while (((opcion<'1') || (opcion>'3')) && (opcion!=27));
if (opcion=='1') {
do {
printf("\n\nSector a alterar: "); scanf ("%d", §or);
for (i=0; i<numsect; i++) if (buffer[i*4+2]==sector) break;
if (buffer[i*4+2]!=sector)
printf("Ese sector no existe. No discutamos ");
else {
printf("Nº Cilindro (anterior=%d): ", buffer[i*4]);
scanf ("%d", &dato); buffer[i*4]=(char) dato;
printf("Nº cabezal (anterior=%d): ", buffer[i*4+1]);
scanf ("%d", &dato); buffer[i*4+1]=(char) dato;
printf("Nº sector (anterior=%d): ", buffer[i*4+2]);
scanf ("%d", &dato); buffer[i*4+2]=(char) dato;
printf("Tamaño sector (anterior=%d): ", buffer[i*4+3]);
scanf ("%d", &dato); buffer[i*4+3]=(char) dato;
}
printf("¿De acuerdo (S/N)?");
} while ((getch() | 0x20)!='s');
}
else if (opcion=='2') {
do {
printf("\n\nCaracterística a cambiar: \n");
printf(" (0) Nº Cilindro, (1) Nº cabezal,");
printf(" (2) Nº sector, (3) Tamaño de sector: ");
opcion=getch();
} while ((opcion<'0') || (opcion>'3'));
printf("\n Nuevo valor para todos los sectores: ");
scanf ("%d", &dato);
for (i=0; i<numsect; i++) buffer[i*4+opcion-'0']=(char) dato;
}
} while (opcion!=27);
clrscr();
}
do {
clrscr();
printf("\n\n\n\n\n\n\n\n\n\n\n\t\t\t\tLeyendo ID's...");
do { /* esperar interrupción */
antlectura=lectura;
outportb (0x43, 0x80); /* enclavamiento */
lectura=inportb(0x42); /* parte baja de la cuenta */
lectura|=inportb(0x42) << 8; /* parte alta de la cuenta */
if (lectura>antlectura) if (cnth++>8) break; /* timeout */
} while (!(peekb(0x40, 0x3E) & 0x80));
pokeb (0x40, 0x3E, peekb (0x40, 0x3E) & 0x7F); /* reset int. */
if (cnth<9)
tmp[i]=cnth*65535L + (65535-lectura);
else {
tmp[i]=0L; /* error */
nec[i][0]=-1; /* no informar */
pokeb (0x40, 0x40, 0xFF); /* asegurar motor en marcha */
} /* porque probablemente se está perdiendo mucho tiempo */
}
clrscr();
printf("\r Longitud (ms) ");
printf(" Sector Tamaño Cilindro Cabeza ST0 ST1 ST2 \n");
printf(" ------------------- ");
printf("------ ------------ -------- ------ ----- ----- -----\n");
acu=0;
for (j=0; j<21; j++) { /* rechazar primera muestra */
fin_ids: motor_off();
}
void adios()
{
outportb (CONTROL, peekb(0x40, 0x8B) >> 6); /* velocidad normal */
clrscr(); printf("Fin de 765DEBUG\n");
exit (0);
}
void mostrar_sector (unsigned char far *buffer, int tamano, int tt)
{
unsigned char far *p;
int vv, i, j, k, tecla;
i=0;
do {
p=&buffer[i*256];
clrscr(); printf("\n\n\n");
for (j=0; j<tt; j+=16) {
printf(" %04X: ", p-buffer);
for (k=0; k<8; k++) printf("%02X ", *p++);
printf("- ");
for (k=8; k<16; k++) printf("%02X ", *p++); p-=16;
printf(" ");
for (k=0; k<16; k++) {
if (*p<' ') printf("."); else printf("%c", *p);
p++;
}
printf("\n");
}
printf("\n\t\t Bytes %04d-%04d del sector (%d/%d)\n",
i*tt, (i+1)*tt-1, i+1, vv);
printf("\t\t Utiliza los cursores [ESC=salir]");
do
tecla=getch();
while (tecla && (tecla!=27) && (tecla!=32) && (tecla!=13));
if ((tecla==32) || (tecla==13)) {
i++; if (i>=vv) i=0;
}
if (!tecla) {
tecla=getch();
if (tecla==0x48) i--; /* cursor arriba */
if (tecla==0x50) i++; /* cursor abajo */
if (tecla==0x47) i=0; /* Inicio */
if (tecla==0x4f) i=vv-1; /* Fin */
if (tecla==0x49) i-=2; /* Re Pág */
if (tecla==0x51) i+=2; /* Av pág */
if (i<0) i=0; if (i>=vv) i=vv-1;
}
} while (tecla!=27);
}
/**** Evitar que la BIOS pare el motor (al menos en 14") ****/
pokeb(0x40,0x40,0xFF);
void motor_off()
{
pokeb(0x40,0x40,55); /* la BIOS lo detendrá en 55/18.2 segundos */
}
do {
i++; t=peekb(0x40, 0x6C);
while ((t==peekb(0x40, 0x6C)) && ((rd=inportb(FDCSTATUS)>>7)==0));
} while ((i<8) && !rd);
do {
i++; t=peekb(0x40, 0x6C);
while ((t==peekb(0x40, 0x6C)) && ((rd=inportb(FDCSTATUS)>>7)==0));
} while ((i<8) && !rd);
do {
i++; t=peekb(0x40, 0x6C);
while ((t==peekb(0x40, 0x6C)) && (!(peekb(0x40, 0x3E) & 0x80)));
} while ((i<37) && (!(peekb(0x40, 0x3E) & 0x80)));
Listado 765NODMA.ASM
; ********************************************************************
; * *
; * 765NODMA.ASM 2.0 - Programa de demostración de acceso a *
; * bajo nivel al disquete sin emplear DMA. *
; * *
; ********************************************************************
fdc_test SEGMENT
ASSUME CS:fdc_test, DS:fdc_test
ORG 100h
main PROC
CALL menu ; opciones
DEC AL
JZ leer ; opción de leer sector
DEC AL
JZ escribir ; opción de escribirlo
LEA DX,adios_txt
CALL print ; opción de salir:
MOV AX,40h
MOV DS,AX
MOV AL,DS:[8Bh] ; velocidad previa al programa
MOV CL,6
SHR AL,CL ; pasarla a bits 0..1
MOV DX,3F7h
OUT DX,AL ; restaurar velocidad previa
INT 20h
leer: LEA DX,cls_txt
CALL print ; borrar pantalla
LEA DX,lectura_txt
CALL print ; mensaje inicial
LEA DX,aviso_txt
CALL print
CALL pide_sector ; pedir pista, cabeza, ...
MOV orden,F_READ
CALL init_drv
CALL recalibrar
JC fallo
CALL seek_drv
JC fallo
LEA DI,buffer
CALL sector_io ; cargar dicho sector
JC fallo
CALL imprime_sector ; mostrar su contenido
JMP main
menu PROC
LEA DX,cls_txt
CALL print
LEA DX,opciones_txt ; texto del menú
CALL print
espera_opc: CALL getch
CMP AL,'1'
JE opc_ok ; elegida opción 1
CMP AL,'2'
JE opc_ok ; elegida opción 2
CMP AL,27
JE opc3_ok ; ESC (opción '3')
CMP AX,2D00h
JNE espera_opc ; no es ALT-X
opc3_ok: MOV AL,'3'
opc_ok: SUB AL,'0'
RET
menu ENDP
pide_sector PROC
LEA DX,unidad_txt
CALL input_AL ; pedir unidad
MOV unidad,AL
LEA DX,vunidad_txt
CALL input_AL ; seleccionar velocidad
MOV vunidad,AL
LEA DX,tdisco_txt
CALL input_AL ; problema de 40/80 pistas
MOV tunidad,AL
LEA DX,tamano_txt
CALL input_AL ; preguntar tamaño sector
MOV tsector,AL
LEA DX,gap_rw_txt
CALL input_AL ; preguntar tamaño sector
MOV gap,AL
LEA DX,pista_txt
CALL input_AL ; pedir pista
MOV cilindro,AL
LEA DX,cabeza_txt
CALL input_AL ; pedir cabeza
MOV cabezal,AL
LEA DX,sector_txt
CALL input_AL ; pedir sector
MOV sector_ini,AL
MOV sector_fin,AL
MOV CL,tsector
MOV CH,0
INC CX ; CX: 1-128 bytes, 2-256, ...
MOV AX,64
computab: SHL AX,1
LOOP computab
MOV bsector,AX ; bytes/sector
MOV AL,cabezal
SHL AL,1
SHL AL,1
OR AL,unidad
MOV byte1,AL ; byte 1 común a muchas órdenes
RET
pide_sector ENDP
imprime_sector PROC
LEA BX,buffer
MOV AX,bsector
MOV CL,AH
MOV CH,0 ; CX secciones de 256 bytes
AND CX,CX
JNZ otra_mitad
INC CX ; al menos imprimir una vez
otra_mitad: PUSH CX
LEA DX,cls_txt
CALL print
MOV CX,16 ; 16 líneas
otra_linea: PUSH CX
MOV CX,16 ; de 16 caracteres
pr_hexa: MOV AL,' '
CALL printAL
MOV AL,[BX]
INC BX
CALL print8hex
LOOP pr_hexa
MOV AL,' '
CALL printAL
CALL printAL
SUB BX,16
MOV CX,16
pr_ascii: MOV AL,[BX]
INC BX
CMP AL,' '
JAE ascii_ok
MOV AL,'.'
ascii_ok: CALL printAL
LOOP pr_ascii
MOV AL,13
CALL printAL
MOV AL,10
CALL printAL
POP CX
LOOP otra_linea
LEA DX,ptecla_txt
CALL print
CALL getch
POP CX
LOOP otra_mitad
RET
imprime_sector ENDP
pide_relleno PROC
LEA DX,relleno_txt
CALL input_AL
LEA DI,buffer
MOV CX,bsector ; tamaño de sector en bytes
CLD
REP STOSB
RET
pide_relleno ENDP
print PROC
PUSH AX
MOV AH,9 ; función de impresión
INT 21h ; llamar al sistema
POP AX
RET
print ENDP
printAL PROC
PUSH AX
PUSH DX ; registros usados preservados
MOV AH,2 ; función de impresión del DOS
MOV DL,AL ; carácter a imprimir
INT 21h ; llamar al sistema
POP DX
POP AX ; recuperar registros
RET ; retornar
printAL ENDP
print4hex PROC
PUSH AX ; preservar AX
ADD AL,'0' ; pasar binario a ASCII
CMP AL,'9'
JBE no_sup9 ; no es letra
ADD AL,'A'-'9'-1 ; lo es
no_sup9: CALL printAL ; imprimir dígito hexadecimal
POP AX ; restaurar AX
RET
print4hex ENDP
print8hex PROC
PUSH CX
PUSH AX
MOV CL,4
SHR AL,CL ; pasar bits 4..7 a 0..3
CALL print4hex ; imprimir nibble más
significativo
POP AX ; restaurar AL
PUSH AX ; y preservarlo de nuevo
AND AL,1111b ; dejar nibble menos significativo
CALL print4hex ; e imprimirlo
POP AX
POP CX
RET
print8hex ENDP
getch PROC
MOV AH,1 ; esperar carácter (algunos
INT 16h ; KEYB de XT se cuelgan
JZ getch ; al usar directamente el
MOV AH,0 ; servicio 0).
INT 16h
RET
getch ENDP
input_AL PROC
PUSH BX
PUSH CX
PUSH DX
PUSH AX
pedir_dato: CALL print
MOV AH,0Ah ; función de entrada (teclado)
LEA DX,buffer
MOV BX,DX
MOV WORD PTR [BX],4 ; (inicializar dos variables)
INT 21h ; llamar al sistema
MOV CL,[BX+1]
XOR CH,CH ; número de caracteres pulsados
POP AX
POP DX
PUSH DX
PUSH AX
JCXZ pedir_dato ; se pulsó RETURN: reiterar
XOR DX,DX
gen_num: MOV AX,10
MUL DX
MOV DX,AX
MOV AL,[BX+2]
SUB AL,'0'
INC BX
XOR AH,AH
ADD DX,AX
LOOP gen_num ; conversión ASCII -> binario
POP AX
MOV AL,DL ; resultado
POP DX
POP CX
POP BX
RET
input_AL ENDP
init_drv PROC
PUSH CX
CALL reset_drv
MOV CX,18
CALL retardo ; esperar aceleración disco
POP CX
RET
init_drv ENDP
reset_drv PROC
XPUSH <DS, AX, BX, CX, DX>
PUSH DS
MOV BX,40h ; engañar al BIOS para
MOV DS,BX ; que no pare el motor al
MOV BYTE PTR DS:[BX],255 ; menos durante 14 seg.
POP DS
MOV DX,3F2h ; registro de salida digital
MOV CL,unidad
ADD CL,4
MOV AL,1
SHL AL,CL ; colocar bit del motor
OR AL,unidad ; seleccionar unidad; NO DMA
OUT DX,AL ; reset
OR AL,00000100b
JMP SHORT $+2
OUT DX,AL ; fin del reset
CALL espera_int
MOV AL,3
CALL fdc_write ; Comando 'Specify':
MOV AL,0DFh
CALL fdc_write
MOV AL,3 ; modo NO DMA
CALL fdc_write ; head load y modo
PUSH DS
MOV BX,40h
MOV DS,BX
MOV CL,CS:unidad
MOV AL,1
SHL AL,CL
AND BYTE PTR DS:[BX-1],11110000b
OR DS:[BX-1],AL ; indicar motor ON
POP DS
MOV DX,3F7h
MOV AL,vunidad ; velocidad de transferencia
OUT DX,AL
XPOP <DX, CX, BX, AX, DS>
RET
reset_drv ENDP
recalibrar PROC
XPUSH <AX, CX>
seek_drv PROC
XPUSH <AX, CX>
CLI
CALL habilita_int ; usar interrupciones
MOV AL,0Fh
CALL fdc_write ; comando 'seek'
JZ fallo_seek
MOV AL,byte1
CALL fdc_write ; enviar HD, US1, US0
MOV AL,cilindro
CMP tunidad,0
JE pista_ok ; es unidad de doble densidad
CMP vunidad,1 ; es de alta:
JNE pista_ok ; no es disco 5¼-360
SHL AL,1 ; cilindro=cilindro*2
pista_ok: CALL fdc_write ; enviar cilindro
CALL espera_int ; esperar interrupción
CLI
MOV AL,8
CALL fdc_write ; comando 'leer estado int...'
JZ fallo_seek
CALL fdc_read ; leer registro de estado 0
CALL fdc_read ; leer cilindro actual
STI
MOV CX,1
CALL retardo ; esperar asentamiento cabezal
XPOP <CX, AX>
CLC ; retornar con éxito
RET
fallo_seek: STI
XPOP <CX, AX>
habilita_int PROC
XPUSH <AX, CX, DX>
MOV CL,unidad
ADD CL,4
MOV AL,1
SHL AL,CL ; colocar bit del motor
OR AL,unidad ; seleccionar unidad
OR AL,00000100b ; no hacer reset
MOV DX,3F2h
OUT DX,AL
OR AL,00001000b ; modo DMA
JMP SHORT $+2
OUT DX,AL
XPOP <DX, CX, AX>
RET
habilita_int ENDP
espera_int PROC
STI
XPUSH <AX, CX>
XPUSH <DS, 40h>
POP DS
MOV AH,0FFh
esperar_int: CMP AL,DS:[6Ch]
JE mira_int
MOV AL,DS:[6Ch]
INC AH
CMP AH,37 ; no esperar más de 2 segundos
JA fin_espera ; timeout
mira_int: TEST BYTE PTR DS:[3Eh],80h
JZ esperar_int
fin_espera: AND BYTE PTR DS:[3Eh],127 ; resetear flag
POP DS ; para futura interrupción
MOV CL,unidad
ADD CL,4
MOV AL,1
SHL AL,CL ; colocar bit del motor
OR AL,unidad ; seleccionar unidad
OR AL,00000100b ; no hacer reset y no DMA
MOV DX,3F2h
OUT DX,AL
XPOP <CX, AX>
RET
espera_int ENDP
sector_io PROC
XPUSH <AX, BX, CX, DX, DI>
MOV AL,orden
CLI
CALL fdc_write ; comando leer/escribir del 765
JNZ io_proc
JMP sector_io_ko
io_proc: MOV AL,byte1
CALL fdc_write ; enviar HD, US1, US0
MOV AL,cilindro
CALL fdc_write ; enviar cilindro
MOV AL,cabezal
CALL fdc_write ; enviar cabezal
MOV AL,sector_ini
CALL fdc_write ; enviar nº sector
MOV AL,tsector
CALL fdc_write ; longitud sector
MOV AL,sector_fin
CALL fdc_write ; último sector
MOV AL,gap
CALL fdc_write ; GAP de lectura/escritura
MOV AL,128
CALL fdc_write ; tamaño sector si longitud=0
CLD
MOV AL,sector_fin
SUB AL,sector_ini
INC AL
XOR AH,AH ; AX = nº de sectores
MUL bsector
MOV CX,AX ; bytes a leer/escribir
MOV DX,3F4h ; registro de estado del FDC
espera_exec: IN AL,DX
TEST AL,80h ; ¿alcanzada fase ejecución?
JZ espera_exec
CMP orden,F_WRITE
JE fdc_wr_sect
fdc_rd_sect: IN AL,DX
TEST AL,80h ; ¿listo para E/S?
JZ fdc_rd_sect
TEST AL,16
JZ sector_io_ko ; fallo en lectura
INC DX ; apuntar al registro de datos
IN AL,DX ; leer byte del sector
DEC DX
STOSB ; ES:[DI++] <-- AL
LOOP fdc_rd_sect ; repetir hasta fin sector(es)
JMP sect_io_fin
fdc_wr_sect: IN AL,DX
TEST AL,80h ; ¿listo para E/S?
JZ fdc_wr_sect
TEST AL,64
JNZ sector_io_ko ; fallo en escritura
MOV AL,ES:[DI]
INC DX ; apuntar al registro de datos
OUT DX,AL ; escribir byte del sector
DEC DX
INC DI
LOOP fdc_wr_sect ; hasta acabar sector(es)
sect_io_fin: MOV CX,7
sect_io_rx: CALL fdc_read ; leyendo resultados del éxito
LOOP sect_io_rx
STI ; ...fin de la fase crítica
fdc_read PROC
PUSH CX
PUSH DX
MOV DX,3F4h ; registro de estado del FDC
XOR CX,CX ; evitar cuelgue total si falla
espera_rd: IN AL,DX ; leer registro de estado
TEST AL,80h ; ¿bit 7 inactivo?
LOOPZ espera_rd ; así es: el FDC está ocupado
INC DX ; apuntar al registro de datos
IN AL,DX ; leer byte del FDC
AND CX,CX ; ZF = 1 si fallo al leer
POP DX
POP CX
RET
fdc_read ENDP
fdc_write PROC
PUSH AX
PUSH CX
PUSH DX
MOV DX,3F4h ; registro de estado del FDC
XCHG AH,AL ; preservar AL en AH
XOR CX,CX ; evitar cuelgue total si falla
espera_wr: IN AL,DX ; leer registro de estado
TEST AL,80h ; ¿bit 7 inactivo?
LOOPZ espera_wr ; así es: el FDC está ocupado
XCHG AH,AL ; recuperar el dato de AL
INC DX ; apuntar al registro de datos
OUT DX,AL ; enviar byte al FDC
AND CX,CX ; ZF = 1 si fallo al escribir
POP DX
POP CX
POP AX
RET
fdc_write ENDP
retardo PROC
PUSH DS
PUSH AX
PUSH CX
MOV AX,40h
MOV DS,AX
STI
espera_tics: MOV AX,DS:[6Ch] ; esperar que el contador
espera_tic: CMP AX,DS:[6Ch] ; de hora del BIOS...
JE espera_tic
LOOP espera_tics ; ... cambie lo suficiente
POP CX
POP AX
POP DS
RET
retardo ENDP
; ************ Mensajes
cls_txt DB 10,10,10,10,10,10,10,10,10,10,10,10
DB 10,10,10,10,10,10,10,10,10,10,10,10,13,"$"
aviso_txt DB 13,10,"--------------------",13,10
DB "Aviso: No se validan las entradas.",10,"$"
; ************ Datos
fdc_test ENDS
END main
Listado DMAP
/********************************************************************/
/* */
/* DMAP 2.1 - Utilidad de información gráfica de discos. */
/* */
/* (c) Julio 1994 Ciriaco García de Celis. */
/* */
/* Compilar con Borland C++ en modelo large con */
/* la opción «Jump optimization» desactivada. */
/* */
/********************************************************************/
#include <string.h>
#include <dos.h>
#include <dir.h>
#include <conio.h>
#include <alloc.h>
cb=0;
if (!strcmp(strupr(argv[argc-1]),"/I")) cb++; /* parámetro /I */
sp^=cb;
if (argc>cb+1)
unidad=(*argv[1] | 0x20)-'a';
else
unidad=getdisk();
preservar_pantalla (&scrbuf,&modo,&pag,&cur_x,&cur_y,&scr_ok,&cb);
if (!existe_vga()) salida_error (1);
if ((boot=farmalloc(2048L))==NULL) salida_error (2);
if (leesect(unidad, 1, 0L, boot)!=0) salida_error (3);
if (!info_disco (boot, &numsect, &numclusters, &tamcluster,
&inifat, §fat, &tamfat, &tsect)) salida_error(5);
if ((fat=farmalloc(tamfat))==NULL) salida_error (2);
if ((bitfat=farmalloc((long)numclusters))==NULL) salida_error (2);
aviso_espera();
carga_fat (fat, inifat, sectfat, tsect);
genera_bitfat (fat, bitfat, numclusters);
analiza_fat (bitfat, numclusters, &clusters_datos, &clusters_malos);
init_video();
prepara_paleta();
informe_disco (unidad, boot, numsect,
clusters_datos, clusters_malos);
leyendas (numclusters, clusters_datos, clusters_malos);
prepara_punto();
marco();
while (kbhit()) getch();
pinta_fat (bitfat, numclusters);
if (!getch()) getch();
restaurar_pantalla (scrbuf,modo,pag,cur_x,cur_y,scr_ok,cb);
}
*scr_ok=1;
if (*modo==7)
movedata(0xb000,0,FP_SEG(*scrbuf),FP_OFF(*scrbuf),4096);
else
movedata(0xb800,peek(0x40,0x4e),
FP_SEG(*scrbuf),FP_OFF(*scrbuf),4096);
*pag=peekb(0x40,0x62);
*cx=peekb(0x40,0x50+(*pag)*2); *cy=peekb(0x40,0x51+(*pag)*2);
*colorbits=peek(0x40, 0x10) & 0x30;
}
}
void init_video()
{
struct REGPACK r;
void prepara_paleta()
{
struct REGPACK r;
char i, paleta[17];
static unsigned char dac[][3] = {
/* R G B */
{ 0, 0, 0}, /* VGA negro */
r.r_ax=0x1013; r.r_bx=0x0100;
intr (0x10, &r); /* DAC: 16 bloques de 16 elementos */
r.r_ax=0x1013; r.r_bx=1;
intr (0x10, &r); /* página 0: paleta en elementos 0..15 del DAC */
r.r_es=FP_SEG(paleta); r.r_dx=FP_OFF(paleta);
r.r_ax=0x1002; intr (0x10, &r); /* establecer paleta y borde */
void aviso_espera()
{
int cx;
parte1=(sectfat+2)/3;
parte2=(sectfat-parte1)/2;
parte3=sectfat-parte1-parte2; /* la FAT se carga de tres veces */
if (parte1)
if (leesect(unidad, parte1, inifat, fat)!=0) salida_error (3);
if (parte2)
if (leesect(unidad, parte2, inifat+parte1,
fat + (unsigned long) parte1 * tsect)!=0) salida_error (3);
if (parte3)
if (leesect(unidad, parte3, inifat+parte1+parte2, fat +
(unsigned long) (parte1+parte2) * tsect)!=0) salida_error (3);
}
void escribir (int cx, int cy, int color, unsigned char *cadena)
{
struct REGPACK r;
unsigned char *p, pagina;
unsigned char far *cursor_x;
p=cadena;
while (*p) {
r.r_ax=0x900 | *p; r.r_bx = (pagina << 8) | color; r.r_cx=1;
intr (0x10, &r);
(*cursor_x)++;
p++;
}
}
break;
case 2: printf (sp?"\n Memoria insuficiente.\n":
"\n Insufficient memory.\n");
break;
case 3: printf (sp?"\n Unidad incorrecta, no preparada, HPFS o de
red.\n":
"\n Incorrect, not ready, HPFS or network
drive.\n");
break;
case 4: printf (sp?"\n Sólo soportados sistemas FAT12/FAT16.\n":
"\n Only supported FAT12/FAT16 filesystems.\n");
break;
case 5: printf (sp?"\n Sector de arranque dañado, imposible
informar.\n":
"\n Boot record damaged, impossible to analyze
drive.\n");
break;
}
exit (error);
}
switch (longitud) {
case 13: coma=1; div=1000000000L; break;
case 6: coma=2; div=10000L; break;
}
for (i=0; i<longitud; i++, div/=10L) {
if (i==coma) {
cadena[i]='.'; coma+=4; i++;
}
cadena[i]=num/div+'0'; num%=div;
}
cadena[i]=0;
while (((*cadena=='0') || (*cadena=='.')) && (*(cadena+1)))
*cadena++=' ';
}
if (cadena[0]=='0') {
cadena[0]=' ';
if (cadena[1]=='0') cadena[1]=' ';
}
}
unsigned i;
if (numclusters>4084) fat16++;
porc=datos*10000L/numclusters+5;
porc2str (cad, porc); escribir (sp?19:18, 8, C_LEYENDA, cad);
escribir (28, 8, C_LIBRE, "||");
escribir (31, 8, C_LEYENDA, sp?"Area libre (":"Free area (");
porc=(numclusters-datos-malos)*10000L/numclusters+5;
porc2str (cad, porc); escribir (sp?43:42, 8, C_LEYENDA, cad);
escribir (52, 8, C_ERRONEA, "||");
escribir (55, 8, C_LEYENDA, sp?"Area defectuosa (":"Damaged area (");
porc=malos*10000L/numclusters+5;
porc2str (cad, porc); escribir (sp?72:69, 8, C_LEYENDA, cad);
}
void marco()
{
int x, y;
factor=(long) (MAX_X-MIN_X+1)*(MAX_Y-MIN_Y+1);
factor=factor*16384L/numclusters;
asm {
push ax; push bx; push cx; push dx; push si; push di; push es;
mov cx,numclusters
les bx,bitfat
mov si,bx } /* SI --> posición del primer cluster */
proc_fat: asm {
mov al,es:[bx] }
cuenta: asm {
inc bx
cmp al,es:[bx]
loope cuenta
mov di,bx
sub di,si /* DI --> número de cluster hasta donde avanzar */
push si
mov ax,word ptr factor
mul di
mov si,ax
mov ax,di
mov di,dx /* DI:SI producto parcial */
mul word ptr [factor+2] /* DX:AX segundo producto parcial */
add ax,di
adc dx,0 /* DX:AX:SI producto */
shl si,1
rcl ax,1
rcl dx,1
shl si,1
rcl ax,1
rcl dx,1 /* DX:AX = DX:AX:SI / 16384 = pixel */
mov si,dx
mov di,ax
sub di,ant_pixel_l
sbb si,ant_pixel_h /* SI:DI = nº de pixels a pintar */
mov ant_pixel_l,ax
mov ant_pixel_h,dx
push bx; push cx; push ds; push bp;
mov ch,es:[bx-1]
mov bx,coord_x
mov bp,coord_y
mov dx,3CEh
mov ax,0A000h
mov ds,ax
mov al,8
mov cl,bl /* BX = cx, BP = cy*80 */
and cl,7
mov ah,80h
shr ah,cl /* AH = bit a pintar en su sitio */
push bx
mov cl,3
shr bx,cl
add bx,bp /* BX = cy*80+cx/8 */
push si
mov si,80
out dx,ax }
pinta_mas: asm {
mov cl,[bx] /* acceso en lectura */
mov [bx],ch /* pintar punto */
sub di,1
jc dec_msb } /* evitar salto la mayoría de las veces */
incy: asm {
add bx,si
add bp,si
cmp bp,(MAX_Y+1)*80
jb pinta_mas
ror ah,1 /* siguiente pixel en el eje X */
out dx,ax
pop si
pop bx
inc bx
push bx
push si
mov si,80
mov bp,MIN_Y*80
mov cl,3
shr bx,cl
add bx,bp /* BX = cy*80+cx/8 */
push ax
mov ah,1
int 16h
pop ax
jz pinta_mas
pop si; pop bx; pop bp; pop ds; pop cx; pop bx; pop si;
jmp fin_proc } /* tecla pulsada */
dec_msb: asm {
pop si
sub si,1
push si
mov si,80
jnc incy
pop si
pop bx
mov ax,bp
pop bp
pop ds
mov coord_x,bx
mov coord_y,ax
pop cx
pop bx
pop si
jcxz fin_proc
jmp proc_fat }
fin_proc: asm {
pop es; pop di; pop si; pop dx; pop cx; pop bx; pop ax;
}
}
void prepara_punto()
{
asm {
push ax; push dx /* preparar la VGA para punto() */
mov dx,3CEh
mov ax,205h /* registro de modo (5): escr. 2 lect. 0 */
out dx,ax
mov ax,3 /* cambiar AH para hacer OR/XOR/AND */
out dx,ax
pop dx; pop ax
}
}
mov dx,coord_y
xchg bx,cx /* BX = cx, DX = cy */
mov cx,0A000h
mov ds,cx
mov cl,4
shl dx,cl /* DX = cy * 16 */
mov ax,dx
shl ax,1
shl ax,1 /* CX = cy * 64 */
add dx,ax /* DX = cy * 80 */
mov al,bl
dec cl
shr bx,cl /* CL = 3 */
add bx,dx /* BX = cy * 80 + cx / 8 */
and al,7
mov cl,al
mov ah,80h
shr ah,cl /* AH = bit a pintar en su sitio */
mov dx,3CEh /* registro de direcciones */
mov al,8
out dx,ax
mov al,[bx] /* acceso en lectura */
mov ax,color
mov [bx],al
pop dx; pop cx; pop bx; pop ax;
pop ds
}
}
int leesect(int unidad, int nsect, unsigned long psect, void *buffer)
{
struct fatinfo fatdisco;
static anterior_unidad=0xFFFF, tipo_disco;
unsigned buffer_s, buffer_o, psectl, psecth, flags;
buffer_o=FP_OFF(buffer); buffer_s=FP_SEG(buffer);
psectl=psect & 0xFFFF; psecth=psect >> 16;
if (_osmajor>=3) {
r.x.ax=0x3800; s.ds=FP_SEG(info); r.x.dx=FP_OFF(info);
intdosx (&r, &r, &s);
i=0; while (spl[i++]) if (spl[i-1]==r.x.bx) idioma=1;
}
return (idioma);
}
Listado HEX$
; ********************************************************************
; * *
; * HEX$ 1.0 - (c) 1992 Ciriaco García de Celis. *
; * *
; * Controlador de dispositivo para volcado hexadecimal en salida. *
; * *
; ********************************************************************
HEXSEG SEGMENT
ASSUME CS:HEXSEG, DS:HEXSEG
ini_buffer EQU $
DB 8 DUP (0) ; buffer para contener los caracteres
med_buffer EQU $ ; recibidos de 16 en 16.
DB 8 DUP (0)
fin_buffer EQU $
media_check:
build_bpb:
read: ; sólo soportada la salida
read_nowait:
ioctl_input: MOV AX,8103h ; órdenes no soportadas
RET
remove PROC
MOV AX,300h ; indicar
RET ; «controlador ocupado»
remove ENDP
write PROC
write_verify: MOV CX,[BX+12h] ; bytes a transferir
LES DI,[BX+0Eh] ; dirección inicial
MOV AX,CS
MOV DS,AX ; DS: -> HEX$
otro_car: MOV AL,ES:[DI]
XPUSH <CX, DI>
CALL procesa_AL ; procesar carácter
XPOP <DI, CX>
INC DI ; otro carácter
LOOP otro_car
JMP retorno_ok ; siempre Ok.
write ENDP
AL_procesado: RET
procesa_AL ENDP
POP AX
RET
print_8hex ENDP
init PROC
PUSH BX
MOV AH,30h
INT 21h ; obtener versión del DOS
POP BX
CMP AL,3
JAE dos_ok
MOV WORD PTR [BX+0Eh],0 ; OFFSET 0: terminar
MOV [BX+10h],CS ; sin quedar residente
LEA BX,mal_dos_txt
CALL print
MOV AX,100h
RET
dos_ok: LEA AX,retorno_ok
MOV CS:p_rutinas,AX ; anular rutina INIT
MOV WORD PTR [BX+0Eh],OFFSET init
MOV [BX+10h],CS ; indicado área residente
LEA BX,instalado_txt
CALL print
MOV AX,100h ; instalación siempre Ok.
RET
init ENDP
HEXSEG ENDS
END
Listado MAPAMEM
; ********************************************************************
; * *
; * MAPAMEM 2.2 - Utilidad para listar los bloques de memoria. *
; * *
; ********************************************************************
mapamem SEGMENT
ASSUME CS:mapamem; DS:mapamem
mapa PROC
MOV BX,tam_mapmem ; tamaño de este programa
MOV AH,4Ah ; modificar memoria asignada
INT 21h ; ejecutar función del DOS
LEA DX,cabecera_txt
CALL print
MOV AH,52h ; función "Get List of Lists"
INT 21h
MOV AX,ES:[BX-2] ; segmento del primer M.C.B.
MOV ES,AX
DEC AX
CALL print16hex ; imprimir dónde acaba el DOS
INC AX
SUB AX,50h
MOV DX,16
MUL DX ; pasar párrafos a bytes
MOV CL,8+16
CALL print_32 ; imprimir tamaño zona del DOS
LEA DX,cabx_txt
CALL print
otro_mcb: MOV BX,WORD PTR ES:[1] ; P.I.D. (Process ID)
MOV DL,0 ; supuesta zona libre (tipo DL)
CMP BX,0
JE tipo_ok ; lo es (PID = 0)
MOV DL,1 ; supuesto bloque XMS de DR-DOS
CMP BX,6
JE tipo_ok ; lo es (PID = 6)
MOV DL,2 ; supuesta zona del sistema
PUSH DS
MOV DS,BX
MOV AX,WORD PTR DS:[0] ; AX = [PID:0000]
MOV CX,WORD PTR DS:[2Ch] ; CX = [PID:002C]
POP DS
CMP AX,20CDh
JE no_tipo_sys ; es un PSP
CMP AX,27CDh
JNE tipo_ok ; no es un PSP
no_tipo_sys: MOV DL,3 ; supuesta zona de programa
MOV AX,ES
INC AX
CMP BX,AX ; ¿PID=MCB+1?
JE tipo_ok ; lo es
MOV DL,4 ; supuesta zona de entorno
CMP CX,AX
JE tipo_ok
INC DL ; por eliminación zona de datos
tipo_ok: MOV pid,BX
MOV tipo,DL
CALL imprime_tipo ; tipo del bloque
CALL imprime_rango ; ubicación y tamaño
CALL imprime_pid
CALL imprime_nombre
MOV AL,13 ; retorno de carro
CALL printAL
MOV AL,10 ; salto de línea
CALL printAL
MOV AX,ES ; MCB ya tratado
ADD AX,ES:[3] ; tamaño del bloque
INC AX ; apuntar al siguiente MCB
CMP BYTE PTR ES:[0],5Ah ; ¿es el último?
MOV ES,AX ; puntero al siguiente MCB
JNE otro_mcb ; no, no era el último
PUSH AX
INT 12h
MOV BX,64
MUL BX
DEC AX
MOV BX,AX
POP AX
imprime_tipo PROC
LEA SI,tabla_tipos
MOV AL,tipo
XOR AH,AH
SHL AX,1 ; AX = tipo * 2
ADD SI,AX
MOV DX,[SI] ; dirección del mensaje
CALL print ; imprimirlo
RET
imprime_tipo ENDP
imprime_rango PROC
MOV AX,ES
INC AX
CALL print16hex ; imprimir inicio del bloque
MOV AL,'-'
CALL printAL ; imprimir guión
MOV AX,ES
ADD AX,ES:[3]
CALL print16hex ; imprimir final del bloque
MOV AX,ES:[3]
MOV DX,16
MUL DX ; pasar bytes a párrafos
MOV CL,8+16
CALL print_32 ; imprimir tamaño del bloque
RET
imprime_rango ENDP
imprime_pid PROC
MOV AL,' '
CALL printAL
CALL printAL
MOV AX,pid
CALL print16hex
MOV AL,' '
CALL printAL
CALL printAL
RET
imprime_pid ENDP
imprime_nombre PROC
PUSH ES
LEA DX,libre_txt
CMP tipo,0 ; ¿bloque libre?
JNE no_libre ; no
CALL print ; imprimirlo
JMP nombre_ok
no_libre: CMP tipo,1
JE nombre_listo ; bloque XMS: nombre de ES:8 a ES:16
CMP tipo,2
JE nombre_ok ; nombre del propietario desconocido
MOV BX,ES:[1] ; segmento del PSP dueño del bloque
DEC BX ; apuntar al MCB
MOV ES,BX
nombre_listo: MOV BX,7 ; nombre de ES:BX+1 a ES:BX+9
MOV CX,8 ; máximo tamaño del nombre
otra_letra: INC BX
MOV AL,ES:[BX] ; carácter del nombre
AND AL,AL
JZ nombre_ok ; es cero: fin del nombre
CMP AL,' '
JAE cod_normal
MOV AL,'?' ; evitar códigos raros en DOS < 4.0
cod_normal: CALL printAL ; imprimirlo
LOOPNZ otra_letra ; a por otro (8 como máximo)
nombre_ok: POP ES
RET
imprime_nombre ENDP
printAL ENDP
print_32 PROC
PUSHF
PUSH AX ; preservar registros
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
PUSH DS
PUSH ES
MOV BX,CS
MOV DS,BX
MOV ES,BX
MOV formato_pr32,CL ; byte del formato de impresión
MOV BX,OFFSET tabla_pr32
MOV CX,10
digit_pr32: PUSH CX
PUSH AX
PUSH DX
XOR DI,DI
MOV SI,1 ; DISI = 1
DEC CX ; CX - 1
JCXZ hecho_pr32
factor_pr32: SAL SI,1
RCL DI,1 ; DISI * 2
MOV DX,DI
MOV AX,SI
SAL SI,1
RCL DI,1
SAL SI,1
RCL DI,1 ; DISI * 8
ADD SI,AX
ADC DI,DX ; DISI=DISI*8+DISI*2=DISI*10
LOOP factor_pr32 ; DISI=DISI*(10^(CX-1))
hecho_pr32: POP DX
POP AX ; CX se recuperará más tarde
MOV CL,0FFh
rep_sub_pr32: INC CL
SUB AX,SI
SBB DX,DI ; DXAX = DXAX - DISI
JNC rep_sub_pr32 ; restar factor cuanto se pueda
ADD AX,SI ; subsanar el desbordamiento:
ADC DX,DI ; DXAX = DXAX + DISI
ADD CL,'0' ; pasar binario a ASCII
MOV [BX],CL
POP CX ; CX se recupera ahora
INC BX
LOOP digit_pr32 ; próximo dígito del número
STD ; transferencias hacia atrás
DEC BX ; BX apunta al último dígito
MOV final_pr32,BX ; último dígito
MOV ent_frac_pr32,BX ; frontera parte entera/fracc.
MOV CL,5
MOV AL,formato_pr32
SHR AL,CL ; AL = nº de decimales
AND AL,AL
JZ no_frac_pr32 ; ninguno
MOV CL,AL
XOR CH,CH
MOV SI,final_pr32
MOV DI,SI
INC DI
REP MOVSB ; cadena arriba (hacer hueco)
INC final_pr32
MOV AL,fracc_pr32
MOV [DI],AL ; separador de parte fraccional
MOV ent_frac_pr32,SI ; indicar nueva frontera
no_frac_pr32: MOV AL,formato_pr32
TEST AL,16 ; interpretar el formato
JZ poner_pr32 ; imprimir como tal
entera_pr32: MOV CX,final_pr32 ; añadir separadores de millar
SUB CX,ent_frac_pr32
ADD CX,3
MOV SI,final_pr32
MOV DI,SI
INC DI
REP MOVSB ; cadena arriba (hacer hueco)
MOV AL,millares_pr32
MOV [DI],AL ; poner separador de millares
INC final_pr32
MOV ent_frac_pr32,SI ; usar la variable como puntero
SUB SI,OFFSET tabla_pr32
CMP SI,3
JAE entera_pr32 ; próximo separador
poner_pr32: MOV BX,final_pr32
MOV BYTE PTR [BX+1],"$" ; delimitador fin de cadena
MOV BX,OFFSET tabla_pr32
MOV principio_pr32,BX ; inicio de cadena
limpiar_pr32: MOV AL,[BX]
CMP AL,'0'
JE blanco_pr32 ; cero a la izda --> poner " "
CMP AL,millares_pr32 ; separador millares a la izda
JE blanco_pr32
CMP AL,fracc_pr32
JNE acabar_pr32
MOV BYTE PTR [BX-1],'0' ; reponer 0 antes de la coma
DEC principio_pr32
acabar_pr32: MOV AL,formato_pr32 ; imprimir
AND AL,00001111b
XOR AH,AH
MOV DX,final_pr32
SUB DX,AX
INC DX ; DX = offset 'principio'
AND AX,AX
JNZ format_pr32 ; longitud solicitada
MOV DX,principio_pr32 ; longitud obtenida del número
format_pr32: CALL print ; imprimir cadena en DS:DX
POP ES
POP DS ; restaurar todos los registros
POP DI
POP SI
POP DX
POP CX
POP BX
POP AX
POPF
RET ; salida del procedimiento
blanco_pr32: MOV BYTE PTR [BX],' ' ; quitar 0 / separador millares
INC BX ; sustituyendo por espacios
INC principio_pr32
CMP BX,final_pr32
JB limpiar_pr32
MOV DX,BX ; es el número 0.000.000.00X
JMP SHORT acabar_pr32 ; imprimir
formato_pr32 DB 0
DB 5 DUP (' ') ; área de trabajo
tabla_pr32 DT 0
DW 0,0
millares_pr32 EQU '.' ; separador de millares
fracc_pr32 EQU ',' ; " parte fraccional
final_pr32 DW 0 ; offset último byte a imprimir
principio_pr32 DW 0 ; " " primer " " "
ent_frac_pr32 DW 0 ; offset frontera entero-fracc.
print_32 ENDP
; ------------ Datos
tipo DB 0
pid DW 0
tam_mapmem EQU ($-OFFSET mapamem)/16+1 ; tamaño de MAPAMEM
mapamem ENDS
END mapa
;*********************************************************************
;* *
;* RCLOCK v2.3 (c) Septiembre 1992 CiriSOFT *
;* (c) Grupo Universitario de Informática - Valladolid *
;* *
;* »»» Utilidad de reloj-alarma residente ««« *
;* *
;*********************************************************************
XPUSH MACRO RM
XPOP MACRO RM
IRP reg, <RM>
POP reg
ENDM
ENDM
; ------------ Programa
rclock SEGMENT
ASSUME CS:rclock, DS:rclock
ORG 100h
ini_residente EQU $
; ****************************************
; * *
; * D A T O S R E S I D E N T E S *
; * *
; ****************************************
ant_int2F_seg DW 0
; ------------ Sonido
; formato de la música:
; número de nota (0-88), duración (en 1/18,2 seg.)
;
; Las primeras 7 notas son inaudibles y sirven para
; hacer pausas; si al byte de duración se le suma 128,
; se produce una pausa de 1/18,2 segundos antes de que
; suene otra nota. El final se indica con un 255.
musica_alarma DB 47,2,52,2,56,3,1,1,47,2,52,2,56,3,1,1
DB 47,2,52,2,54,3,1,1,51,2,54,2,59,3,1,1
DB 49,2,54,2,59,3,1,1,49,2,54,2,57,3,1,1
DB 49,2,52,2,56,3,1,1,52,2,56,2,61,3,1,1
DB 51,2,56,2,61,3,1,1,51,2,56,2,59,3,1,1
DB 51,2,54,2,57,3,1,1
DB 255
musica_horas DB 61,10,57,10,59,10,52,20,1,7,52,10,59,10,61,10,57
DB 20,255
musica_medias DB 47,7,54,7,56,7,52,7,255
musica_cuartos DB 52,7,56,7,59,10,255
musica_5min DB 57,3+128,57,3+128,1,8,57,3+128,57,3+128,255
minutosL DB 0
DB ":"
segundosH DB 0
segundosL DB 0
DB 0
; ***************************************
; * *
; * C O D I G O R E S I D E N T E *
; * *
; ***************************************
IRET
ges_int08 ENDP
avisos_sonoros PROC
CMP parando,0 ; ¿"callar" durante 1 segundo?
JE avisos_on ; no
DEC parando ; sí
JMP fin_avisos
avisos_on: CMP musica_sonando,1
JNE no_mas_notas ; no hay sonido en curso
DEC contador_nota
JNZ misma_nota ; sigue sonando todavía la nota
CMP turno_blanco,0 ; ¿pausa entre notas?
JE otra_nota ; no
MOV turno_blanco,0 ; sí, sólo una vez
MOV contador_nota,1 ; y durante una interrupción
MOV AX,0 ; período inaudible
CALL programar_8253
misma_nota: JMP fin_avisos
otra_nota: MOV BX,puntero_notas ; puntero a la siguiente nota
INC BX
INC BX
MOV puntero_notas,BX ; actualizarlo
MOV BX,[BX] ; siguiente nota
MOV AL,BH
AND AL,128 ; aislar bit más significativo
ROL AL,1 ; ahora el menos significativo
MOV turno_blanco,AL ; bit de separación entre notas
AND BH,127 ; el resto de BH es la duración
CMP BL,255 ; ¿se acabaron las notas?
JNE sonar ; no, luego tocar esta nota
MOV musica_sonando,0 ; sí
MOV alarm_enable,0 ; desactivar alarma
CALL chiton ; acallar altavoz
JMP no_mas_notas
sonar: INC BH
MOV contador_nota,BH ; INT's 8 que dura esa nota
XOR BH,BH ; BX = posición en la tabla
SHL BX,1 ; la tabla es de palabras
MOV AX,[BX+tabla_periodos] ; período del sonido
CALL programar_8253
JMP fin_avisos
no_mas_notas: CMP alarm_enable,0
JE no_alarma ; alarma desactivada
LEA SI,hora_actual
LEA DI,hora_alarma
MOV CX,8
CLD
REP CMPSB ; ¿hora actual = hora alarma?
JNE no_alarma ; no es la hora de la alarma
LEA AX,musica_alarma-2 ; sí lo es
JMP fin_avisando
no_alarma: MOV CL,tipo_aviso
MOV SI,WORD PTR minutosH
MOV DI,WORD PTR segundosH
CMP SI,"00" ; ¿hora en punto?
JNE media?
CMP DI,"00"
JNE media?
LEA AX,musica_horas-2 ; hora en punto
chiton PROC
IN AL,61h
AND AL,0FCh
JMP SHORT $+2
JMP SHORT $+2
OUT 61h,AL ; altavoz silenciado
RET
chiton ENDP
programar_8253 PROC
PUSH AX
MOV AL,182 ; preparar canal 2
OUT 43h,AL
POP AX
JMP SHORT $+2
JMP SHORT $+2
OUT 42h,AL
MOV AL,AH
JMP SHORT $+2
JMP SHORT $+2
OUT 42h,AL ; canal #2 del 8253 programado
JMP SHORT $+2
JMP SHORT $+2
IN AL,61h
OR AL,3
gestiona_fondo PROC
MOV AH,15
INT 10h ; modo de vídeo AL y página BH
CMP AL,modo_video ; ¿ha cambiado modo de vídeo?
JNE clr_fondo? ; en efecto
CMP BH,pagina ; ¿ha cambiado la página?
JNE clr_fondo? ; así es
RET ; no ha cambiado nada
clr_fondo?: MOV modo_video,AL ; actualizar nuevos parámetros
MOV pagina,BH
MOV BL,c_x ; coordenada X teórica
CMP BL,72 ; ¿es la 72?
JNE dejar_c_x ; no: se deja como tal
MOV BL,AH ; sí: ajustar posición lo más
SUB BL,8 ; a la derecha posible
dejar_c_x: MOV c_xx,BL ; coordenada X real
CMP AL,3 ; ¿modo de texto de color?
JBE get_fondo ; sí: preservar área pantalla
CMP AL,7 ; ¿modo de texto monocromo?
JE get_fondo ; sí: preservar área pantalla
MOV CX,8 ; modo gráfico: no preservar,
LEA BX,restaurar ; cubrir con espacios en blanco
fondo_clr_ar: MOV BYTE PTR DS:[BX],' '
MOV BYTE PTR DS:[BX+8],7 ; y atributos blancos
INC BX
LOOP fondo_clr_ar ; acabar buffer
RET
get_fondo: MOV operacion,8 ; preservar zona de la pantalla
CALL bios_scr_proc
RET
gestiona_fondo ENDP
print_reloj PROC
MOV AH,3
MOV BH,pagina
INT 10h ; coordenadas del cursor en DX
PUSH DX ; guardarlas para restaurarlas
MOV AH,2
MOV DL,c_xx
MOV DH,c_y ; coordenadas del reloj
MOV BH,pagina
INT 10h ; ubicar cursor
LEA BX,hora_actual ; cadena a imprimir
CALL bios_print ; imprimir reloj
POP DX ; recuperar posición del cursor
MOV BH,pagina ; y página activa
MOV AH,2
INT 10h ; restaurar posición del cursor
RET
print_reloj ENDP
obtiene_hora PROC
PUSH DS
XOR AX,AX
MOV DS,AX
MOV SI,DS:[46Ch]
MOV DI,DS:[46Eh] ; contador de hora del BIOS
POP DS
MOV AX,1080
CALL mult32x16 ; DXDISI = DISI * 1080
MOV AX,19663
CALL divi48x15 ; DXDISI = DXDISI / 19663
PUSH DI
PUSH SI ; DISI = tics/18,2065 = seg.
MOV AX,3600
CALL divi48x15
MOV AX,SI ; AX = SI = horas
MOV CL,10
DIV CL ; pasar a BCD no empaquetado
OR AX,"00" ; pasar BCD a ASCII
CMP AL,'0'
JNE no_cero_izda
MOV AL,' ' ; evitar cero a la izda en hora
no_cero_izda: MOV horasH,AL
MOV horasL,AH
MOV AX,3600
MUL SI ; DXAX = horas*3600
POP SI
POP DI
SUB SI,AX
SBB DI,DX ; DISI = segundos+minutos*60
MOV AX,SI
MOV CL,60
DIV CL ; AL = minutos
PUSH AX
MOV AH,0
MOV CL,10
DIV CL ; pasar binario a BCD
OR AX,"00" ; pasar BCD a ASCII
MOV minutosH,AL
MOV minutosL,AH
POP AX
MOV CL,60
MUL CL
SUB SI,AX ; SI = segundos restantes
MOV AX,SI
MOV CL,10
DIV CL ; pasar binario a BCD
OR AX,"00" ; pasar BCD a ASCII
MOV segundosH,AL
MOV segundosL,AH
RET
obtiene_hora ENDP
bios_print PROC
cursor_derecha PROC
MOV BH,pagina
MOV AH,3
INT 10h ; DX = coordenadas actuales
INC DL ; incrementar X (sin controlar
MOV AH,2 ; posible desbordamiento)
MOV BH,pagina
INT 10h ; actualizar posición cursor
RET
cursor_derecha ENDP
bios_scr_proc PROC
MOV AH,3
MOV BH,pagina
INT 10h ; obtener posición del cursor
PUSH DX ; y preservarla para el final
MOV AH,2
MOV DL,c_xx
MOV DH,c_y ; coordenadas del reloj
MOV BH,pagina
INT 10h ; mover cursor
LEA SI,restaurar ; dirección del buffer
MOV CX,8 ; 8 caracteres
proximo_car: PUSH CX
MOV AH,operacion ; 8 ->preservar, 9 ->restaurar
MOV BH,pagina
MOV BL,[SI+8] ; preparar BL por si AH=9
MOV AL,[SI] ; preparar AL por si AH=9
MOV CX,1 ; preparar CX por si AH=9
INT 10h ; leer/escribir carácter
CMP operacion,8 ; ¿se trataba de leer?
JNE opcont ; no
MOV [SI],AL ; sí, guardar carácter leído
MOV [SI+8],AH ; y su atributo
opcont: CALL cursor_derecha ; siguiente posición
INC SI ; próximo carácter
POP CX
LOOP proximo_car ; acabar caracteres
POP DX ; recuperar coordenadas
MOV BH,pagina
MOV AH,2
INT 10h ; y reponer posición del cursor
RET
bios_scr_proc ENDP
mult32x16 PROC
PUSH AX
XCHG SI,AX ; multiplicador en SI
MUL SI ; AX (parte baja) * SI --> DXAX
PUSH DX ; preservar resultado parcial
PUSH AX
MOV AX,DI
MUL SI ; AX (parte alta) * SI --> DXAX
POP SI ; parte baja del resultado
POP DI ; parte media del resultado
ADD DI,AX ; acumular resultado intermedio
ADC DX,0 ; arrastrar posible acarreo
POP AX
RET
mult32x16 ENDP
divi48x15 PROC
PUSH BX
PUSH CX
XOR BX,BX
MOV CX,49 ; rotar 49 veces
divi48_15_cmp: CMP AX,BX
JA divi48_nosub
SUB BX,AX
STC
divi48_nosub: RCL SI,1
RCL DI,1
RCL DX,1
PUSHF
CMP CX,1
JE divi48_resto ; ¡no rotar el resto al final!
POPF
RCL BX,1
PUSHF
divi48_resto: POPF
LOOP divi48_15_cmp
MOV AX,BX
POP CX
POP BX
RET
divi48x15 ENDP
; *****************************
; * *
; * I N S T A L A C I O N *
; * *
; *****************************
main PROC
LEA DX,rclock_txt ; nombre del programa
CALL print
CALL obtener_param ; analizar posibles parámetros
JNC params_ok ; son correctos
CALL print_err ; no: informar del error/ayuda
JMP fin_noresid
params_ok: CALL inic_XMS ; considerar presencia de XMS
CALL residente? ; ¿programa ya residente?
JC no_residente ; todavía no
CMP param_u,1 ; sí: ¿solicitan desinstalarlo?
JE desinst ; así es
CALL adaptar_param ; parámetros en copia residente
JMP fin_noresid
desinst: MOV ES,tsr_seg
CALL rclock_off
MOV AH,ES:multiplex_id
CALL mx_unload ; desinstalarlo:
LEA DX,des_ok_txt
JNC mens_ok ; ha sido posible
LEA DX,des_no_ok_txt ; es imposible
mens_ok: CALL print
JMP fin_noresid
no_residente: CMP AX,0 ; ¿reside una versión distinta?
JE instalable ; no: se admite instalación
CALL error_version ; error de versión incompatible
JMP fin_noresid
instalable: CMP param_u,1 ; no residente: ¿desinstalar?
JNE instalar ; no lo piden
LEA DX,imp_desins_txt ; lo piden, ¡serán despistados!
CALL print
JMP fin_noresid
instalar: CALL mx_get_handle ; obtener entrada Multiplex
JNC handle_ok
LEA DX,nocabe_txt ; no quedan entradas
CALL print
JMP fin_noresid
handle_ok: MOV multiplex_id,AH ; entrada multiplex para RCLOCK
LEA DX,instalado_txt ; mensaje de instalación
CALL print
CALL preservar_ints ; tomar nota de vectores
CMP param_ml,0 ; ¿se indicó parámetro /ML?
JNE instalar_ml ; en efecto
MOV AX,parrafos_resid ; párrafos de memoria precisos
CALL UMB_alloc ; pedir memoria superior XMS
JNC instalar_umb ; hay la suficiente
MOV AX,parrafos_resid
CALL UPPER_alloc ; pedir memoria superior DOS 5
JC instalar_ml ; no hay la suficiente
STC ; indicar que usa memoria DOS
instalar_umb: MOV ES,AX ; segmento del bloque UMB
MOV DI,0 ; ES:0 zona a donde reubicar
CALL inicializa_id ; inicializar identificación
CALL reubicar_prog ; reubicar el programa a ES:DI
CALL activar_ints ; interceptar vectores
;*********************************************************
;* *
;* SUBRUTINAS DE PROPOSITO GENERAL PARA LA INSTALACION *
;* *
;*********************************************************
obtener_param PROC
MOV BX,81h ; apuntar a zona de parámetros
otro_pmt_mas: CALL saltar_esp ; saltar delimitadores
JNC otro_pmt ; quedan más parámetros
JMP fin_proc_pmt ; no más parámetros
otro_pmt: CMP AL,'/'
JE pmt_barrado ; parámetro precedido por '/'
CMP AL,'?'
MOV DH,128 ; código de «error» para ayuda
JNE pmt_nobarrado
JMP mal_proc_pmt ; «error» de solicitud de ayuda
pmt_nobarrado: OR WORD PTR [BX]," " ; pasar a minúsculas
CMP WORD PTR [BX],"no" ; ¿parámetro ON?
JNE pmt_off?
MOV visibilidad,1
MOV visible,1
MOV param_onoff,1
ADD BX,2
JMP otro_pmt_mas
pmt_off?: CMP WORD PTR [BX],"fo" ; ¿parámetro OFx?
MOV DH,0 ; código de error
JNE mal_proc_pmt
OR BYTE PTR [BX+2],' ' ; pasar a minúsculas
CMP BYTE PTR [BX+2],'f' ; ¿parámetro OFF?
JNE mal_proc_pmt
MOV visibilidad,0
MOV visible,0
MOV param_onoff,1
ADD BX,3
JMP otro_pmt_mas
pmt_barrado: INC BX
MOV AL,[BX] ; letra del parámetro
CMP AL,13 ; ¿fin de mandatos?
MOV DH,0
JE mal_proc_pmt ; falta parámetro
CMP AL,'?'
MOV DH,128 ; código de «error» para ayuda
JE mal_proc_pmt
pmt_A: PUSH BX
CALL get_num
JNC bien_pmt_A
POP BX
ADD BX,2
OR WORD PTR [BX]," " ; pasar a minúsculas
CMP WORD PTR [BX],"no" ; ¿parámetro ON?
JNE pmt_A_off?
MOV alarm_enable,1
MOV param_a_onoff,1
ADD BX,2
JMP otro_pmt_mas
pmt_A_off?: CMP WORD PTR [BX],"fo" ; ¿parámetro OFx?
MOV DH,0 ; código de error
JNE mal_proc_pm
OR BYTE PTR [BX+2],' ' ; pasar a minúsculas
CMP BYTE PTR [BX+2],'f' ; ¿parámetro OFF?
JNE mal_proc_pm
MOV alarm_enable,0
MOV param_a_onoff,1
ADD BX,3
JMP otro_pmt_mas
bien_pmt_A: MOV param_a,1
ADD SP,2 ; «sacar» BX de la pila
CMP AX,23
JA mal_pmtA
MOV CL,10
DIV CL ; pasar binario a BCD
ADD AX,"00" ; pasar BCD a ASCII
CMP AL,'0'
JNE no_cero_izda2
MOV AL,' ' ; evitar cero a la izda. hora
no_cero_izda2: MOV BYTE PTR alarm_h,AL
MOV BYTE PTR alarm_h+1,AH
DEC BX
CALL get_num
JC mal_pmtA
CMP AX,59
JA mal_pmtA
MOV CL,10
DIV CL ; pasar binario a BCD
ADD AX,'00' ; pasar BCD a ASCII
MOV BYTE PTR alarm_m,AL
MOV BYTE PTR alarm_m+1,AH
DEC BX
CALL get_num
JC mal_pmtA
CMP AX,59
JA mal_pmtA
MOV CL,10
DIV CL ; pasar binario a BCD
ADD AX,'00' ; pasar BCD a ASCII
MOV BYTE PTR alarm_s,AL
MOV BYTE PTR alarm_s+1,AH
MOV alarm_enable,1
JMP otro_pmt_mas
mal_pmtA: MOV DH,1
mal_proc_pm: JMP mal_proc_pmt
obtener_param ENDP
get_num: INC BX
MOV AL,[BX]
INC BX
CMP AL,'='
JE delimit_ok
CMP AL,':'
JE delimit_ok
err_sintax: STC ; sintaxis incorrecta
RET
delimit_ok: MOV AL,[BX]
CALL obtener_num
JC err_sintax
INC BX
RET
obtener_num PROC
CMP AL,0Dh ; fin zona parámetros y número
JE fin_num
CMP AL,32 ; fin número
JE fin_num
CMP AL,9 ; fin número
JE fin_num
CMP AL,'/' ; fin número (otro parámetro)
JE fin_num
CMP AL,':' ; fin número (otro dato)
JE fin_num
INC BX
MOV AL,[BX]
JMP obtener_num
fin_num: MOV SI,BX
DEC SI
XOR DX,DX
MOV AX,1 ; AX = 10 elevado a la 0 = 1
otro_car: DEC BX ; próximo carácter a procesar
MOV CL,[BX]
CMP CL,'='
JE ok_num ; delimitador: fin de número
CMP CL,':'
JE ok_num ; delimitador: fin de número
CMP CL,'.'
JNE no_millar ; saltar los puntos de millar
CMP AX,1000
JE otro_car
JMP mal_num ; separador millar descolocado
no_millar: CMP CL,'0'
JB mal_num
CMP CL,'9'
JA mal_num
SUB CL,'0' ; pasar ASCII a binario
MOV CH,0 ; CX = 0 .. 9
PUSH AX ; AX = 10 elevado a la N
AND AX,AX
JNZ multiplica
AND CL,CL
JNZ mal_num_pop ; a la izda sólo permitir ceros
multiplica: PUSH DX ; tras completar 5º dígito
MUL CX
POP DX
JC mal_num_pop
ADD DX,AX ; DX = DX + digito (CX) * 10 ^ N (AX)
JC mal_num_pop
POP AX
CMP AX,10000
JNE potencia ; AX*10 no se desbordará
MOV AX,0 ; como próximo dígito<>0 a
JMP otro_car ; la izda ... pobre usuario
potencia: MOV DI,10
PUSH DX ; no manchar DX al multiplicar
MUL DI ; AX = AX elevado a la (N+1)
POP DX
JMP otro_car
mal_num_pop: POP AX ; reequilibrar pila
mal_num: MOV BX,SI ; número mayor de 65535
STC ; condición de error
RET
ok_num: MOV BX,SI ; número correcto
MOV AX,DX ; resultado
CLC ; condición de Ok.
RET
obtener_num ENDP
print_err PROC
CMP DH,128 ; error: DH código de error
JNE no_ayuda
LEA DX,ayuda_txt
JMP pr_ret
no_ayuda: MOV AH,DH
MOV AL,CL ; CL=parámetro en errores 1..6
LEA DX,ini_err_txt
CALL print
LEA BX,tabla_err ; tabla de mensajes de error
PUSH AX
MOV AL,AH
SHL AL,1 ; AL = AL * 2
XOR AH,AH ; AX = AL
ADD BX,AX
MOV DX,[BX] ; dirección del texto
CALL print
POP AX ; recuperar código y parámetro
CMP AH,1
JBE no_pr_pmt ; error 0 ó 1
MOV DL,AL
MOV AH,2
INT 21h ; imprimir letra del parámetro
no_pr_pmt: LEA DX,fin_err_txt
pr_ret: CALL print
RET
print_err ENDP
error_version PROC
PUSH ES
LEA DX,mal_ver_txt1
CALL print
LES DI,tsr_dir
MOV AL,':'
MOV CL,255
CLD
REPNE SCASB
REPNE SCASB
MOV DL,ES:[DI] ; número de versión
MOV AH,2
INT 21h
MOV DL,'.'
MOV AH,2
INT 21h
MOV DL,ES:[DI+2] ; revisión
MOV AH,2
INT 21h
LEA DX,mal_ver_txt2
CALL print
POP ES
RET
error_version ENDP
inic_XMS PROC
MOV AX,4300h
INT 2Fh ; chequear presencia XMS
CMP AL,80h
JNE XMS_ausente ; no instalado
PUSH ES
MOV AX,4310h
INT 2Fh ; sí: obtener su dirección
MOV XMS_off,BX ; y preservarla
MOV XMS_seg,ES
MOV xms_ins,1
POP ES
RET
XMS_ausente: MOV xms_ins,0
RET
inic_XMS ENDP
residente? PROC
PUSH CX
PUSH SI
PUSH DI
PUSH ES
PUSH AX
LEA DI,autor_nom_ver ; identificación del programa
MOV SI,DI
MOV AL,0
MOV CL,255
CLD
REPNE SCASB
SUB DI,SI
MOV CX,DI ; tamaño autor+programa+versión
MOV AX,1492h
MOV ES,AX
MOV DI,1992h ; ES:DI protocolo de búsqueda
CALL mx_find_tsr ; buscar si está en memoria
MOV tsr_off,DI ; anotar la dirección programa
MOV tsr_seg,ES ; por si estaba instalado
POP AX
JNC resid_ok ; CF=0 -> programa ya residente
POP ES
PUSH ES
LEA DI,autor_nom_ver
MOV SI,DI
MOV AL,':'
MOV CL,255
REPNE SCASB
REPNE SCASB
SUB DI,SI
MOV CX,DI ; tamaño autor+programa
MOV AX,1492h
MOV ES,AX
MOV DI,1992h ; ES:DI protocolo de búsqueda
CALL mx_find_tsr ; buscar si está en memoria
MOV tsr_off,DI ; anotar dirección del programa
MOV tsr_seg,ES ; por si instalada otra versión
MOV AX,0
JC resid_ok ; CF=1, AX=0 -> no residente
MOV AX,1
STC ; CF=1, AX=1 -> sí: otra vers.
resid_ok: POP ES
POP DI
POP SI
POP CX
RET
residente? ENDP
adaptar_param PROC
LEA DX,ya_install_txt
CALL print
MOV ES,tsr_seg
CMP param_onoff,1
JNE param_a?
MOV AL,visibilidad ; parámetros ON u OFF:
MOV ES:visibilidad,AL ; adaptar visibilidad del reloj
param_a?: CMP param_a,1
JNE param_aonoff?
LEA SI,alarm_enable ; parámetro /A=hh:mm:ss
rclock_off PROC
MOV ES:visibilidad,0
CALL espera_reloj ; eliminarlo de la pantalla
MOV ES:musica_sonando,0
IN AL,61h ; parar posible sonido
AND AL,0FCh
JMP SHORT $+2
JMP SHORT $+2
OUT 61h,AL
RET
rclock_off ENDP
espera_reloj PROC
PUSH DS
PUSH AX
PUSH CX
MOV CL,refresco ; nº tics suficientes para que
MOV CH,0 ; aparezca en pantalla
ADD CX,2 ; redondear hacia arriba
MOV AX,40h
MOV DS,AX
STI
espera_tics: MOV AX,DS:[6Ch]
espera_tic: CMP AX,DS:[6Ch]
JE espera_tic
LOOP espera_tics
POP CX
POP AX
POP DS
RET
espera_reloj ENDP
preservar_INTs PROC
PUSH ES
PUSH DI
LEA DI,tabla_vectores
MOV CL,[DI-1]
MOV CH,0 ; CX vectores interceptados
otro_vector: PUSH CX
PUSH DI
MOV AH,35h
MOV AL,[DI]
INT 21h ; obtener vector de INT xx
POP DI
POP CX
MOV [DI+1],BX ; anotar donde apunta
MOV [DI+3],ES
ADD DI,5
LOOP otro_vector ; repetir con los restantes
POP DI
POP ES
RET
preservar_INTs ENDP
free_environ PROC
PUSH ES
MOV ES,DS:[2Ch] ; dirección del entorno
MOV AH,49h
INT 21h ; liberar espacio de entorno
POP ES
RET
free_environ ENDP
UMB_alloc PROC
PUSH BX
PUSH CX
PUSH DX
CMP xms_ins,1
JNE no_umb_disp ; no hay controlador XMS
MOV DX,AX ; número de párrafos
MOV AH,10h ; solicitar memoria superior
CALL gestor_XMS
CMP AX,1 ; ¿ha ido todo bien?
MOV AX,BX ; segmento UMB/código de error
UPPER_alloc PROC
PUSH AX
MOV AH,30h
INT 21h
CMP AL,5
POP AX
JAE UPPER_existe
STC
JMP UPPER_fin ; necesario DOS 5.0 mínimo
UPPER_existe: PUSH AX ; preservar párrafos...
MOV AX,5800h
INT 21h
MOV alloc_strat,AX ; preservar estrategia
MOV AX,5802h
INT 21h
MOV umb_state,AL ; preservar estado UMB
MOV AX,5803h
MOV BX,1
INT 21h ; conectar cadena UMB's
MOV AX,5801h
MOV BX,41h
INT 21h ; High Memory best fit
POP BX ; ...párrafos requeridos
MOV AH,48h
INT 21h ; asignar memoria
PUSHF
PUSH AX ; guardado el resultado
MOV AX,5801h
MOV BX,alloc_strat
INT 21h ; restaurar estrategia
MOV AX,5803h
MOV BL,umb_state
XOR BH,BH
INT 21h ; restaurar estado cadena UMB
POP AX
POPF
JC UPPER_fin ; hubo fallo
PUSH DS
DEC AX
MOV DS,AX
INC AX
MOV WORD PTR DS:[1],AX ; manipular PID
MOV WORD PTR DS:[16],20CDh ; simular PSP
PUSH ES
MOV CX,DS
MOV ES,CX
MOV CX,CS
DEC CX
MOV DS,CX
MOV CX,8
MOV SI,CX
MOV DI,CX
CLD
REP MOVSB ; copiar nombre de programa
POP ES
POP DS
CLC
UPPER_fin: RET
UPPER_alloc ENDP
inicializa_id PROC
PUSHF
MOV segmento_real,ES ; anotar segmento del bloque
MOV offset_real,DI ; ídem con el offset
MOV longitud_total,parrafos_resid
MOV CL,4
MOV AX,DI
SHR AX,CL
ADD longitud_total,AX ; consumirá desde offset=0
MOV AL,1
POPF ; CF=0: usar memoria UMB XMS
JNC info_ok
DEC AL ; usar memoria convencional
info_ok: OR info_extra,AL
RET
inicializa_id ENDP
reubicar_prog PROC
PUSH DI
LEA SI,ini_residente
MOV CX,bytes_resid
CLD
ADD SI,2 ; no copiar primera palabra
ADD DI,2 ; respetar primera palabra
SUB CX,2
REP MOVSB
POP DI
RET
reubicar_prog ENDP
activar_INTs PROC
PUSH CX
PUSH DS ; preservar DS para el retorno
MOV AX,100h
SUB AX,DI ; AX = 100h-DI
MOV CL,4
SHR AX,CL ; AX = (100h-DI)/16
MOV CX,ES
SUB CX,AX
MOV DS,CX
LEA SI,offsets_ints
MOV CX,CS:[SI] ; CX vectores a desviar
ADD SI,2
desvia_otro: MOV AL,CS:[SI] ; número del vector en curso
MOV DX,CS:[SI+1] ; obtener offset
MOV AH,25h
INT 21h ; desviar INT xx a DS:DX
ADD SI,3
LOOP desvia_otro
POP DS
POP CX
RET
activar_INTs ENDP
mx_get_handle PROC
MOV AH,0C0h
mx_busca_hndl: PUSH AX
MOV AL,0
INT 2Fh
CMP AL,0FFh
POP AX
JNE mx_si_hueco
INC AH
JNZ mx_busca_hndl
mx_no_hueco: STC
RET
mx_si_hueco: CLC
RET
mx_get_handle ENDP
mx_find_tsr PROC
MOV AH,0C0h
mx_rep_find: PUSH AX
PUSH CX
PUSH SI
PUSH DS
PUSH ES
PUSH DI
MOV AL,0
PUSH CX
INT 2Fh
POP CX
CMP AL,0FFh
mx_unload PROC
PUSH ES
CALL mx_ul_tsrcv?
JNC mx_ul_able
POP ES
RET
mx_ul_able: XOR AL,AL
XCHG AH,AL
MOV BP,AX ; BP=entrada Multiplex del TSR
MOV CX,2
mx_ul_pasada: PUSH CX ; siguiente pasada
LEA SI,tabla_vectores
MOV CL,ES:[SI-1]
MOV CH,0 ; CX = nº vectores
mx_ul_masvect: POP AX
PUSH AX ; pasada en curso
DEC AL
PUSH CX
mx_ul_2f: MOV AL,ES:[SI] ; vector en curso
JNZ mx_ul_pasok
CMP CX,1 ; ¿último vector?
JNE mx_ul_noult
MOV AL,2Fh
LEA SI,tabla_vectores
mx_ul_busca2f: CMP ES:[SI],AL ; ¿INT 2Fh?
JE mx_ul_pasok
ADD SI,5
JMP mx_ul_busca2f
mx_ul_noult: CMP AL,2Fh ; ¿restaurar INT 2Fh?
JNE mx_ul_pasok
ADD SI,5
JMP mx_ul_2f
mx_ul_pasok: PUSH ES
PUSH AX
MOV AH,0
SHL AX,1
SHL AX,1
DEC AX
MOV CS:mx_ul_tsroff,AX
MOV CS:mx_ul_tsrseg,0 ; apuntar a tabla vectores
POP AX
PUSH AX
MOV AH,35h
INT 21h ; vector en ES:BX
POP AX
MOV CL,4
SHR BX,CL
MOV DX,ES
ADD DX,BX ; INT xx en DX (aprox.)
MOV AH,0C0h
mx_ul_masmx: CALL mx_ul_tsrcv?
JNC mx_ul_tsrcv
JMP mx_ul_otro
mx_ul_tsrcv: PUSH ES:[DI-16] ; ...TSR del convenio en ES:DI
PUSH ES:[DI-12]
MOV DI,ES:[DI-8] ; offset a la tabla de vectores
MOV CL,ES:[DI-1]
MOV CH,0 ; número de vectores en CX
mx_ul_buscav: CMP AL,ES:[DI]
JE mx_ul_usavect ; este TSR usa vector analizado
ADD DI,5
LOOP mx_ul_buscav
ADD SP,4 ; no lo usa
JMP mx_ul_otro
mx_ul_usavect: POP CX ; tamaño del TSR
POP BX ; segmento del TSR
CMP DX,BX
JB mx_ul_otro ; la INT xx no le apunta
ADD BX,CX
CMP DX,BX
JA mx_ul_otro ; la INT xx le apunta
PUSH AX
XOR AL,AL
XCHG AH,AL
CMP AX,BP ; ¿es el propio TSR?
POP AX
JNE mx_ul_chain ; no
POP ES ; sí: ¡posible reponer vector!
POP CX
POP BX
PUSH BX
PUSH CX
PUSH ES
DEC BX
JNZ mx_ul_norest ; no es la segunda pasada
POP ES ; segunda pasada...
PUSH ES
PUSH DS
MOV BX,CS:mx_ul_tsroff ; restaurar INT's
MOV DS,CS:mx_ul_tsrseg
CLI
MOV CX,ES:[SI+1]
MOV [BX+1],CX
MOV CX,ES:[SI+3]
MOV [BX+3],CX
STI
POP DS
mx_ul_norest: POP ES
POP CX
ADD SI,5 ; siguiente vector
DEC CX
JZ mx_unloadable ; no más, ¡desinstal-ar/ado!
JMP mx_ul_masvect
mx_ul_chain: MOV CS:mx_ul_tsroff,DI ; ES:DI almacena la dirección
MOV CS:mx_ul_tsrseg,ES ; de la variable vector
MOV DX,ES:[DI+1]
MOV CL,4
SHR DX,CL
MOV CX,ES:[DI+3]
ADD DX,CX ; INT xx en DX (aprox.)
MOV AH,0BFh
mx_ul_otro: INC AH ; a por otro TSR
JZ mx_ul_exitnok ; ¡se acabaron!
JMP mx_ul_masmx
mx_ul_exitnok: ADD SP,6 ; equilibrar pila
POP ES
STC
RET ; imposible desinstalar
mx_unloadable: POP CX
DEC CX
JZ mx_ul_exitok ; desinstalado
JMP mx_ul_pasada ; 1ª pasada exitosa: por la 2ª
mx_ul_exitok: TEST ES:info_extra,111b ; ¿tipo de instalación?
MOV ES,ES:segmento_real ; segmento real del bloque
JZ mx_ul_freeml ; cargado en RAM convencional
CMP xms_ins,1
JNE mx_ul_freeml ; no hay controlador XMS (¿?)
MOV DX,ES
MOV AH,11h
CALL gestor_XMS ; liberar memoria superior
POP ES
CLC
RET
mx_ul_freeml: MOV AH,49h
INT 21h ; liberar bloque de memoria ES:
POP ES
CLC
RET
mx_ul_tsrcv?: PUSH AX ; ¿es TSR del convenio?...
PUSH ES
PUSH DI
MOV DI,1492h
MOV ES,DI
MOV DI,1992h
INT 2Fh
CMP AX,0FFFFh
JNE mx_ul_ncvexit
CMP WORD PTR ES:[DI-4],"#*"
JNE mx_ul_ncvexit
CMP WORD PTR ES:[DI-2],"*#"
JNE mx_ul_ncvexit
ADD SP,4 ; CF=0
POP AX
RET
mx_ul_ncvexit: POP DI ; ...no es TSR del convenio
POP ES
POP AX
STC ; CF=1
RET
mx_ul_tsroff DW 0
mx_ul_tsrseg DW 0
mx_unload ENDP
print PROC
PUSH AX
MOV AH,9
INT 21h
POP AX
RET
print ENDP
; ***********************************************
; * *
; * D A T O S N O R E S I D E N T E S *
; * *
; ***********************************************
DB "siempre refe-",13,10
DB " ridas al modo texto, aunque la pantalla esté en modo "
DB "gráfico. Para /X=72",13,10
DB " (valor por defecto) el reloj no se imprimirá realmente en "
DB "la columna 72,",13,10
DB " sino lo más a la derecha posible según el modo de vídeo "
DB "activo.",13,10
DB " /C Indica los atributos de color en que aparece el reloj."
DB 13,10
DB " /U Permite desinstalar el programa de la memoria si ello es "
DB "posible.",13,10
DB " /ML Fuerza la instalación en memoria convencional -por defecto "
DB "se cargará en",13,10
DB " memoria superior XMS o en su ausencia en la administrada "
DB "por el DOS 5.0-",13,10,"$"
rclock ENDS
END inicio
Listado SCRCAP
; ********************************************************************
; * *
; * SCRCAP 1.0 *
; * *
; * Utilidad residente de captura de pantallas de texto. *
; * *
; ********************************************************************
XPUSH MACRO RM
IRP reg, <RM>
PUSH reg
ENDM
ENDM
XPOP MACRO RM
IRP reg, <RM>
POP reg
ENDM
ENDM
; ------------ Programa
scrcap SEGMENT
ASSUME CS:scrcap, DS:scrcap
ORG 100h
ini_residente EQU $
crit_err_off DW ?
crit_err_seg DW ?
ant_pila_off DW ?
ant_pila_seg DW ?
mainpsp DW ? ; PSP del programa principal
maindta LABEL DWORD ; DTA del programa principal
maindta_off DW ?
maindta_seg DW ?
errinfo LABEL DWORD ; Extended error information
errinfo_ax DW ? ; del programa principal
errinfo_bx DW ?
errinfo_cx DW ?
DW 8 DUP (0) ; DX, SI, DI, DS, ES, etc.
ret_off DW ?
ret_seg DW ?
ret_flags DW ?
fich_nom DB "SCRxx-00.SCR",0
fich_handle DW ?
local_ints DW 3
DB 1Bh ; INT 1Bh
DW ges_int1B ; nueva dirección
ant_int1B LABEL DWORD ; dirección original
ant_int1B_off DW 0
ant_int1B_seg DW 0
DB 23h ; INT 23h
DW ges_int23 ; nueva dirección
ant_int23 LABEL DWORD ; dirección original
ant_int23_off DW 0
ant_int23_seg DW 0
DB 24h ; INT 24h
DW ges_int24 ; nueva dirección
ant_int24 LABEL DWORD ; dirección original
ant_int24_off DW 0
ant_int24_seg DW 0
ges_int08 PROC
PUSHF
CALL CS:ant_int08
STI
CMP CS:inminente,ON
JNE exit_08 ; no hay ejecución pendiente
CALL proceso_tsr ; ejecutar TSR si es posible
exit_08: IRET
ges_int08 ENDP
ges_int09 PROC
STI
PUSH AX
IN AL,60h
PUSHF
CALL CS:ant_int09
CMP AL,CS:cod_rastreo ; ¿tecla de activación?
JNE fin_09
MOV AX,40h
PUSH DS
MOV DS,AX
MOV AL,DS:[17h]
POP DS
AND AL,15
CMP AL,CS:marcas ; ¿marcas de activación?
JNE fin_09
CALL proceso_tsr ; ejecutar TSR si es posible
fin_09: POP AX
IRET
ges_int09 ENDP
MOV AL,CS:[SI+2]
MOV AH,25h
MOV DX,CS:[SI+5]
MOV DS,CS:[SI+7]
INT 21h ; INT xx restaurada
ADD SI,7
POP CX
LOOP pop_otro
POP DS
RET
pop_ints ENDP
pushset_dta PROC
pop_dta PROC
PUSH DS
MOV AH,1Ah
MOV DX,maindta_off
MOV DS,maindta_seg
INT 21h ; restaurar DTA
POP DS
RET
pop_dta ENDP
push_crit_err PROC
CMP dosver,300h
JB push_crit_fin ; necesario DOS 3.0+
MOV AH,59h
MOV BX,0
INT 21h
MOV errinfo_ax,AX ; preservar información de
MOV errinfo_bx,BX ; errores críticos
MOV errinfo_cx,CX
push_crit_fin: RET
push_crit_err ENDP
pop_crit_err PROC
CMP dosver,300h
JB pop_crit_fin ; necesario DOS 3.0+
MOV AX,5D0Ah
MOV BX,0
LEA DX,errinfo
INT 21h ; restaurar información de
pop_crit_fin: RET ; errores críticos
pop_crit_err ENDP
tarea_TSR PROC
CALL sonidoUp
CALL init_nomfich
LEA DX,fich_nom
MOV CX,0
MOV AH,3Ch
INT 21h ; abrir fichero
JC tarea_err
MOV fich_handle,AX
CALL dscx_eq_video
MOV BX,CS:fich_handle
XOR DX,DX
MOV AH,40h
INT 21h ; grabar pantalla
JC tarea_err
PUSH CS
POP DS
MOV BX,fich_handle
MOV AH,3Eh
INT 21h ; cerrar fichero
JC tarea_err
CALL inc_nombre ; preparar futuro nombre
CALL sonidoDown
RET
tarea_err: PUSH CS
POP DS
RET
tarea_TSR ENDP
init_nomfich PROC
PUSH DS
MOV AX,40h
MOV DS,AX
MOV AX,DS:[4Ah] ; anchura de pantalla
POP DS
MOV AH,AL
SHR AH,1
SHR AH,1
SHR AH,1
SHR AH,1
AND AL,15
ADD AX,'00' ; binario -> hex
CMP AL,'9'
JBE al_es_hex
ADD AL,'A'-'9'-1
al_es_hex: CMP AH,'9'
JBE ah_es_hex
ADD AH,'A'-'9'-1
ah_es_hex: XCHG AH,AL
MOV WORD PTR fich_nom+3,AX ; anchura de pantalla
RET
init_nomfich ENDP
MOV CL,4
SHR AX,CL ; bytes -> párrafos
ADD BX,AX ; segmento de vídeo efectivo
MOV AX,25 ; 25 líneas
CMP CS:ega,ON
JNE modo_ok ; tarjeta modesta
XOR AH,AH
MOV AL,DS:[84h]
INC AL ; AX = líneas EGA/VGA
modo_ok: MUL WORD PTR DS:[4Ah] ; líneas*columnas = caracteres
SHL AX,1 ; AX = tamaño buffer de vídeo
MOV CX,AX
video_ok: MOV DS,BX
RET
dscx_eq_video ENDP
inc_nombre PROC
LEA BX,fich_nom
MOV AX,[BX+6]
INC AH
CMP AH,'9'
JBE inc_ok
MOV AH,'0'
INC AL
CMP AL,'9'
JBE inc_ok
MOV AL,'9'
inc_ok: MOV [BX+6],AX
RET
inc_nombre ENDP
sonidoUp PROC
CALL espera55ms
CALL sonidoON
MOV AX,2400
MOV CX,18
sonar_arriba: CALL sonidoAX
CALL espera55ms
SUB AX,30
LOOP sonar_arriba
CALL sonidoOFF
RET
sonidoUp ENDP
sonidoDown PROC
CALL espera55ms
CALL sonidoON
MOV AX,3000
MOV CX,18
sonar_abajo: CALL sonidoAX
CALL espera55ms
ADD AX,30
LOOP sonar_abajo
CALL sonidoOFF
RET
sonidoDown ENDP
espera55ms PROC
XPUSH <AX, DS>
MOV AX,40h
MOV DS,AX
STI ; por si acaso
MOV AL,DS:[6Ch]
espera_tic: CMP AL,DS:[6Ch]
JE espera_tic
XPOP <DS, AX>
RET
espera55ms ENDP
sonidoON PROC
PUSH AX
IN AL,61h
OR AL,3
JMP SHORT $+2
JMP SHORT $+2
OUT 61h,AL ; activar sonido
MOV AL,182
JMP SHORT $+2
JMP SHORT $+2
OUT 43h,AL ; preparar canal 2
POP AX
RET
sonidoON ENDP
sonidoOFF PROC
PUSH AX
IN AL,61h
AND AL,255-3
JMP SHORT $+2
JMP SHORT $+2
OUT 61h,AL ; desactivar sonido
POP AX
RET
sonidoOFF ENDP
sonidoAX PROC
PUSH AX
OUT 42h,AL
MOV AL,AH
JMP SHORT $+2
JMP SHORT $+2
OUT 42h,AL ; canal 2 del 8253 programado
POP AX
RET
sonidoAX ENDP
fin_residente EQU $
; *****************************
; * *
; * I N S T A L A C I O N *
; * *
; *****************************
main PROC
LEA DX,scrcap_txt ; mensaje inicial
CALL print
CALL inic_general ; inicializar ciertas variables
CALL detectarEGA
CALL obtener_param ; analizar posibles parámetros
JNC params_ok ; son correctos
CALL info_err_param ; no: informar del error/ayuda
JMP fin_noresid
params_ok: CALL residente? ; ¿programa ya residente?
JC no_residente ; aún no
CMP param_u,1 ; ¿se solicita desinstalarlo?
JE desinst ; así es
CALL adaptar_param ; parámetros en copia residente
LEA DX,ya_install_txt
CALL print
CALL info_ya_ins ; informar de teclas activación
JMP fin_noresid
desinst: MOV ES,tsr_seg
MOV AH,ES:multiplex_id
CALL mx_unload ; desinstalarlo:
LEA DX,des_ok_txt
JNC no_pesame ; ha sido posible
LEA DX,des_no_ok_txt ; no es posible
no_pesame: CALL print
JMP fin_noresid
no_residente: CMP AX,0 ; ¿reside una versión distinta?
JE instalable ; no: se admite instalación
CALL error_version ; error de versión incompatible
JMP fin_noresid
instalable: CMP param_u,1 ; no residente: ¿desinstalar?
JNE instalar ; no lo piden
LEA DX,imp_desins_txt ; lo piden, ¡serán despistados!
CALL print
JMP fin_noresid
instalar: MOV AX,parrafos_resid ; área residente
ADD AX,16 ; 256 bytes de PSP (completo)
MOV memoria,AX
CALL mx_get_handle ; obtener entrada Multiplex
JNC handle_ok
LEA DX,nocabe_txt ; no quedan entradas
CALL print
JMP fin_noresid
handle_ok: MOV multiplex_id,AH ; entrada multiplex para SCRCAP
LEA DX,instalado_txt ; mensaje de instalación
CALL print
CALL info_ya_ins ; informar teclas activación
CALL preservar_ints ; tomar nota de vectores
CMP param_ml,0 ; ¿se indicó parámetro /ML?
JNE instalar_ml ; en efecto
MOV AX,memoria ; párrafos de memoria precisos
; *************************************
; * *
; * SUBRUTINAS PARA LA INSTALACION *
; * *
; *************************************
obtener_param PROC
MOV BX,81h ; apuntar a zona de parámetros
otro_pmt_mas: CALL saltar_esp ; saltar delimitadores
JNC otro_pmt ; quedan más parámetros
JMP fin_proc_pmt ; no más parámetros
otro_pmt: CMP AL,'/'
JE pmt_barrado ; parámetro precedido por '/'
CMP AL,'?'
JE pmt_hlp
JMP mal_proc_pmt
pmt_barrado: INC BX
MOV AL,[BX] ; letra del parámetro
CMP AL,13 ; ¿fin de mandatos?
JE mal_proc_pmt ; falta parámetro
CMP AL,'?'
JE pmt_hlp
OR AL,' ' ; poner en minúsculas
CMP AL,'h'
JE pmt_hlp
CMP AL,'s'
JE pmt_S ; parámetro /S=
CMP AL,'t'
JE pmt_T ; parámetro /T=
CMP AL,'u'
JE pmt_U
MOV SI,[BX] ; ¿parámetro de dos caracteres?
OR SI," " ; mayusculizar
CMP SI,"lm" ; ¿parámetro /ML?
JE pmt_ML
mal_proc_pmt: STC ; error en parámetro(s)
RET
fin_proc_pmt: CLC ; parámetros procesados ok.
RET
pmt_hlp: MOV param_ayuda,1
JMP mal_proc_pmt ; «error» de ayuda
pmt_S: MOV param_s,1
CALL get_num
JC mal_proc_pmt
MOV marcas,AL
CMP AX,15
JA fuera_rango
AND AL,AL
JZ fuera_rango
JMP otro_pmt_mas
fuera_rango: MOV marcas,255
JMP mal_proc_pmt
pmt_T: MOV param_t,1
CALL get_num
MOV cod_rastreo,AL
JMP otro_pmt_mas
pmt_U: MOV param_u,1
INC BX
JMP otro_pmt_mas
pmt_ML: MOV param_ml,1 ; en efecto
ADD BX,2
JMP otro_pmt_mas
obtener_param ENDP
get_num: INC BX
MOV AL,[BX]
INC BX
CMP AL,'='
JE delimit_ok
CMP AL,':'
JE delimit_ok
err_sintax: STC ; sintaxis incorrecta
RET
delimit_ok: MOV AL,[BX]
CALL obtener_num
JC err_sintax
INC BX
RET
obtener_num PROC
CMP AL,0Dh ; fin zona parámetros y número
JE fin_num
CMP AL,32 ; fin número
JE fin_num
CMP AL,9 ; fin número
JE fin_num
CMP AL,'/' ; fin número (otro parámetro)
JE fin_num
CMP AL,':' ; fin número (otro dato)
JE fin_num
INC BX
MOV AL,[BX]
JMP obtener_num
fin_num: MOV SI,BX
DEC SI
XOR DX,DX
MOV AX,1 ; AX = 10 elevado a la 0 = 1
otro_car: DEC BX ; próximo carácter a procesar
MOV CL,[BX]
CMP CL,'='
JE ok_num ; delimitador: fin de número
CMP CL,':'
JE ok_num ; delimitador: fin de número
CMP CL,'.'
JNE no_millar ; saltar los puntos de millar
CMP AX,1000
JE otro_car
JMP mal_num ; separador millar descolocado
no_millar: CMP CL,'0'
JB mal_num
CMP CL,'9'
JA mal_num
SUB CL,'0' ; pasar ASCII a binario
MOV CH,0 ; CX = 0 .. 9
PUSH AX ; AX = 10 elevado a la N
AND AX,AX
JNZ multiplica
AND CL,CL
JNZ mal_num_pop ; a la izda sólo permitir ceros
multiplica: PUSH DX ; tras completar 5º dígito
MUL CX
POP DX
JC mal_num_pop
ADD DX,AX ; DX = DX + digito (CX) * 10 ^ N (AX)
JC mal_num_pop
POP AX
CMP AX,10000
JNE potencia ; AX*10 no se desbordará
MOV AX,0 ; como próximo dígito<>0 a
JMP otro_car ; la izda ... pobre usuario
potencia: MOV DI,10
PUSH DX ; no manchar DX al multiplicar
MUL DI ; AX = AX elevado a la (N+1)
POP DX
JMP otro_car
mal_num_pop: POP AX ; reequilibrar pila
info_err_param PROC
CMP param_ayuda,1
JNE otro_error
LEA DX,ayuda_txt
CALL print
RET
otro_error: LEA DX,err_sintax_txt
CMP marcas,255
JNE err_ok
LEA DX,err_tec_txt
err_ok: CALL print
LEA DX,err_sintax_fin
CALL print
RET
info_err_param ENDP
error_version PROC
PUSH ES
LEA DX,mal_ver_txt1
CALL print
LES DI,tsr_dir
MOV AL,':'
MOV CL,255
CLD
REPNE SCASB
REPNE SCASB
MOV DL,ES:[DI] ; número de versión
MOV AH,2
INT 21h
MOV DL,'.'
MOV AH,2
INT 21h
MOV DL,ES:[DI+2] ; revisión
MOV AH,2
INT 21h
LEA DX,mal_ver_txt2
CALL print
POP ES
RET
error_version ENDP
inic_XMS PROC
MOV AX,4300h
INT 2Fh ; chequear presencia XMS
CMP AL,80h
JNE XMS_ausente ; no instalado
PUSH ES
MOV AX,4310h
INT 2Fh ; sí: obtener su dirección
MOV XMS_off,BX ; y preservarla
MOV XMS_seg,ES
MOV xms_ins,1
POP ES
RET
XMS_ausente: MOV xms_ins,0
RET
inic_XMS ENDP
residente? PROC
PUSH CX
PUSH SI
PUSH DI
PUSH ES
PUSH AX
LEA DI,autor_nom_ver ; identificación del programa
MOV SI,DI
MOV AL,0
MOV CL,255
CLD
REPNE SCASB
SUB DI,SI
MOV CX,DI ; tamaño autor+programa+versión
MOV AX,1492h
MOV ES,AX
MOV DI,1992h ; ES:DI protocolo de búsqueda
CALL mx_find_tsr ; buscar si está en memoria
MOV tsr_off,DI ; anotar la dirección programa
MOV tsr_seg,ES ; por si estaba instalado
POP AX
JNC resid_ok ; CF=0 -> programa ya residente
POP ES
PUSH ES
LEA DI,autor_nom_ver
MOV SI,DI
MOV AL,':'
MOV CL,255
REPNE SCASB
REPNE SCASB
SUB DI,SI
MOV CX,DI ; tamaño autor+programa
MOV AX,1492h
MOV ES,AX
MOV DI,1992h ; ES:DI protocolo de búsqueda
CALL mx_find_tsr ; buscar si está en memoria
MOV tsr_off,DI ; anotar dirección del programa
MOV tsr_seg,ES ; por si instalada otra versión
MOV AX,0
JC resid_ok ; CF=1, AX=0 -> no residente
MOV AX,1
STC ; CF=1, AX=1 -> sí: otra vers.
resid_ok: POP ES
POP DI
POP SI
POP CX
RET
residente? ENDP
inic_general PROC
XPUSH <ES, DS> ; **
MOV AH,30h
INT 21h
XCHG AH,AL
MOV dosver,AX ; versión del DOS
CALL inic_XMS ; detectar controlador XMS
MOV AH,34h
INT 21h
MOV indos_off,BX
MOV indos_seg,ES ; dirección de InDOS
INC BX
CMP dosver,300h
JB crit_ok ; Critical Error detrás en 2.x
SUB BX,2
CMP dosver,300h
JE crit_ok ; Critical Error antes en 3.0
MOV AX,5D06h
INT 21h
XPUSH <DS, SI>
XPOP <BX, ES>
crit_ok: POP DS ; *
MOV crit_err_off,BX
MOV crit_err_seg,ES ; dirección de ese flag
POP ES ; *
RET
inic_general ENDP
detectarEGA PROC
MOV BL,10h
MOV AH,12h
INT 10h ; pedir información EGA al BIOS
CMP BL,10h
MOV AL,OFF
JE ega_ini ; no es EGA
MOV AL,ON
ega_ini: MOV ega,AL
RET
detectarEGA ENDP
info_ya_ins PROC
PUSH DS
CALL residente?
JC tec_no_res
MOV DS,tsr_seg
tec_no_res: MOV AL,marcas
MOV AH,cod_rastreo
POP DS
LEA DX,act_teclas_txt
CALL print
TEST AL,4
JZ alt?
LEA DX,act_ctrl
CALL print_
alt?: TEST AL,8
JZ shift_izq?
LEA DX,act_alt
CALL print_
shift_izq?: TEST AL,2
JZ shift_der?
LEA DX,act_shift_izq
CALL print_
shift_der?: TEST AL,1
JZ fin
LEA DX,act_shift_der
CALL print_
fin: CMP cod_rastreo,0
JE no_mas_teclas
LEA DX,act_c_txt
CMP AH,54h
JE act_ok
LEA DX,act_otra_txt
act_ok: CALL print_
no_mas_teclas: LEA DX,act_fin_txt
CALL print
RET
print_: CALL print
PUSH AX
MOV DL,'-'
MOV AH,2
INT 21h
POP AX
RET
info_ya_ins ENDP
adaptar_param PROC
PUSH ES
MOV ES,tsr_seg
CMP param_s,1
JNE s_ok
MOV AL,marcas
MOV ES:marcas,AL
s_ok: CMP param_t,1
JNE c_ok
MOV AL,cod_rastreo
MOV ES:cod_rastreo,AL
c_ok: POP ES
RET
adaptar_param ENDP
inicializa_id PROC
PUSHF
MOV segmento_real,ES ; anotar segmento del bloque
MOV offset_real,DI ; ídem con el offset
MOV AX,memoria
MOV longitud_total,AX
MOV AL,1
POPF ; CF=0: usar memoria UMB XMS
JNC info_ok
DEC AL ; usar memoria convencional
info_ok: OR info_extra,AL
RET
inicializa_id ENDP
preservar_INTs PROC
PUSH ES
PUSH DI
LEA DI,tabla_vectores
MOV CL,[DI-1]
MOV CH,0 ; CX vectores interceptados
otro_vector: PUSH CX
PUSH DI
MOV AH,35h
MOV AL,[DI]
INT 21h ; obtener vector de INT xx
POP DI
POP CX
MOV [DI+1],BX ; anotar donde apunta
MOV [DI+3],ES
ADD DI,5
LOOP otro_vector ; repetir con los restantes
POP DI
POP ES
RET
preservar_INTs ENDP
free_environ PROC
PUSH ES
MOV ES,DS:[2Ch] ; dirección del entorno
MOV AH,49h
INT 21h ; liberar espacio de entorno
POP ES
RET
free_environ ENDP
UMB_alloc PROC
PUSH BX
PUSH CX
PUSH DX
CMP xms_ins,1
JNE no_umb_disp ; no hay controlador XMS
MOV DX,AX ; número de párrafos
MOV AH,10h ; solicitar memoria superior
CALL gestor_XMS
CMP AX,1 ; ¿ha ido todo bien?
MOV AX,BX ; segmento UMB/código de error
JNE XMS_fallo ; fallo
POP DX ; ok
POP CX
POP BX
CLC
RET
no_umb_disp: MOV AX,0
XMS_fallo: POP DX
POP CX
POP BX
STC
RET
UMB_alloc ENDP
UPPER_alloc PROC
PUSH AX
MOV AH,30h
INT 21h
CMP AL,5
POP AX
JAE UPPER_existe
STC
JMP UPPER_fin ; necesario DOS 5.0 mínimo
UPPER_existe: PUSH AX ; preservar párrafos...
MOV AX,5800h
INT 21h
MOV alloc_strat,AX ; preservar estrategia
MOV AX,5802h
INT 21h
MOV umb_state,AL ; preservar estado UMB
MOV AX,5803h
MOV BX,1
INT 21h ; conectar cadena UMB's
MOV AX,5801h
MOV BX,41h
INT 21h ; High Memory best fit
POP BX ; ...párrafos requeridos
MOV AH,48h
INT 21h ; asignar memoria
PUSHF
PUSH AX ; guardado el resultado
MOV AX,5801h
MOV BX,alloc_strat
INT 21h ; restaurar estrategia
MOV AX,5803h
MOV BL,umb_state
XOR BH,BH
INT 21h ; restaurar estado cadena UMB
POP AX
POPF
JC UPPER_fin ; hubo fallo
PUSH DS
DEC AX
MOV DS,AX
INC AX
MOV WORD PTR DS:[1],AX ; manipular PID
MOV WORD PTR DS:[16],20CDh ; simular PSP
PUSH ES
MOV CX,DS
MOV ES,CX
MOV CX,CS
DEC CX
MOV DS,CX
MOV CX,8
MOV SI,CX
MOV DI,CX
CLD
REP MOVSB ; copiar nombre de programa
POP ES
POP DS
CLC
UPPER_fin: RET
UPPER_alloc ENDP
reubicar_prog PROC
PUSH DI
LEA SI,ini_residente
MOV CX,bytes_resid
CLD
REP MOVSB
XOR SI,SI
XOR DI,DI
MOV CX,256
REP MOVSB
POP DI
MOV ES:[36h],ES ; nuevo segmento de la JFT
RET
reubicar_prog ENDP
activar_INTs PROC
PUSH CX
PUSH DS ; preservar DS para el retorno
MOV AX,100h
SUB AX,DI ; AX = 100h-DI
MOV CL,4
SHR AX,CL ; AX = (100h-DI)/16
MOV CX,ES
SUB CX,AX
MOV DS,CX
LEA SI,offsets_ints
MOV CX,CS:[SI] ; CX vectores a desviar
ADD SI,2
desvia_otro: MOV AL,CS:[SI] ; número del vector en curso
MOV DX,CS:[SI+1] ; obtener offset
MOV AH,25h
INT 21h ; desviar INT xx a DS:DX
ADD SI,3
LOOP desvia_otro
POP DS
POP CX
RET
activar_INTs ENDP
mx_get_handle PROC
MOV AH,0C0h
mx_busca_hndl: PUSH AX
MOV AL,0
INT 2Fh
CMP AL,0FFh
POP AX
JNE mx_si_hueco
INC AH
JNZ mx_busca_hndl
mx_no_hueco: STC
RET
mx_si_hueco: CLC
RET
mx_get_handle ENDP
mx_find_tsr PROC
MOV AH,0C0h
mx_rep_find: PUSH AX
PUSH CX
PUSH SI
PUSH DS
PUSH ES
PUSH DI
MOV AL,0
PUSH CX
INT 2Fh
POP CX
CMP AL,0FFh
JNE mx_skip_hndl ; no hay TSR ahí
CLD
PUSH DI
REP CMPSB ; comparar identificación
POP DI
JE mx_tsr_found ; programa buscado hallado
mx_skip_hndl: POP DI
POP ES
POP DS
POP SI
POP CX
POP AX
INC AH
JNZ mx_rep_find
STC
RET
mx_tsr_found: ADD SP,4 ; «sacar» ES y DI de la pila
POP DS
POP SI
POP CX
POP AX
CLC
RET
mx_find_tsr ENDP
mx_unload PROC
PUSH ES
CALL mx_ul_tsrcv?
JNC mx_ul_able
POP ES
RET
mx_ul_able: XOR AL,AL
XCHG AH,AL
MOV BP,AX ; BP=entrada Multiplex del TSR
MOV CX,2
mx_ul_pasada: PUSH CX ; siguiente pasada
LEA SI,tabla_vectores
MOV CL,ES:[SI-1]
MOV CH,0 ; CX = nº vectores
mx_ul_masvect: POP AX
PUSH AX ; pasada en curso
DEC AL
PUSH CX
mx_ul_2f: MOV AL,ES:[SI] ; vector en curso
JNZ mx_ul_pasok
CMP CX,1 ; ¿último vector?
JNE mx_ul_noult
MOV AL,2Fh
LEA SI,tabla_vectores
mx_ul_busca2f: CMP ES:[SI],AL ; ¿INT 2Fh?
JE mx_ul_pasok
ADD SI,5
JMP mx_ul_busca2f
mx_ul_noult: CMP AL,2Fh ; ¿restaurar INT 2Fh?
JNE mx_ul_pasok
ADD SI,5
JMP mx_ul_2f
mx_ul_pasok: PUSH ES
PUSH AX
MOV AH,0
SHL AX,1
SHL AX,1
DEC AX
MOV CS:mx_ul_tsroff,AX
MOV CS:mx_ul_tsrseg,0 ; apuntar a tabla vectores
POP AX
PUSH AX
MOV AH,35h
INT 21h ; vector en ES:BX
POP AX
MOV CL,4
SHR BX,CL
MOV DX,ES
ADD DX,BX ; INT xx en DX (aprox.)
MOV AH,0C0h
mx_ul_masmx: CALL mx_ul_tsrcv?
JNC mx_ul_tsrcv
JMP mx_ul_otro
mx_ul_tsrcv: PUSH ES:[DI-16] ; ...TSR del convenio en ES:DI
PUSH ES:[DI-12]
MOV DI,ES:[DI-8] ; offset a la tabla de vectores
MOV CL,ES:[DI-1]
MOV CH,0 ; número de vectores en CX
mx_ul_buscav: CMP AL,ES:[DI]
JE mx_ul_usavect ; este TSR usa vector analizado
ADD DI,5
LOOP mx_ul_buscav
ADD SP,4 ; no lo usa
JMP mx_ul_otro
mx_ul_usavect: POP CX ; tamaño del TSR
POP BX ; segmento del TSR
CMP DX,BX
JB mx_ul_otro ; la INT xx no le apunta
ADD BX,CX
CMP DX,BX
JA mx_ul_otro ; la INT xx le apunta
PUSH AX
XOR AL,AL
XCHG AH,AL
CMP AX,BP ; ¿es el propio TSR?
POP AX
JNE mx_ul_chain ; no
POP ES ; sí: ¡posible reponer vector!
POP CX
POP BX
PUSH BX
PUSH CX
PUSH ES
DEC BX
JNZ mx_ul_norest ; no es la segunda pasada
POP ES ; segunda pasada...
PUSH ES
PUSH DS
MOV BX,CS:mx_ul_tsroff ; restaurar INT's
MOV DS,CS:mx_ul_tsrseg
CLI
MOV CX,ES:[SI+1]
MOV [BX+1],CX
MOV CX,ES:[SI+3]
MOV [BX+3],CX
STI
POP DS
mx_ul_norest: POP ES
POP CX
ADD SI,5 ; siguiente vector
DEC CX
JZ mx_unloadable ; no más, ¡desinstal-ar/ado!
JMP mx_ul_masvect
mx_ul_chain: MOV CS:mx_ul_tsroff,DI ; ES:DI almacena la dirección
MOV CS:mx_ul_tsrseg,ES ; de la variable vector
MOV DX,ES:[DI+1]
MOV CL,4
SHR DX,CL
MOV CX,ES:[DI+3]
ADD DX,CX ; INT xx en DX (aprox.)
MOV AH,0BFh
mx_ul_otro: INC AH ; a por otro TSR
JZ mx_ul_exitnok ; ¡se acabaron!
JMP mx_ul_masmx
mx_ul_exitnok: ADD SP,6 ; equilibrar pila
POP ES
STC
RET ; imposible desinstalar
mx_unloadable: POP CX
DEC CX
JZ mx_ul_exitok ; desinstalado
JMP mx_ul_pasada ; 1ª pasada exitosa: por la 2ª
mx_ul_exitok: TEST ES:info_extra,111b ; ¿tipo de instalación?
MOV ES,ES:segmento_real ; segmento real del bloque
JZ mx_ul_freeml ; cargado en RAM convencional
CMP xms_ins,1
JNE mx_ul_freeml ; no hay controlador XMS (¿?)
MOV DX,ES
MOV AH,11h
CALL gestor_XMS ; liberar memoria superior
POP ES
CLC
RET
mx_ul_freeml: MOV AH,49h
INT 21h ; liberar bloque de memoria ES:
POP ES
CLC
RET
mx_ul_tsrcv?: PUSH AX ; ¿es TSR del convenio?...
PUSH ES
PUSH DI
MOV DI,1492h
MOV ES,DI
MOV DI,1992h
INT 2Fh
CMP AX,0FFFFh
JNE mx_ul_ncvexit
CMP WORD PTR ES:[DI-4],"#*"
JNE mx_ul_ncvexit
CMP WORD PTR ES:[DI-2],"*#"
JNE mx_ul_ncvexit
ADD SP,4 ; CF=0
POP AX
RET
mx_ul_ncvexit: POP DI ; ...no es TSR del convenio
POP ES
POP AX
STC ; CF=1
RET
mx_ul_tsroff DW 0
mx_ul_tsrseg DW 0
mx_unload ENDP
print PROC
XPUSH <AX, BX, CX, DX>
MOV BX,DX
print_mas: MOV AL,[BX]
AND AL,AL
JZ fin_print
MOV DL,AL
MOV AH,2
PUSH BX
INT 21h
POP BX
INC BX
JMP print_mas
fin_print: XPOP <DX, CX, BX, AX>
RET
print ENDP
; **********************************
; * *
; * DATOS PARA LA INSTALACION *
; * *
; **********************************
; ------------ Texto
act_c_txt DB "SysReq",0
act_otra_txt DB 8," y la tecla elegida",0
act_fin_txt DB 8," para activarlo.",13,10,0
mal_ver_txt1 DB 13,10
DB " - Error: ya está instalada la versión ",0
mal_ver_txt2 DB " de este programa.",13,10,7,0
fin_prog EQU $
scrcap ENDS
END inicio
Listado SCRVER
/********************************************************************/
/* */
/* SCRVER 1.0 - Utilidad para visualizar pantallas 80x25 y 40x25 */
/* capturadas por SCRCAP. Borland C en modo "Large". */
/* */
/********************************************************************/
#include <dos.h>
#include <dir.h>
#include <fcntl.h>
#include <conio.h>
#include <string.h>
if (argc<2) {
printf("\nIndique el(los) fichero(s) a visualizar.\n");
exit (1); }
while (!ultimo) {
fnmerge (ruta, disco, direct, fichero.ff_name, "");
if (fichero.ff_name[3]=='2') {
_AX=1; __emit__(0xcd, 0x10); } /* modo de 40x25 */
else {
_AX=3; __emit__(0xcd, 0x10); } /* modo 80x25 */
if ((handle=open(ruta, O_RDONLY | O_BINARY, 0)) == -1) {
printf("Error al abrir fichero de entrada.\n"); exit(1); }
read(handle, buffer, 30000); close(handle);
ultimo=(getch()==27) || findnext (&fichero);
Listado SCRVER
/********************************************************************/
/* */
/* SCRVER 1.0 - Utilidad para visualizar pantallas 80x25 y 40x25 */
/* capturadas por SCRCAP. Borland C en modo "Large". */
/* */
/********************************************************************/
#include <dos.h>
#include <dir.h>
#include <fcntl.h>
#include <conio.h>
#include <string.h>
if (argc<2) {
printf("\nIndique el(los) fichero(s) a visualizar.\n");
exit (1); }
while (!ultimo) {
fnmerge (ruta, disco, direct, fichero.ff_name, "");
if (fichero.ff_name[3]=='2') {
_AX=1; __emit__(0xcd, 0x10); } /* modo de 40x25 */
else {
_AX=3; __emit__(0xcd, 0x10); } /* modo 80x25 */
if ((handle=open(ruta, O_RDONLY | O_BINARY, 0)) == -1) {
printf("Error al abrir fichero de entrada.\n"); exit(1); }
read(handle, buffer, 30000); close(handle);
ultimo=(getch()==27) || findnext (&fichero);
}
/********************************************************************/
/* */
/* SCR2TXT 1.0 - Utilidad para convertir pantallas capturadas por */
/* SCRCAP a modo texto. Borland C en modo "Large". */
/* */
/********************************************************************/
#include <dos.h>
#include <dir.h>
#include <fcntl.h>
#include <conio.h>
#include <string.h>
printf("\n");
if (argc<2) {
printf("Indique el(los) fichero(s) a convertir.\n"); exit (1); }
while (!ultimo) {
fnmerge (rutar, disco, direct, fichero.ff_name, "");
strcpy (rutaw, rutar); p=rutaw; while ((*p) && (*p!='.')) p++;
*(p-5)=*(p-4)=*(p-3)='0'; *(p+1)=*(p+3)='T'; *(p+2)='X'; *(p+4)=0;
Listado TDSK
cab_READ_WRITE STRUC
DB (TYPE cab_PETICION) DUP (?)
DB ? ; descriptor de medio
transfer_desp DW ? ; dirección de transferencia
transfer_segm DW ?
transfer_sect DW ? ; nº de sectores a transferir
transfer_sini DW ? ; primer sector a transferir
cab_READ_WRITE ENDS
_PRINCIPAL SEGMENT
ASSUME CS:_PRINCIPAL, DS:_PRINCIPAL
estrategia ENDP
procesa_io EQU $
procesa_ems PROC
JNC no_emslib
MOV DH,45h ; sistema reinicializando:
CALL llama_EMM ; liberar memoria EMS
RET
no_emslib: MOV SI,DX ; preservar DX
MOV DH,47h
CALL llama_EMM ; DH=47h -> salvar contexto EMS
MOV DX,SI ; recuperar DX
MOV BX,4000h ; tamaño de página (16 Kb)
DIV BX ; AX = 1ª página EMS a mapear
MOV SI,DX ; offset relativo en 1ª página
procesa_pag: PUSH CX ; **
MOV BX,DI
MOV CL,4
SHR BX,CL ; bytes del offset -> párrafos
MOV CX,ES
ADD BX,CX ; AX = segmento de datos
MOV CX,CS:ems_pagina0
MOV DS,CX
XOR DL,DL ; intentar emplear página 0
SUB BX,CX
JNC rpos
NEG BX ; valor absoluto
rpos: CMP BX,401h ; distancia respecto página EMS
JAE no_conflicto ; más de 16 Kb: no solapamiento
CALL copia_contexto ; está CX apilado
MOV DS,CS:ems_paginai
MOV DL,CS:ems_pagni ; usar página alternativa
OR BP,8000h ; indicar su uso
no_conflicto: POP CX ; * pila totalmente equilibrada
MOV BX,AX
MOV DH,44h ; DL = 0 ó 2 (página física)
CALL llama_EMM ; DH = 44h -> mapear página EMS
XPUSH <CX,SI> ; ++
SUB SI,4000h
NEG SI ; SI = 4000h - SI: «resto»
SHR SI,1 ; bytes -> palabras
CMP CX,SI
JB cx_ok ; no ocupada toda la página
MOV CX,SI
cx_ok: POP SI ; + SI=desplazamiento relativo
CLD
POP BX ; + palabras restantes
SUB BX,CX ; descontar las que se moverán
PUSH BX ; * volver a apilar el viejo CX
CALL coloca_regs
CMP CS:cpu386,ON ; ¿386 o superior?
JNE trans_16bit
.386
PUSHAD
SHR CX,1 ; nº palabras de 32 bit a mover
JCXZ transferido ; evitar desgracia
XOR EAX,EAX ; asegurar no violación
DEC AX ; de segmento-64K
AND ECX,EAX ; EAX = 0FFFFh
AND ESI,EAX
AND EDI,EAX
REP MOVSD ; transferencia ultrarrápida
transferido: POPAD ; POPAD falla en muchos 386
.8086
NOP ; arreglar fallo de POPAD
ADD CX,CX
ADD DI,CX ; simular cambio normal de DI
ADD SI,CX ; y de SI
JMP fin_trans
trans_16bit: REP MOVSW ; mover palabras de 16 bit
fin_trans: CALL coloca_regs
AND BP,BP ; ¿se usó página alternativa?
JNS ahorra_ms
CALL copia_contexto ; está CX apilado
AND BP,1 ; de momento, no se usará más
ahorra_ms: POP CX ; **
JCXZ fin_leer ; no quedan más palabras
INC AX ; próxima página EMS
XOR SI,SI ; ahora desde inicio página EMS
JMP procesa_pag
fin_leer: MOV DH,48h
CALL llama_EMM ; DH=47h restaurar contexto EMS
MOV AX,100h ; no hubo problemas
RET
procesa_ems ENDP
llama_EMM PROC
XPUSH <AX,BX,CX,BP>
MOV AX,DX ; función en AX
llama_denuevo: MOV DX,CS:mem_handle ; handle EMS
XPUSH <AX,BX>
INT 67h ; llamar al EMM
MOV CL,AH
XPOP <BX,AX>
AND CL,CL
JZ llama_ok ; además, ZF = 1
CMP CL,82h
JE llama_denuevo ; intentarlo hasta que funcione
llama_ok: XPOP <BP,CX,BX,AX>
JNE ret_atras
RET
ret_atras: POP AX ; sacar dirección de retorno
MOV AX,810Ch ; error de «anomalía general»
RET ; retornar dos niveles atrás
llama_EMM ENDP
copia_contexto PROC
XPOP <BX,CX> ; equilibrar pila a llama_EMM
MOV DH,48h
CALL llama_EMM ; restaurar contexto EMS
MOV DH,47h
CALL llama_EMM ; preservarlo de nuevo
PUSH CX
JMP BX ; más rápido que PUSH BX/RET
copia_contexto ENDP
init PROC
MOV CS:modo,CONFIG ; ejecutando desde CONFIG
CALL obtDosVer ; obtener versión del DOS
LEA AX,retorno_ok
MOV CS:p_rutinas,AX ; anular rutina INIT
INC CS:tipo_soporte ; 0: disco no formateado
MOV CS:cs_tdsk,CS ; inicializar esa variable
CMP CS:dosver,300h ; ¿DOS inferior al 3.0?
JAE dos_ok ; DOS 3.0+
AND CS:tipo_drive,0F7FFh ; ajustar atributos
MOV CS:num_ordenes,0Dh ; y número de órdenes
dos_ok: MOV SI,[BX].bpb_cmd_desp
MOV ES,[BX].bpb_cmd_segm ; ES:SI -> parámetros
MOV [BX].num_discos,1 ; una unidad de disco
LEA AX,bpb_ptr
MOV [BX].bpb_cmd_desp,AX
MOV [BX].bpb_cmd_segm,CS ; inicializado puntero BPB
CALL desvia_int19 ; controlar INT 19h
obtDosVer PROC
XPUSH <AX,BX,CX,DX>
MOV AH,30h
INT 21h
XCHG AH,AL
MOV CS:dosver,AX
XPOP <DX,CX,BX,AX>
RET
obtDosVer ENDP
gestionar_ram PROC
MOV CS:segm_psp,DS ; indicar segmento del PSP
MOV AX,DS:[2] ; segmento más alto
MOV CS:top_ram,AX ; indicar tope de memoria
PUSH ES
MOV ES,DS:[2Ch] ; segmento del entorno
MOV AH,49h
INT 21h ; liberar área de entorno
POP ES ; ES: -> PSP
MOV BX,6
MOV AH,4Ah ; hacer creer al DOS que
INT 21h ; TDSK ocupa sólo 96 bytes
RET
gestionar_ram ENDP
procesar_param PROC
CALL busca_param ; saltar delimitadores
JC fin_param ; no hay más parámetros
CALL param_barra ; gestionar parámetro tipo "/A"
JC procesar_param ; era parámetro tipo "/A"
MOV param_tdisco,AX ; es numérico: tamaño del disco
MOV param_tdiscof,ON ; parámetro de tamaño indicado
p_param2: CALL busca_param
JC fin_param
CALL param_barra
JC p_param2
MOV param_tsect,AX ; tamaño de sector
p_param3: CALL busca_param
JC fin_param
CALL param_barra
JC p_param3
MOV param_tdir,AX ; entradas al directorio
p_param4: CALL busca_param
JC fin_param
CALL param_barra
JC p_param4
MOV param_tcluster,AX ; tamaño de cluster
p_param5: CALL busca_param
JC fin_param
CALL param_barra ; últimas opciones posibles
JC p_param5
fin_param: CALL validacion ; validación de parámetros
RET
procesar_param ENDP
param_barra PROC
CMP AX,"e/" ; ¿indicado /E?
JNE p_exp1?
MOV param_e,ON
JMP p_barra_exit
p_exp1?: CMP AX,"a/" ; ¿indicado /A?
JNE p_exp2?
p_exp: MOV param_a,ON
JMP p_barra_exit
p_exp2?: CMP AX,"x/" ; /A y /X son equivalentes
JE p_exp
CMP AX,"c/" ; ¿indicado /C?
JNE p_ayuda?
MOV param_c,ON
JMP p_barra_exit
p_ayuda?: CMP AX,"h/" ; ¿indicado /H?
JNE p_exit?
p_ayuda: MOV param_h,ON
JMP p_barra_exit
p_exit?: CMP AX,"?/" ; /H y /? son equivalentes
JE p_ayuda
CMP AX,"m/" ; ¿indicado /M?
JNE param_id?
MOV param_m,ON
JMP p_barra_exit
param_id?: CMP AX,"i/" ; ¿indicado /I= o /I:?
JNE param_fats?
ADD BX,3
CMP BYTE PTR ES:[BX-1],'='
JE p_id_ok
CMP BYTE PTR ES:[BX-1],':'
JNE param_b_mal
p_id_ok: CALL obt_num ; leer código telefónico
MOV param_i,ON
MOV codigo_tfno,AX
SUB BX,2
JMP p_barra_exit
param_fats?: CMP AX,"f/" ; ¿indicado /F= o /F:?
JNE param_b?
ADD BX,3
CMP BYTE PTR ES:[BX-1],'='
JE p_f_ok
CMP BYTE PTR ES:[BX-1],':'
JNE param_b_mal
p_f_ok: CALL obt_num ; leer número de FATs
MOV param_f,AX
SUB BX,2
JMP p_barra_exit
param_b?: CMP AX,"b/" ; ¿indicado /B?
JNE param_unidad?
MOV param_b,ON
JMP p_barra_exit
param_unidad?: CMP AH,':' ; ¿parámetro de unidad?
JNE param_num?
AND AL,255-32 ; poner en mayúsculas
MOV param_unidad,AL
JMP p_barra_exit
param_num?: CMP AL,'/'
JNE param_num ; puede ser número
param_b_mal: OR lista_err,ERROR0
param_num: CALL obt_num ; es parámetro numérico: leerlo
CLC ; no es parámetro barrado
RET
p_barra_exit: ADD BX,2 ; saltar este parámetro
STC ; es parámetro barrado
RET
param_barra ENDP
validacion PROC
MOV AX,0FFFFh
CMP AX,param_tdisco ; ¿números correctos?
JE sintax_err
CMP AX,param_tsect
JE sintax_err
CMP AX,param_tdir
JE sintax_err
CMP AX,param_tcluster
JE sintax_err
CMP param_tdisco,0
JE valida_tsect ; no indicado tamaño (o 0)
CMP param_tdisco,8
JB sintax_err
CMP param_tdisco,65534
JA sintax_err
valida_tsect: MOV AX,param_tsect
CMP AX,0
JE valida_tclus ; no indicado tamaño de sector
CMP AX,32
JE valida_tclus
CMP AX,64
JE valida_tclus
CMP AX,128
JE valida_tclus
CMP AX,256
JE valida_tclus
CMP AX,512
JE valida_tclus
CMP AX,1024
JE valida_tclus
CMP AX,2048
JNE sintax_err
valida_tclus: CMP param_tcluster,256
JAE sintax_err ; debe estar entre 0..255
CMP param_f,1
JB pf_a1 ; /F=1 ó /F=2 exclusivamente
CMP param_f,2 ; si no, forzarlo y perdonar
JBE fin_validar
MOV param_f,2
JMP fin_validar
pf_a1: MOV param_f,1
JMP fin_validar
sintax_err: MOV param_tdiscof,OFF ; no definir disco ahora
XOR AX,AX
MOV param_tdisco,AX
MOV param_tsect,AX
MOV param_tdir,AX
MOV param_tcluster,AX
MUL SI ; AX = AX * 10
JC num_incorr
XOR CH,CH
SUB CL,'0'
ADD AX,CX ; AX = AX + dato
JC num_incorr
INC BX
JMP otro_digito
num_incorr: MOV AX,65535 ; indicar valor incorrecto
fin_num: XPOP <SI,DX,CX>
RET
obt_num ENDP
errores_Dos PROC
PUSH ES
CMP dosver,200h ; necesario DOS 2.x+
JAE existe_tdsk?
OR err_grave,ERROR0 ; error de DOS incorrecto
JMP fin_err_Dos
existe_tdsk?: CALL reside_tdsk? ; ¿instalado TURBODSK?
CMP segm_tdsk,0
JNE busca_unidad ; ya instalado
OR err_grave,ERROR1 ; error: TURBODSK no instalado
JMP fin_err_Dos
busca_unidad: MOV ES,segm_tdsk ; ES: -> disco virtual
CMP param_unidad,0
JE disco_defecto ; no se indicó letra de unidad
CALL obtener_segm ; segmento del TDSK indicado
JC fin_err_Dos ; fallo (no es unidad TDSK)
disco_defecto: CALL max_sector ; obtener mayor sector
MOV BX,param_tsect
CMP BX,AX
JBE fin_err_Dos ; tamaño de sector correcto
OR lista_err,ERROR3 ; el tamaño no definible ahora
MOV param_tsect,0 ; ignorar tamaño indicado
fin_err_Dos: CALL test32Mb
CALL testWin
POP ES
RET
errores_Dos ENDP
errores_config PROC
CMP param_unidad,0
JE no_unidad
OR lista_err,ERROR1
no_unidad: CMP param_c,ON
JNE fin_err_con
OR lista_err,ERROR1
fin_err_con: CALL test32Mb
RET
errores_config ENDP
set_errorlevel PROC
MOV AL,255
max_sector PROC
XPUSH <BX,ES>
MOV AH,52h
INT 21h ; Get List of Lists
ADD BX,10h
CMP CS:dosver,30Ah
JAE psect_ok
INC BX ; DOS anterior al 3.1
psect_ok: MOV AX,ES:[BX] ; mayor tamaño de sector
XPOP <ES,BX> ; definido por cualquier disp.
RET
max_sector ENDP
test32Mb PROC
CMP param_tdisco,32768
JBE fin32mb
CMP param_tsect,1024
JAE fin32mb
OR lista_err,ERROR15 ; sector de menos de 1024
MOV param_tdisco,32768 ; evitar fallo posterior
fin32mb: RET
test32Mb ENDP
testWin PROC
CMP param_tdiscof,ON
JNE fin_testWin ; no redefinido el disco
CMP dosver,300h
JB fin_testWin ; no buscar Windows en DOS 2.x
MOV AX,1600h
INT 2Fh
AND AL,AL ; ¿Windows en modo extendido?
JZ noWinEnh
CMP AL,80h ; ¿Windows en modo extendido?
JE noWinEnh
siWin: OR err_grave,ERROR3 ; estamos dentro de Windows
JMP fin_testWin
noWinEnh: MOV AX,4680h
INT 2Fh
AND AX,AX
JZ siWin ; Windows en modo real/estándar
fin_testWin: RET
testWin ENDP
reside_tdsk? PROC
XPUSH <AX, SI>
CALL lista_discos
LEA SI,area_trabajo-4
busca_final: ADD SI,4
CMP WORD PTR [SI],0
JNE busca_final ; ir al final de la tabla
busca_tdsk: SUB SI,4
CMP SI,OFFSET area_trabajo
JB fin_busca ; no reside (segm_tdsk = 0)
CMP BYTE PTR [SI+3],1
JNE busca_tdsk
MOV AX,[SI] ; encontrada unidad TURBODSK
MOV segm_tdsk,AX
PUSH DS
MOV DS,AX
MOV AL,letra_unidad ; con esta letra de unidad
POP DS
MOV letra_unidad,AL
fin_busca: XPOP <SI, AX>
RET
reside_tdsk? ENDP
obtener_segm PROC
CALL lista_discos
LEA SI,area_trabajo-4
busca_ultimo: ADD SI,4
CMP WORD PTR [SI],0
JNE busca_ultimo ; realmente, el primero
recorre_dsks: SUB SI,4
CMP SI,OFFSET area_trabajo
JB tdsk_no_hay
CMP BYTE PTR [SI+3],1
JNE recorre_dsks
PUSH DS
MOV DS,[SI]
MOV AL,letra_unidad ; unidad del TDSK residente
POP DS
CMP AL,param_unidad ; disco TDSK: ¿es el buscado?
JNE recorre_dsks
MOV letra_unidad,AL ; inicializar letra de unidad
MOV AX,[SI]
desvia_int19 PROC
XPUSH <BX,DS,ES>
MOV BX,CS
MOV DS,BX
CALL test_CPU
CMP cpu286,ON
JNE fin_desvia19 ; no es 286 ó superior
MOV AX,3519h
INT 21h ; ES:BX anterior INT 19h
MOV ant19off,BX
MOV ant19seg,ES
LEA DX,nueva_int19
MOV AX,2519h
INT 21h ; nueva rutina de control
fin_desvia19: XPOP <ES,DS,BX>
RET
desvia_int19 ENDP
inic_letra PROC
XPUSH <AX,BX,SI,DS>
MOV AL,[BX].nuevo_disco ; unidad en DOS 3.0+
ADD AL,'A'
PUSH CS
POP DS ; DS -> _PRINCIPAL
CMP dosver,300h
JAE letra_ok
CALL lista_discos ; hallar unidad en DOS 2.x
LEA SI,area_trabajo
XOR AL,AL ; cuenta de discos
cuenta_discos: ADD AL,[SI+2]
ADD SI,4
CMP WORD PTR [SI],0
JNE cuenta_discos
ADD AL,'A'
letra_ok: MOV letra_unidad,AL ; guardar letra de unidad
XPOP <DS,SI,BX,AX>
RET
inic_letra ENDP
lista_discos PROC
XPUSH <AX,BX,CX,DX,SI,DI,ES>
MOV AH,52h ; "Get list of lists"
INT 21h ; obtener puntero en ES:BX
MOV CX,17h ; supuesto DOS 2.x
CMP dosver,300h
JB pdisp_ok
MOV CX,28h ; supuesto DOS 3.0x
CMP dosver,30Ah
JB pdisp_ok
MOV CX,22h ; versiones del DOS superiores
pdisp_ok: ADD BX,CX
LEA DI,area_trabajo-4 ; tabla de dispositivos-4
disp_otro: ADD DI,4
disp_skip: LES BX,ES:[BX] ; siguiente dispositivo
CMP BX,-1
JE disp_fin
TEST BYTE PTR ES:[BX+5],80h
JNZ disp_skip ; es dispositivo de caracteres
MOV CL,ES:[BX+10] ; es de bloques
MOV [DI],ES ; anotar dirección
MOV [DI+2],CL
MOV BYTE PTR [DI+3],0 ; de momento, no es TDSK
PUSH DI
LEA SI,id_tdsk ; identificación de TURBODSK
MOV DI,SI
MOV CX,5
CLD
REP CMPSB ; ¿es TURBODSK?
POP DI
JNE disp_otro ; es de bloques, pero no TDSK
MOV AX,ES:cs_tdsk ; segmento real de TDSK
MOV [DI],AX ; corregir dirección en tabla
INC BYTE PTR [DI+3] ; indicar dispositivo TDSK
JMP disp_otro ; buscar hasta completar tabla
disp_fin: MOV WORD PTR [DI],0 ; final de la lista
XPOP <ES,DI,SI,DX,CX,BX,AX>
RET
lista_discos ENDP
desinstala PROC
MOV DX,ES:mem_handle
MOV AL,ES:tipo_soporte
DEC AL
JZ libera_ext ; liberar memoria extendida
DEC AL
JZ libera_exp ; liberar memoria expandida
PUSH ES
MOV ES,DX
mem_info PROC
MOV tdisco,0 ; ley de Murphy
CALL eval_xms ; inicializar «xms_kb»
CALL eval_ems ; inicializar «ems_kb»
CALL eval_con ; inicializar «con_kb»
MOV AX,param_tdisco ; cantidad de memoria necesaria
CMP param_a,ON
JNE no_ems ; no solicitan memoria EMS
eval_xms PROC
PUSH ES
MOV AX,352Fh
INT 21h ; dirección de INT 2Fh en ES:BX
MOV AX,ES
AND AX,AX
JZ xms_ok ; apunta a 0000:XXXX (DOS 2.x)
MOV AX,4300h
INT 2Fh
CMP AL,80h ; ¿hay controlador XMS?
JNE xms_ok
MOV AX,4310h ; obtener su dirección
INT 2Fh
MOV xms_segm,ES
MOV xms_desp,BX
MOV AH,8
CALL xms_driver ; preguntar memoria libre
AND AX,AX
JNZ xms_kb_ok ; no hubo fallo
CMP BL,0A0h
JE xms_kb_ok ; asignada ya toda la memoria
TEST BL,80h
JZ xms_kb_ok ; no hay memoria XMS disponible
OR lista_err,ERROR8 ; fallo real del controlador
xms_kb_ok: CMP AX,8 ; mayor bloque XMS disponible
JB xms_ok
MOV xms_kb,AX ; mínimo necesario: 8 Kb
xms_ok: POP ES
RET
eval_xms ENDP
eval_ems PROC
PUSH ES
MOV AX,3567h
INT 21h ; vector de INT 67h en ES:BX
MOV DI,10
LEA SI,emm_id
MOV CX,8
CLD
REP CMPSB ; ¿instalado controlador EMS?
JE ems_existe
JMP ems_ok
ems_existe: MOV CX,8000h ; nº de intentos prudente
emm_llama: MOV AH,40h
INT 67h
AND AH,AH
JZ emm_responde
CMP AH,82h
LOOPE emm_llama
emm_fatal: OR lista_err,ERROR9 ; fallo del EMM
JMP ems_ok
emm_responde: MOV AH,41h
INT 67h
AND AH,AH
JZ emm_pag_ok
CMP AH,82h
JE emm_responde ; reintentar (EMM ocupado)
JMP emm_fatal
emm_pag_ok: MOV ems_pagina0,BX ; inicializar página EMS
ADD BX,0C00h
MOV ems_paginai,BX
MOV ems_pagni,3 ; página alternativa: la 3
MOV AH,46h
INT 67h ; obtener versión del EMM
CMP AL,40h
JB emm_obt_kb ; versión anterior a la 4.0
MOV ems4,ON
emm_obt_pag: XPUSH <ES,DS>
POP ES
MOV AX,5800h ; obtener dirección de páginas
LEA DI,area_trabajo
INT 67h
POP ES
AND AH,AH
JZ emm_pags_ok
CMP AH,82h
JE emm_obt_pag
JMP emm_fatal
emm_pags_ok: XOR DX,DX
CALL emm_busca_pag ; buscar página 0
JC emm_fatal
MOV ems_pagina0,BX
ems_busca_i: INC DX ; buscar la siguiente
CMP DX,5 ; la 5ª y siguientes no valen
JE emm_fatal ; +------+
CALL emm_busca_pag ; | |
JC emm_fatal ; +> +>+------+<-- pág i
MOV ems_paginai,BX ;0C00h| 32 | | |
MOV ems_pagni,DL ; pá | Kb | +------+
SUB BX,ems_pagina0 ; rra | | | |
JNC bxpositivo ; fos | +>+------+
NEG BX ; | | |
bxpositivo: CMP BX,0C00h ; +> +------+<-- pág 0
JB ems_busca_i ; no distan 32 Kb: buscar otra
emm_obt_kb: MOV AH,42h
INT 67h
AND AH,AH
JZ emm_kb_ok
CMP AH,82h
JE emm_obt_kb
JMP emm_fatal
emm_kb_ok: MOV CL,4
SHL BX,CL ; páginas EMS disponibles
MOV ems_kb,BX ; Kb EMS disponibles (0,16,...)
ems_ok: POP ES
RET
eval_ems ENDP
eval_con PROC
CMP modo,AUTOEXEC ; ¿se ejecuta desde el DOS?
JNE conv_ok ; no, desde el config
MOV AH,48h
MOV BX,0FFFFh ; pedir 1 Mb al DOS (fallará)
INT 21h
MOV DX,BX ; tamaño del mayor bloque
MOV CL,6
SHR BX,CL ; BX = Kb del mayor bloque
SUB BX,128 ; restar 128 Kb
JC conv_ok ; no quedan ni 128 Kb
CMP BX,8
JB conv_ok ; no quedan siquiera 8 Kb
MOV con_kb,BX
MOV BX,DX ; tamaño del mayor bloque
MOV AH,48h
PUSH BX
INT 21h ; localizarlo (AX=segmento)
POP BX
XPUSH <ES,AX> ; preservar ES y segmento (AX)
ADD AX,BX ; añadir longitud
SUB AX,1024/16*64 ; restar 64 Kb
MOV segm_reubicar,AX ; segmento de autoreubicación
POP ES ; recuperar segmento del bloque
MOV AH,49h
INT 21h ; liberarlo
POP ES ; recuperar ES
conv_ok: RET
eval_con ENDP
mem_reserva PROC
MOV AL,tipo_soporte ; tipo de memoria empleada
DEC AL
JZ mem_r_xms ; 1: memoria extendida XMS
DEC AL
JZ mem_r_ems ; 2: memoria expandida EMS
MOV CL,6
ren_handle ENDP
test_CPU PROC
PUSHF
POP AX
OR AH,70h ; intentar activar bit 12, 13 ó 14
PUSH AX ; del registro de estado
POPF
PUSHF
POP AX
AND AH,0F0h
CMP AH,0F0h
JE fin_test_CPU ; es 8086 o similar
MOV cpu286,ON ; es 286 o superior
AND AH,70h ; 286 pone bits 12, 13 y 14 a cero
JZ fin_test_CPU ; es 286
MOV cpu386,ON ; 386 o superior
fin_test_CPU: RET
test_CPU ENDP
; ------------------ + 1 = --------------------- + 1
; tamcluster + 1,5 2 * tamcluster + 3
;
; Al resultado se le suma 1, ya que los clusters se
; numeran a partir de 2, para calcular el cluster de nº
; más alto del disco. Si ese número es 4086 o más habrá
; de utilizarse una FAT de 16 bits, recalculándose la
; fórmula anterior sustituyendo 1,5 por 2 y 3 por 4. Al
; final, una vez determinado el tipo de FAT habrá de
; calcularse con exactitud el número de cluster más alto,
; ya que hay casos críticos en que una FAT12 no sirve
; pero al aplicar una FAT16 el número de clusters baja de
; nuevo de 4085 (debido al mayor consumo de disco de la
; FAT16) resultado de ello la asignación de una FAT12,
; pese a que se reserva espacio para la de 16. Hay que
; considerar además el caso de que el disco tenga 2 FAT.
adaptar_param PROC
MOV AX,tdisco ; en Kb
MOV BX,AX ; entradas de directorio propuestas
MOV CL,1 ; sectores por cluster propuestos
CMP AX,128 ; ¿disco de 128 Kb o menos?
JBE prop_ok
MOV BX,128
CMP AX,512 ; ¿disco de 512 Kb o menos?
JBE prop_ok
MOV BX,256
CMP AX,2042 ; ¿disco de casi 2 Mb o menos?
JBE prop_ok
MOV CL,2 ; evitar FAT16
CMP AX,4084 ; ¿disco de casi 4 Mb o menos?
JBE prop_ok
MOV CL,4 ; evitar FAT16 hasta 8 Mb
MOV BX,384
CMP AX,16384 ; ¿disco de menos de 16 Mb?
JB prop_ok
MOV BX,512
prop_ok: CMP dosver,300h
JAE prop_valido
CMP AX,4084*2 ; en DOS 2.xx evitar FAT16
JB prop_valido
MOV CL,8
CMP AX,4084*4
JB prop_valido
MOV CL,16
CMP AX,4084*8
JB prop_valido
MOV CL,32
prop_valido: MOV tdir,BX
MOV tcluster,CL ; inicializar valores recomendados
MOV DX,1024 ; AX = tamaño del disco en Kb
MUL DX ; DX:AX = bytes totales del disco
MOV CX,param_tsect
AND CX,CX
JNZ tsect_def ; se ha definido tamaño de sector
tsect_rec: MOV CX,tsect ; tamaño por defecto
tsect_def: CALL divCX
JNC nsect_ok ; menos de 65536 sectores: correcto
OR lista_err,ERROR11
JMP tsect_rec ; asumir por defecto y recalcular
nsect_ok: MOV tsect,CX
MOV numsect,AX
MOV BX,AX
SHR BX,1 ; BX = 1/2 del nº total de sectores
MOV CX,param_tdir
AND CX,CX
JNZ tdir_def ; se ha definido nº entradas
tdir_rec: MOV CX,tdir ; nº por defecto
tdir_def: MOV AX,tsect
XOR DX,DX
MOV SI,32 ; 32 bytes = tamaño entrada direct.
DIV SI ; AX nº entradas direct. por sector
XCHG AX,CX
XOR DX,DX ; DX:AX = nº de entradas
DIV CX ; CX = entradas en cada sector
AND DX,DX ; AX = nº sectores del ROOT
JZ dir_ok?
INC AX ; redondear tamaño de ROOT
dir_ok?: CMP AX,BX ; BX = 1/2 nº sectores del disco
JB dir_ok
OR lista_err,ERROR12 ; directorio excesivo
JMP tdir_rec ; directorio por defecto
dir_ok: MOV sdir,AX
MUL tsect
MOV CX,32
CALL divCX
MOV tdir,AX ; optimizar tamaño de directorio
MOV AX,512
XOR DX,DX
DIV tsect ; 512 / tamaño de sector
MOV BL,tcluster
XOR BH,BH
MUL BX ; ajustar tamaño de cluster
AND AL,AL
JZ propclus_ok
MOV tcluster,AL
propclus_ok: MOV BX,param_tcluster
AND BX,BX
JNZ tcluster_def ; se ha definido tamaño de cluster
tcluster_rec: MOV BL,tcluster ; tamaño por defecto
XOR BH,BH
tcluster_def: SHL BX,1
CMP BX,numsect ; ¿cabe seguro un cluster?
JB tcluster_ok
tcluster_mal: OR lista_err,ERROR13 ; tamaño de cluster incorrecto
JMP tcluster_rec
tcluster_ok: SHR BX,1
MOV AX,tsect
MUL BX ; DX:AX = tamaño de cluster
JC tcluster_mal
CMP AX,31*1024
JA tcluster_mal ; cluster de más de 31 Kb
MOV tcluster,BL ; sectores por cluster
MOV tamcluster,AX ; tamaño de cluster
MOV CX,param_f ; considerar número de FATs
MOV nfats,CL
MOV SI,3
MOV CX,param_f
SHL SI,CL
SHR SI,1
CALL eval_clust ; obtener nº más alto de cluster
CMP AX,4086
JAE fat16 ; el nº más alto supera 4085
MOV CX,3
MUL CX ; clusters * 3
SHR DX,1
RCR AX,1 ; clusters * 3 / 2 = clusters * 1,5
JMP calc_sfat
fat16: MOV SI,4
MOV CX,param_f ; considerar número de FATs
SHL SI,CL
SHR SI,1
CALL eval_clust
SHL AX,1
RCL DX,1 ; clusters * 2
calc_sfat: DIV tsect ; AX = nº sectores de FAT aprox.
AND DX,DX
JZ fat_ok
INC AX ; redondeo
fat_ok: MOV sfat,AX
MOV AX,numsect ; nº total de sectores
DEC AX ; descontar BOOT
SUB AX,sdir ; descontar ROOT
SUB AX,sfat ; descontar FAT
MOV CL,tcluster
XOR CH,CH
XOR DX,DX
DIV CX ; AX = número real de clusters
INC AX ; se numeran desde 2
MOV ultclus,AX
RET
adaptar_param ENDP
preparar_BPB PROC
MOV AX,tsect
MOV bytes_sector,AX
MOV AL,tcluster
MOV sect_cluster,AL
MOV AX,tdir
MOV entradas_raiz,AX
MOV AX,numsect
MOV num_sect,AX
MOV AL,nfats
MOV num_fats,AL
MOV AX,sfat
MOV sectores_fat,AX
MOV cambiado,0FFh ; ha habido «cambio» de disco
RET
preparar_BPB ENDP
prep_driver PROC
MOV AL,tipo_soporte
LEA SI,procesa_xms
MOV CX,tam_proc_xms
DEC AL
JZ prep_mem ; instalar rutina XMS
LEA SI,procesa_ems
MOV CX,tam_proc_ems
DEC AL
JZ prep_mem ; instalar rutina EMS
LEA SI,procesa_con
MOV CX,tam_proc_con ; instalar rutina memoria conv.
prep_mem: LEA DI,procesa_io
CLD
XPUSH <SI,DI,CX>
REP MOVSB ; instalar rutina en el disco
XPOP <CX,DI,SI>
XPUSH <ES,DS>
POP ES
REP MOVSB ; y en el propio TDSK.EXE (para
POP ES ; usarla después al formatear)
LEA CX,f_tdsk_ctrl
LEA SI,i_tdsk_ctrl
SUB CX,SI
MOV DI,SI
REP MOVSB ; actualizar variables
LEA CX,fin_bpb
LEA SI,bpb
SUB CX,SI
MOV DI,SI
REP MOVSB ; actualizar BPB
RET
prep_driver ENDP
relocalizar PROC
CMP tipo_soporte,3
formatear_tdsk PROC
PUSH ES ; *
PUSH DS
POP ES
LEA SI,sector_cero
LEA DI,area_trabajo
MOV CX,128
CLD
REP MOVSB ; primeros 128 bytes del BOOT
XOR AX,AX
MOV CX,tam_a_trabajo-128
REP STOSB ; a 0 resto del área de trabajo
LEA DI,area_trabajo
ADD DI,tsect
MOV [DI-2],0AA55h ; marca de sector válido
CALL escribe_sectAX ; escribir sector BOOT (AX=0)
LEA DI,area_trabajo
MOV CX,tsect
REP STOSB ; borrar area de trabajo
MOV AX,sfat
MOV CX,param_f ; considerar número de FATs
SHL AX,CL
SHR AX,1
escribe_sectAX PROC
PUSHF ; preservar bit DF
XPUSH <AX,BX,CX,DX,SI,DI,BP,DS,ES>
XOR BP,BP ; indicar escritura
LEA DI,area_trabajo ; ES:DI buffer
MOV BX,AX ; número de sector
MOV AX,1 ; 1 sector
CALL io_proc ; acceder al disco directamente
XPOP <ES,DS,BP,DI,SI,DX,CX,BX,AX>
POPF
RET
escribe_sectAX ENDP
fecha_hora PROC
MOV AH,2Ah
INT 21h ; obtener fecha del sistema
MOV AL,32
MUL DH ; AX = mes * 32
SUB CX,1980
SHL CL,1 ; (año-1980)*2
ADD AH,CL ; sumar (año-1980)*512
MOV CL,DL ; CX = dia (CH=0)
ADD AX,CX
PUSH AX ; * guardar fecha
MOV AH,2Ch
INT 21h ; obtener hora del sistema
MOV AL,32
MUL CL ; AX = minutos*32
MOV CL,3
SHL CH,CL
XOR CL,CL ; CX = hora*2048
ADD AX,CX
SHR DH,1 ; segundos/2
ADD AL,DH
ADC AH,0
POP DX ; * recuperar fecha
RET
fecha_hora ENDP
renombrar_mcb PROC
PUSH ES
MOV AL,letra_unidad
MOV BYTE PTR nombre_tdsk+5,AL
MOV BYTE PTR nombre_tdsk+4,'('
MOV BYTE PTR nombre_tdsk+6,')'
MOV AX,segm_psp
DEC AX
MOV ES,AX
LEA SI,nombre_tdsk
MOV DI,8
MOV CX,DI
CLD
REP MOVSB
POP ES
RET
renombrar_mcb ENDP
info_disco PROC
CALL InitMultiPrint
LEA DX,ayuda_txt ; ayuda en español
CMP param_h,ON ; ¿solicitud de ayuda?
JNE cont_info ; no
JMP info_exit
cont_info: TEST err_grave,0FFFFh
JZ info_no_fatal
LEA DX,err_grave_gen ; texto de encabezamiento
CALL imprimir ; imprimir errores graves:
LEA DX,e0
TEST err_grave,ERROR0
JZ otro_fallo ; no es error de DOS incorrecto
CALL imprimir
MOV SP,tam_pila
PUSH segm_psp ; en DOS 1.x hay que terminar
XOR AX,AX ; con CS = PSP
PUSH AX
RETF ; ejecutar INT 20h de PSP:0
otro_fallo: LEA DX,e1
TEST err_grave,ERROR1
JNZ info_g
LEA DX,e2
TEST err_grave,ERROR2
JNZ info_g
LEA DX,e3
info_g: JMP info_exit
info_no_fatal: CMP ES:tipo_soporte,0 ; error no fatal
JNE info_reporte
LEA DX,info_ins ; disco no formateado
CALL imprimir
CALL impr_unidad
LEA DX,info_ins2
CMP lista_err,0
JE info_exit ; sin mensajes de advertencia
CALL imprimir ; ... o con ellos
JMP info_err
info_reporte: CALL pr_info ; disco formateado
CMP lista_err,0
JE info_ret ; sin mensajes de advertencia
LEA DX,cab_adv_txt ; ... o con ellos
CALL imprimir ; cabecera de advertencias
info_err: MOV AX,lista_err
LEA BX,tabla_mens-2 ; tabla de mensajes
MOV CX,16 ; 16 posibles mensajes
busca_err: ADD BX,2
SHR AX,1
JC informa
mas_mens: LOOP busca_err ; no se produce ese error
JMP info_ret
informa: LEA DX,mens_cabec ; inicio común a los mensajes
CALL imprimir
MOV DX,[BX] ; dirección de ese mensaje
CALL imprimir
JMP mas_mens ; acabar con todos
info_exit: CALL imprimir
info_ret: RET
info_disco ENDP
pr_info PROC
LEA DX,info_txt
CALL imprimir
CALL impr_unidad
LEA DX,inf_tsect
CALL imprimir
MOV AX,ES:bytes_sector
XOR DX,DX
MOV CL,5
CALL print_32
LEA DX,inf_tdir
CALL imprimir
MOV AX,ES:entradas_raiz
XOR DX,DX
MOV CL,5
CALL print_32
LEA DX,inf_tdisco
CALL imprimir
MOV AX,ES:num_sect
MUL ES:bytes_sector
MOV BX,1024
DIV BX
MOV CL,5
CALL print_32
LEA DX,inf_tcluster
CALL imprimir
MOV AL,ES:sect_cluster
XOR AH,AH
XOR DX,DX
MOV CL,5
CALL print_32
LEA DX,inf_mem
CALL imprimir
MOV AL,ES:tipo_soporte
LEA DX,inf_mem_xms
DEC AL
JZ mem_ifdo ; memoria XMS
LEA DX,inf_mem_ems
DEC AL
JZ mem_ifdo ; memoria EMS
LEA DX,inf_mem_con
CMP ES:mem_handle,0A000h
JB mem_ifdo ; memoria convencional
LEA DX,inf_mem_sup ; memoria superior
mem_ifdo: CALL imprimir
LEA DX,inf_nclusters
CALL imprimir
MOV AX,ES:entradas_raiz
MOV BX,32
MUL BX ; bytes ocupados por directorio
DIV ES:bytes_sector ; AX = sectores del directorio
ADD AX,ES:sect_reserv
ADD AX,ES:sectores_fat
SUB AX,ES:num_sect
NEG AX ; AX = sectores libres
XOR DX,DX
MOV BL,ES:sect_cluster
XOR BH,BH
DIV BX ; AX = nº de clusters
XOR DX,DX
MOV CL,5
CALL print_32
LEA DX,inf_tfat
CALL imprimir
LEA DX,inf_tfat12
CMP AX,4085 ; ¿FAT12?
JB ifat_ok
LEA DX,inf_tfat16
ifat_ok: CALL imprimir
LEA DX,inf_final
CALL imprimir
RET
pr_info ENDP
impr_unidad PROC
XPUSH <AX, DX>
MOV AL,letra_unidad
MOV AH,0
MOV WORD PTR area_trabajo,AX
LEA DX,area_trabajo
CALL imprimir
XPOP <DX, AX>
RET
impr_unidad ENDP
print_32 PROC
PUSH DS
PUSH ES
PUSH CS
PUSH CS
POP DS
POP ES
PUSH AX ; preservar todos los registros
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
PUSHF
MOV formato_pr32,CL ; byte del formato de impresión
elegido
MOV CX,idioma_seps
separ_pr32: MOV millares_pr32,CH ; separador de millares
MOV fracc_pr32,CL ; separador parte fraccional
MOV BX,OFFSET tabla_pr32
MOV CX,10
digit_pr32: PUSH CX
PUSH AX
PUSH DX
XOR DI,DI
MOV SI,1 ; DISI = 1
DEC CX ; CX - 1
JCXZ hecho_pr32
factor_pr32: SAL SI,1
RCL DI,1 ; DISI * 2
MOV DX,DI
MOV AX,SI
SAL SI,1
RCL DI,1
SAL SI,1
RCL DI,1 ; DISI * 8
ADD SI,AX
ADC DI,DX ; DISI = DISI*8 + DISI*2 = DISI*10
LOOP factor_pr32 ; DISI = DISI*10*10* ... (CX-1
veces)
hecho_pr32: POP DX ; luego DISI = 10 elevado a (CX-1)
POP AX ; CX se recuperará más tarde
MOV CL,0FFh
rep_sub_pr32: INC CL
SUB AX,SI
SBB DX,DI ; DXAX = DXAX - DISI
JNC rep_sub_pr32 ; restar el factor cuanto se pueda
ADD AX,SI ; subsanar el desbordamiento:
ADC DX,DI ; DXAX = DXAX + DISI
ADD CL,'0' ; pasar binario a ASCII
MOV [BX],CL
POP CX ; CX se recupera ahora
INC BX
LOOP digit_pr32 ; próximo dígito del número
STD ; transferencias (MOVS) hacia
atrás
DEC BX ; BX apunta al último dígito
MOV final_pr32,BX ; último dígito
MOV ent_frac_pr32,BX ; frontera parte entera/fraccional
MOV CL,5
MOV AL,formato_pr32
SHR AL,CL ; AL = nº de decimales
AND AL,AL
JZ no_frac_pr32 ; ninguno
MOV CL,AL
XOR CH,CH
MOV SI,final_pr32
MOV DI,SI
INC DI
REP MOVSB ; correr cadena arriba (hacer
hueco)
INC final_pr32
MOV AL,fracc_pr32
MOV [DI],AL ; poner separador de parte
fraccional
MOV ent_frac_pr32,SI ; indicar nueva frontera
no_frac_pr32: MOV AL,formato_pr32
TEST AL,16 ; interpretar el formato
especificado
JZ poner_pr32 ; imprimir como tal
entera_pr32: MOV CX,final_pr32 ; añadir separadores de millar
SUB CX,ent_frac_pr32
ADD CX,3
MOV SI,final_pr32
MOV DI,SI
INC DI
REP MOVSB ; correr cadena arriba (hacer
hueco)
MOV AL,millares_pr32
MOV [DI],AL ; poner separador de millares
INC final_pr32
MOV ent_frac_pr32,SI ; usar esta variable como puntero
SUB SI,OFFSET tabla_pr32
CMP SI,3
JAE entera_pr32 ; próximo separador
poner_pr32: MOV BX,final_pr32
divCX PROC
XPUSH <BX,SI,CX,AX,DX>
MOV SI,32
XOR BX,BX
divmas: SHL AX,1
RCL DX,1
RCL BX,1
CMP BX,CX
JB dividido ; "no cabe"
SUB BX,CX
INC AL ; 1 al cociente
dividido: DEC SI
JNZ divmas
AND DX,DX
JZ div_ok
XPOP <DX,AX> ; error
STC
JMP div_fin
div_ok: MOV DX,BX ; resto en DX y cociente en AX
ADD SP,4 ; «sacar» sin sacar DX y AX
CLC
div_fin: XPOP <CX,SI,BX> ; recuperar CX, SI y BX
RET
divCX ENDP
imprimir PROC
PUSH AX
MOV AL,param_m
CMP modo,CONFIG ; ¿en CONFIG.SYS?
JNE m_ok ; no
XOR AL,ON ; sí: /M opera al revés
m_ok: MOV pr_mono,AL
CALL print
POP AX
RET
imprimir ENDP
print PROC
XPUSH <AX, BX, CX, DX, SI, DI, ES>
CMP idioma,0
JNE pr_decidir
PUSH DX ; *
MOV AH,30h
INT 21h
XCHG AH,AL
MOV CX,AX ; CX = versión del DOS
CMP param_i,ON
MOV AX,codigo_tfno
MOV BX,1234h
JNE pr_busca_cod ; parámetro /I=cod no indicado
MOV BX,AX
MOV AL,0FFh
CMP BX,255
JAE pr_cod ; código mayor o igual de 255
MOV AL,BL ; código menor de 255
pr_cod: CMP CX,200h
JAE pr_cod_tfno ; DOS >= 2.X
pr_busca_cod: CMP CX,200h
MOV AX,1 ; inglés para DOS < 2.X
JB pr_habla_ax
MOV AL,0
pr_cod_tfno: LEA DX,area_trabajo
MOV AH,38h
XPUSH <BX, CX>
INT 21h ; obtener información del pais
XPOP <CX, AX>
JC pr_habla_ax ; fallo en la función
CMP CX,20Bh
JE pr_habla_ax ; DOS 2.11: AX cód. telefónico
CMP CX,300h
MOV AX,1
JB pr_habla_ax ; 2.x excepto 2.11: mala suerte
MOV AX,BX
LEA BX,area_trabajo
MOV CH,[BX+7] ; separador de millares
MOV CL,[BX+9] ; separador de decimales
MOV idioma_seps,CX
pr_habla_ax: LEA BX,info_paises-2
MOV CX,1 ; supuesto idioma 1
pr_busca_idi: ADD BX,2
MOV DX,[BX]
CMP AX,DX
JE pr_habla_ese
AND DX,DX
JNZ pr_busca_idi
INC CX ; será otro idioma
CMP [BX+2],DX
JNE pr_busca_idi ; no es fin de la tabla
pr_habla_ese: MOV idioma,CL
POP DX ; *
MultiPrint PROC
XPUSH <AX,BX,CX,DX,SI,DI,BP,DS,ES>
PUSH DS
POP ES
PUSH CS
POP DS
LEA AX,pr_AL_dos
CMP pr_mono,ON
JE pr_rut_ok
LEA AX,pr_AL_bios
pr_rut_ok: MOV pr_rut,AX ; instalar rutina de impresión
MOV BX,DX
pr_otro: MOV AL,ES:[BX]
PUSH BX
CMP AL,' '
JAE pr_ASCII ; no es un código de control
AND AL,AL
JZ pr_exit ; código de control 0: final
CMP AL,1
JE pr_setcolor ; código de control 1: color
CMP AL,2
JE pr_setveces ; código de control 2: repetir
pr_ASCII: CALL pr_rut
POP BX
INC BX
JMP pr_otro
pr_setcolor: MOV AL,ES:[BX+1]
MOV pr_color,AL ; actualizar color
POP BX
ADD BX,2
JMP pr_otro
pr_setveces: MOV AL,ES:[BX+1]
MOV pr_veces,AL ; actualizar repeticiones
POP BX
ADD BX,2
JMP pr_otro
pr_exit: XPOP <BX,ES,DS,BP,DI,SI,DX,CX,BX,AX>
RET
MultiPrint ENDP
InitMultiPrint PROC
XPUSH <AX,BX,CX,DX,BP,DS,ES>
PUSH CS
POP DS
MOV pr_veces,1
MOV pr_color,15 ; valores por defecto
MOV pr_mono,OFF
pr_i_80?: MOV AH,0Fh
INT 10h
CMP AH,80 ; ¿80 ó más columnas?
JAE pr_i_video_ok ; así es
MOV AX,3
INT 10h ; forzar modo de 80 columnas
JMP pr_i_80?
pr_i_video_ok: MOV pr_maxX,AH ; inicializar máxima coord. X
MOV pr_pagina,BH ; inicializar página activa
MOV AX,40h
MOV ES,AX ; ES: -> variables del BIOS
MOV AL,ES:[84h] ; variable de nº líneas - 1
CMP AL,24 ; ¿el BIOS define la variable?
JB pr_i_maxy_ok ; no
MOV pr_maxY,AL ; inicializar máxima coord. Y
pr_i_maxy_ok: MOV AH,8 ; (BH = página)
procesa_xms PROC
MOV DS,CS:mem_handle
JNC no_xmslib
.286 ; rutina ejecutada desde 286+
PUSHA ; sistema reinicializando:
MOV AH,0Dh
CALL llama_XMS ; desbloquear EMB (prudente)
MOV AH,0Ah
CALL llama_XMS ; liberar EMB
POPA
.8086
RET
no_xmslib: DEC BP ; leer/escribir en el disco
JNZ xms_escribe
PUSH ES
PUSH DI ; segmento:offset destino
PUSH BP ; handle destino (BP=0)
xms_escribe: PUSH DX
PUSH AX ; desplazamiento DX:AX
PUSH DS ; handle fuente/destino
JZ xms_general
INC BP ; hacer BP = 0
PUSH ES
PUSH DI ; segmento:offset fuente
PUSH BP ; handle fuente (BP=0)
xms_general: SHL CX,1 ; palabras -> bytes
RCL BP,1 ; BP era 0
PUSH BP ; tamaño bloque (parte alta)
PUSH CX ; tamaño bloque (parte baja)
MOV SI,SP
PUSH SS
POP DS ; DS:SI apuntando a la pila
MOV AH,0Bh ; función para mover EMB
CALL llama_XMS ; mover EMB (DS no importa)
ADD SP,16 ; equilibrar pila
CMP AL,1 ; ¿falló el controlador?
JE xms_proc_ok
MOV AX,0C81h ; anomalía general
xms_proc_ok: XCHG AH,AL ; colocar resultado
RET
procesa_xms ENDP
llama_XMS PROC
MOV DX,DS ; handle en DS (si utilizado)
CALL CS:xms_driver ; ejecutar función XMS
RET
llama_XMS ENDP
procesa_con PROC
JC con_exit ; sistema inicializándose
MOV BX,16 ; bytes por párrafo
DIV BX ; AX = segmento, DX = offset
ADD AX,CS:mem_handle ; segmento de inicio datos
MOV DS,AX
MOV SI,DX ; DS:SI inicio de datos
DEC BP ; y ES:DI destino del buffer
JZ con_general ; es lectura
XCHG SI,DI ; escritura: intercambiar
XPUSH <DS,ES>
XPOP <DS,ES>
con_general: CLD
CMP CS:cpu386,ON
JE con_tr32bit
REP MOVSW
JMP con_tr_fin
con_tr32bit: SHR CX,1 ; nº palabras de 32 bit a mover
JCXZ con_trdo ; evitar desgracia
.386
PUSHAD
XOR EAX,EAX ; asegurar no violación
DEC AX ; de segmento-64K
AND ECX,EAX ; EAX = 0FFFFh
AND ESI,EAX
AND EDI,EAX
REP MOVSD ; transferencia ultrarrápida
con_trdo: POPAD ; POPAD falla en muchos 386
NOP ; arreglar fallo de POPAD
.8086
con_tr_fin: MOV AX,100h ; todo fue bien, por supuesto
con_exit: RET
procesa_con ENDP
modo DB ? ; CONFIG/AUTOEXEC
dosver DW ? ; versión del DOS
info_paises DW 54 ; Argentina
DW 591 ; Bolivia
DW 57 ; Colombia
DW 506 ; Costa Rica
DW 56 ; Chile
DW 593 ; Ecuador
DW 503 ; El Salvador
DW 34 ; España
DW 63 ; Filipinas
DW 502 ; Guatemala
DW 504 ; Honduras
DW 212 ; Marruecos
DW 52 ; México
DW 505 ; Nicaragua
DW 507 ; Panamá
DW 595 ; Paraguay
DW 51 ; Perú
DW 80 ; Puerto Rico
DW 508 ; República Dominicana
DW 598 ; Uruguay
DW 58 ; Venezuela
DW 3 ; Latinoamérica
DW 0 ; fin de la información
DW 41 ; Switzerland
DW 43 ; Austria
DW 49 ; Germany
DW 0 ; fin de la información
DW 0 ; no más idiomas
info_txt DB 10,2,12,3,1,colA,"+",2,27,"-+",2,25,"-+",1,colC
DB 10,2,12,3,1,colA,"| ",1,colD,"TURBODSK 2.3",1,colA
DB " - Unidad ",1,colB
DB 255
DB 10,2,10,3,1,colA,"+",2,28,"-+",2,28,"-+",1,colC
DB 10,2,10,3,1,colA,"| ",1,colD,"TURBODSK 2.3",1,colA
DB " - Laufwerk ",1,colB
DB 255
DB 10,2,12,3,1,colA,"+",2,26,"-+",2,25,"-+",1,colC
DB 10,2,12,3,1,colA,"| ",1,colD,"TURBODSK 2.3",1,colA
DB " - Drive ",1,colB
DB 0
DB " ",1,colA,"|",1,colC,10,2,10,3
DB 1,colA,"+",2,28,"-+ Verzeichniseinträge:",1,colB,
" "
DB 255
DB " ",1,colA,"|",1,colC,10,2,12,3
DB 1,colA,"+",2,26,"-+ Root entries:",2,4," ",1,colB,"
"
DB 0
DB " ",1,colA,"|",1,colC,10
DB 2,10,3,1,colA,"| Größe:",2,10," ",1,colB," "
DB 255
DB " ",1,colA,"|",1,colC,10
DB 2,12,3,1,colA,"| Size:",2,4," ",1,colB," "
DB 0
DB " ",1,colA,"|",1,colC,10
DB 2,10,3,1,colA,"| Speicher: ",1,colB
DB 255
DB " ",1,colA,"|",1,colC,10
DB 2,12,3,1,colA,"| Memory: ",1,colB
DB 0
inf_tfat12 DB "12",0
inf_tfat16 DB "16",0
DB 1,colA,")",2,5," ",1,colA,"|",1,colC,10
DB 2,10,3,1,colA,"+",2,28,"-+",2,28,"-+",1,colC,10
DB 255
DB 1,colA,") ",1,colA,"|",1,colC,10,2,12,3
DB 1,colA,"+",2,26,"-+",2,25,"-+",1,colC,10
DB 0
ERROR0 EQU 1
ERROR1 EQU 2
ERROR2 EQU 4
ERROR3 EQU 8 ; TURBODSK es muy flexible y se instala
ERROR4 EQU 16 ; casi de cualquier forma, aunque a
ERROR5 EQU 32 ; veces no se reserve memoria y sea
ERROR6 EQU 64 ; necesario volver a ejecutarlo después
ERROR7 EQU 128 ; desde el DOS para «formatearlo».
ERROR8 EQU 256
ERROR9 EQU 512
ERROR10 EQU 1024
ERROR11 EQU 2048
ERROR12 EQU 4096
ERROR13 EQU 8192
ERROR14 EQU 16384
ERROR15 EQU 32768
mens_cabec DB 2,8,3,0
tabla_mens DW m0,m1,m2,m3,m4,m5,m6,m7
DW m8,m9,m10,m11,m12,m13,m14,m15
cab_adv_txt DB 10,2,8,3,1,12
DB "Advertencias y/o errores de TURBODSK:",2,27,"
",10,1,10
DB 255
DB 10,2,8,3,1,12
DB "Warnungen und Fehlermeldungen von TURBODSK:",2,27,"
",10,1,10
DB 255
DB 10,2,8,3,1,12
DB "Warnings and errors of TURBODSK:",2,32," ",10,1,10
DB 0
CONFIG.SYS ",10,2,8,3
DB " setzen, um Speicher für die EMS-Unterstützung zu
reservieren. ",10,2,8,3
DB " Dadurch erhöht sich der Speicherbedarf von 432
auf 608 Bytes. ",10
DB 255
DB 255
; ------------ Ayuda
DB 255
DB 255
DB 0
_PRINCIPAL ENDS
END main