Volver menú revistas Volver página anterior

El Amiga Me Encanta ha conseguido el permiso por escrito de IDG Comunications España para ofrecer los artículos de la revista Amiga World España.

N° 7- Febrero 1990

INICIACION AL LENGUAJE ENSAMBLADOR (9° Parte)


Por Fernando G. Terradillos

En este capítulo trataremos el sistema de
distribución de infromación dentro de un
disco. Un elemento bastante desconocido al
usuario, pero que trataremos en profundidad.

Para ello veremos la organización jerárquica de bloques para su posterior lectura o grabación con cómodas rutinas. Para completar el capítulo veremos cómo funciona el bootblock, es decir, los bloques 0 y 1 de un disco ejecutable, por ejemplo, su workbench.

Comencemos por el fondo del asunto, es decir, cómo almacena el Amiga toda su información en el disco, siendo esto posible gracias a su compleja estructura, que soporta en un mismo disco un enorme número de ficheros y directorios.

Para continuar debe saber que un disco se compone de 80 cilindros (del 0 a la 79), cada uno de ellos con dos cabezas de lectura (superior e inferior) y en cada una de ellas, 11 sectores (del 0 al 10, 22 em total por cada cilindro). Esto hace en total 1760 bloques (del 0 al 1.759). Cada bloque es cuestión contiene 512 bytes, siendo la totalidad exacta de un disco en bytes 901.120. Pero si sólo disponemos de 856 k. cuando formateamos uno de ellos, dónde se encuentra el resto. Pues exactamente está ocupando bloques tan importantes como el bootblock (2 bloques = 1 k.), el directorio principal (1 bloque), el utilizado como mapa de bloques libres en disco (1 bloque), y si tenemos en cuenta que por cada bloque arranca 24 bytes al disco (un total de 42144 bytes) esto hace una suma suficiente para que nos demos cuenta que el sistema operativo del disco usa parte del mismo para utilización propia.

Resumen de organización del disco:

80 cilindros = 160 pistas.
2 cabezas = 80 pintas por cabeza.
11 sectores por pista = 1.760 bloques.
1 bloque = 512 bytes.
1.760 bloques = 901.120 bytes.

Como he dicho antes, el sistema para organizar todos estos bloques es jerárquico, como el de un árbol, teniendo como base el directorio principal, que es el que conseguimos al hacer un simple DIR desde el CLI a cualquier disco. Este directorio principal ocupa simplemente un bloque de disco, y para mayor facilidad de búsqueda se ha situado en el centro exacto del disco (bloque 880, sector 0, cabeza 0, cilindro 40). Esto es totalmente lógico, pues sería de tontos meter el directorio prinicipal en el comienzo del disco para que el cabezal del DRIVE se volviera loco dándose unos paseos por toda su superficie.

A continuación voy a presentar los 512 bytes del bloque 880 de mi Workbench, para que a continuación explique cuál es el contenido del mismo. Antes de hacerlo quiero decir que hay un libro magnífico que recomendé en el capítulo 1 y voy a repetir su título: "The AmigaDOS Manual" de BANTAM AMIGA LIBRARY.

También recomiendo utilizar algún editor de discos (DISK ED, por ejemplo) para hacer el seguimiento poco a poco.

La información de la derecha pertenece al offset o posición dentro del bloque en hexadecimal. Los números del centro pertenecen a la notación hexadecimal de la información del bloque en sí. La parte de la derecha pertenece a la información en ASCII correspondiente a la misma línea.

Como podrá comprobar de un vistazo ya estamos viendo el nombre del disco en la parte inferior del bloque. También hemos de decir que la información de estos tipos de bloques se agrupan de cuatro en cuatro bytes (dobles palabras), excepto para la información de nombres de ficheros, directorios, etc.

El resto de la información vamos a darla como offsets en hexadecimal, es decir, por ejemplo el offset $0138 contiene $FFFFFFFF y explicaremos el contenido de la misma posición (cuadro 1).

CUADRO 1
OFFSET VALOR CONTENIDO

-$0000 $00000002 tipo de bloque, en este caso 2 = primario
-$0004 $00000000 número cabecera, siempre 0
-$0008 $00000000 número de secuencia mayor, siempre 0
-$000C $00000048 tamaño tabla en dobles palabras (=tamaño bloque-56)
-$0014 $AE6FF84C CHECKSUM o suma de chequeo, contiene el valor $FFFFFFFF menos la suma total del resto de dobles palabras.
-$0018-$0137 --------- este rango contiene los punteros a otros bloques.
Por cada número obtenemos el enlace a un fichero o directorio. Más tarde veremos un ejemplo de cualquiera de ellos.
-$01A4-$1AB --------- fecha en dias y minutos en el que se vario el directorio o fichero
-$01B0-$1CF --------- nombre del directorio principal, cuyo primer byte, en este caso $10 es el número de caracteres del nombre, con un máximo de 30.
-$01E4-$1EB --------- fecha de creación directorio principal
-$01F0 $00000000 siguiente entrada en tabla de bloques.
Cuando el directorio principal contiene más ficheros de los que puede admitir se crea un bloque de expansión que contiene más punteros. Cuando esta a 0 es que no hay expansión, cualquier otro número indicaria que si tiene.
-$01F4 $00000000 directorio anterior que en el caso de ser directorio principal no lo posee, pues es el mayor rango dentro del disco.
-$01F8 $00000000 extensión, siempre a 0
-$01FC $00000001 segundo número indicando el tipo de bloque.

Ahora vamos a ver cómo enlaza el directorio principal con otro directorio; en este caso pondremos el directorio 'devs' que contiene la información acerca de los periféricos del Amiga. Para encontrarlo hay que mirar las posiciones dentro de la tabla de bloques entre los offsets $0018-$0137, y es exactamente el $0070 el que enlaza al directorio. El valor contenido en este offset es el de $0000037D que en decimal se convierte a 893, siendo éste el bloque exacto que contiene la información necesaria para enlazar el directorio 'devs' con otros ficheros u otros directorios. Quiero hacer ver que todavía no hemos llegado a la información propia que contiene un fichero, sino que estamos viendo qué es lo que hace el Amigados hasta llegar a un fichero que le hemos pedido. Y a continuación veamos el bloque referente al directorio 'devs' en mi Workbench.

También podrá ver a primera vista el nombre del directorio, siendo, asi mismo, la estructura parecida a la del directorio principal. veamos cuáles son las diferencias (cuadro 2).

CUADRO 2
OFFSET VALOR CONTENIDO

-$0000 $00000002 tipo de bloque, 2 = primario
-$0004 $0000037D cabecera, apuntándose a el mismo
-$0014 $889B5C91 CHECKSUM
-$0018-$0137 --------- tabla de bloques conteniendo punteros a los directorio y ficheros que contienen el directorio. El siguiente ejemplo lo veremos con un fichero.
-$0140 $00000005 bits de protección del directorio, correspondientes a RWED (lectura,escritura,editar,borrar), en este caso 5 en binario es 0101 y por consiguiente estan activados los bits de protección contra la escritura y el borrado.
-$0148-$01A3 --------- comentario del directorio, en este caso a 0
$01A4-$1AB --------- fecha en dias y minutos en que se creo el directorio
-$01B0-$1CF --------- nombre del directorio, con la misma estructura de longitud que el directorio principal.
-$01F0 $00000000 siguiente entrada en tabla de bloques
-$01F4 $00000370 directorio anterior, que en este caso es el directorio principal con el valor en hexadecimal $0370 = 880. Este valor lo utiliza para una mayor agilidad de ir retrasando directorios.
-$01F8 $00000000 extensión, siempre a 0
-$01FC $00000002 segundo número indicando el tipo de bloque, en este caso el valor 2 representa a un directorio de usuario.

Como podrá observar, la semejanza al directorio principal por parte de un directorio de usuario es bastante, ya que los dos son directorios. A continuación veamos el bloque referente a un fichero, en driver de la impresora.

El bloque es cuestión es el que se encuentra en el offset $0098 con el valor $402 (en decimal 1026). El bloque cabecera de fichero contiene información parecida a los dos tipos de bloques anteriores. Veámoslo.

Podrá observar una serie más o menos consecutiva de números, éstos pertenecen a la lista de bloques en el que se encuentran los datos fichero en cuestión, asimismo contiene información ya vista anteriormente. Veamos la configuración de este tipo de bloques (cuadro 3).

CUADRO 3
OFFSET VALOR CONTENIDO

-$0000 $00000002 tipo de bloque. 2 = primario
-$0004 $00000402 cabecera, apuntandose a el mismo
-$0008 $00000037 número de bloques del fichero
-$0010 $00000087 primer bloque del fichero
-$0014 $EB52C434 CHECKSUM
-$001B-$0137 --------- tabla de bloques conteniendo los bloques pertenecientes al fichero, comenzando de abajo a arriba.
Si el offset anterior que indica el primer bloque del fichero es $87 la continuación de bloques sería esta: $89,$8A,$8B,$8C,$8D,$8E,$79,$7C,$7D,$7E,$7F,$80,$81,$82 y así hasta completar el número de bloques que nos indicaba el otro offset.
-$0140 $00000000 bits de protección del fichero, en este caso sin protección alguna
-$148-$01A3 --------- comentario del fichero
-$01A4-$1AB --------- fecha en dias y minutos de la creación del fichero
-$01B0-$1CF --------- nombre del fichero
-$01F0 $00000000 siguiente entrada en tabla de bloques.

Cuando un fichero contiene más bloques de los que caben en el rango de los offsets dedicados a ellos se crea un bloque de extensión que contendra la continuación de serie de bloques que le faltaban al fichero. En este caso no tiene bloque de extensión, pero veremos otro ejemplo en el siguiente tipo de bloque.

-$01F4 $0000037D, bloque apuntando al directorio anterior, exactamente a 'devs', ya que este bloque pertenece a la estructura del directorio. Observa que el número $037D es el mismo que contenia el anterior tipo.

-$01FC $FFFFFFFD, segundo número indicando el tipo de bloque. Si lo cambiamos como negativo nos encontramos con $-3, el valor exacto perteneciente al tipo de bloque.

Cuando el Amiga tiene que encontrar el nombre del fichero en cuestión para su posterior lectura, busca entre todos los bloques del directorio que tiene presente que le indica los punteros de los offsets $0018 a $0137. Cuando lo haya encontrado pasa a almacenar la cabecera del bloque que le dará toda la información vista anteriormente.

Ahora pasará a la lectura del fichero y para ello chequea el número de bloques que contiene el fichero (offset $0008), almacena la lista de bloques consecutiva contenida en los offsets $0018 a $0137 (en sentido inverso), y si el fichero es lo suficientemente largo para que haya un bloque de expansión también pasa a su lectura.

En el caso anterior, de que necesite un bloque de expansión para almacenar esa lista de punteros, se creará un nuevo bloque llamado simplemente de expansión de fichero. Veamos el ejemplo de mi Workbench con el fichero Preferences situado em el directorio principal.

La estructura para este tipo de bloques es el cuadro 4.

CUADRO 4
OFFSET VALOR CONTENIDO

-$0000 $00000010 tipo de bloque, 2 = bloque lista
-$0004 $00000453 cabecera, apuntando a el mismo
-$0008 $0000002B número de bloques en la expansión
-$0014 $FFFF7866 CHECKSUM
-$0018-$0137 --------- expansión de lista de bloques. Si aún el fichero tiene más bloques comprobar posteriormente el siguiente offset de expansión.
-$01F4 $00000385 puntero al bloque anterior en la expansión de la lista, en este caso la cabecera del fichero.
-$01F8 $00000000 siguiente bloque con la expansion de la lista de bloques, si el fichero es aún más largo de los bloques que puede almacenar el rango.
-$01FC $FFFFFFFD al igual que en el bloque de cabecera.

El último tipo de bloque que queda es el de bloque de datos, es decir, el que realmente contiene los datos del fichero. Como dije anteriormente, 24 bytes de cada uno de estos bloque contienen información para el sistema operativo, y que en realidad se pierden, pues el nuevo (bueno, ya un poco antiguo) Fast File System lo resuelve. Para ello carga sólo en memoria toda la lista de bloques correspondiente al fichero y luego los lee todos. Esto en disco duro es enormememnte favorable, consiguiendo aceleración en lectura cuatro veces superior al sistema antiguo. La estructura para cualquier bloque de datos es el cuadro 5.

CUADRO 5
OFFSET VALOR CONTENIDO

-$0000 $00000008 tipo de bloque, 8 = bloque de datos
-$0004 $00000402 cabecera, apuntándose a el mismo
-$0008 $00000016 número de secuencia, o número de posición en la lista de bloques
-$0010 $000001E8 tamaño del bloque = 488 bytes. Este es un número constante en todos los bloques de datos.
-$0014 $00000072 puntero al siguiente bloque en la lectura del fichero.
-$0018 $99C0C4FD CHECKSUM
-$001C-$200 --------- datos del fichero.

Voy a hacer un resumen viendo cómo el sistema operativo del disco lee el fichero printer.device del directorio 'devs' del Workbench. Lo primero que realiza el ordenador cuando insertamos un disco dentro del Workbench o del CLI es leer el bloque 880 para saber su nombre y que está insertado. En estos momentos el bloque es la única información que tiene el Amiga acerca del disco.

Ahora para pasar al directorio 'devs' dentro del CLI entraremos el comando ya conocido de CD seguido del nombre. El amiga busca entre la tabla de bloques conteniendo punteros hacia todos los directorios y ficheros que se encuentran en el bloque 880 o directorio principal.

Una vez que ya lo ha encontrado pasa a leer en memoria la cabecera del directorio encontrado. Dentro de éste hay otra lista de bloques apuntando a otros ficheros y directorios, siendo en este caso el fichero printer.device. Al igual que en el directorio principal, el Amiga busca de la misma manera y una vez que la encuentra lee el bloque en memoria.

Este bloque se trata de la cabecera del fichero, conteniendo otra lista, que le sirve para leer todos los bloques pertenecientes a los datos reales del fichero. En muchos casos, ocupan más número bloques de los que se puede almacenar en la cabecera del fichero, creándose bloques de extensión.

Para leer el fichero, el Amiga carga en memoria todos los punteros de los bloques de datos, y a continuación los carga uno a uno, es decir, comprobando que esos punteros corresponden con los que se encuentran dentro de cada bloque de datos. En Fast File System esto no lo comprueba, siendo su acceso a los datos muy rápido.

A la hora de grabar un fichero el sistema que utiliza es sencillo. Cada disco tiene un mapa de bloques libres. Primero graba el puntero del fichero dentro de la lista del directorio en el que se encuentra en el momento. Actualiza el mapa de bloques y por último graba todos los bloques de datos.

Esto último tiene un enorme inconveniente, pues sería lo más lógico actualizar el mapa de bloques libres en último lugar. Si ocurre algún gurú mientras estamos grabando un fichero aparte de que nos hemos quedado sin fichero, el sistema de validación (reactualizar el mapa de bloques libres) falla con gran asiduidad, teniendo que copiar todos los ficheros de nuevo a otro disco. Mala suerte para los que posean un disco duro original de Commodore (no he podido probar otros sistemas), pues formateándolo en FFS, si grabando un fichero se queda colgado el Amiga va a tener grandes problemas si al arrancar de nuevo se encuentra que tiene todos los bloques disponibles, aunque estén presentes todos los ficheros. Esto es debido al incorrecto sistema de validación, el cual se podría resolver si a la hora de validar el disco, por cualquier error, verificara la presencia de cada fichero reactualizando por cada uno el mapa de bloques libres. Ahora pasemos a otro tema, que es el misterioso y desconocido bootblock, que tantos quebraderos de cabeza ha traído, pues son los portadores de la mayoría de los virus. El bootblock en el Workbench tiene la misión de configurar todo el sistema (memoria, librerías, periféricos, etc.) para luego activar un CLI, carga las preferencias con el fichero system-configuration del directorio 'devs' y ejecutar el fichero startup-sequence del directorio.

Técnicamente el bootblock son los bloques 0 y 1 de cada disco. Cuando insertamos el disco en el ordenador al empezar la sesión el Amiga carga estos dos bloques (1.024 bytes) en memoria de zona alta en Chip Memory. Las tres primeras largas palabras (12 bytes) son fundamentales para que el ordenador se dé cuenta de que es un disco ejecutable. La primera de ellas contiene el texto DOS seguido del byte 0, que indica el tipo de formato que lleva el disco. Los segundos cuatro bytes son el CHECKSUM o suma de chequeo, en el cual resta el valor hexadecimal $FFFFFFFF a la suma de todas las restantes largas pañlabras, para luego compararlos. Si no corresponden con el valor no continuará el proceso. La tercera palabra larga contiene el bloque donde se encuentra el directorio principal.

En el momento de que el sistema cargue estos dos bloques insertará dos valores en dos registros. En A0 pondrá la dirección de ejecución (posición $C = 12 a partir del bootblock) y en D0 pondrá un valor 0 si el proceso anterior de chequeo de suma ha salido correcto. Una vez verificado todo esto el resto de 1.012 bytes contiene el programa que el ordenador ejecuta. En el momento de hacerlo el sistema tiene un registro ajustado para que lo utilice el usuario. Este es el A1, en el que apunta a una estructura IORequest perteneciente al disco, es decir la estructura encargada de realizar todos los procesos que lleva un drive (lectura, grabación, etc., de bloques). Resumiendo, el ordenador se enciende, insertamos nuestro Workbench o juego preferido, el sistema carga en una posición de zona alta de Chip Memory los bloques 0 y 1 (bootblock). Comprueba que los primeros cuatro bytes llevan el texto DOS, los segundos cuatro bytes se ajustan a la suma de chequeo y que la tercera palabra larga contiene el valor $370 correspondiente al bloque 880 o que es lo mismo, directorio principal. Una vez comprobado inserta en el registro A1 un puntero a la estructura IORequest del 'drive' y ejecuta el código restante (posición 12). Por último, vamos a ver las rutinas necesarias para leer uno o más bloques en memoria, asimismo como el de la grabación. Para ello hay que utilizar estructuras muy específicas, pero el resultado final consiste en un programa de muy fácil uso y de corto espacio. Primero veamos la rutina para la lectura del bloque 880 (ver listado 1).

LISTADO 1

          BSR.L   OPENL		;ABRE LIBRERIA DOS
          BSR.L   WRITE         ;INICIALIZA ESTRUCTURAS
          BSR.L     PROG        ;PROGRAMA PRINCIPAL
          RTS              ;RETORNA AL SISTEMA
OPEN           MOVEA.L 4,A6          ;EXEC
          LEA     DOSLIB,A1     ;NOMBRE LIBRARIA
          JSR     -$198(A6)     ;ABRE LIBRERIA
          MOVE.L  D0,DOSBASE    ;GUARDA BASE LIBRERIA
          RTS
********* RUTINA PRINCIPAL: INSERTA E A1 LA ESTRUCTURA IORequest
WRITE          MOVEA.L 4,96        ;EXEC
          SUBA.L  A1,A1       ;0 EN A1
          JSR     -$126(A6)   ;BUSCA TASK O TAREA PRESENTE
          MOVE.L  D0,TASK          ;GUARDA PUNTERO
          LEA     DISKPORT,A1 ;APUNTAR ESTRUCTURA PORT
          MOVE.L    D0,$10(A1)     ;INSERTA TASK EN DISKPORT
          JSR     -$162(A6)   ;PONER PUERTO
          LEA     IOREQ,A1    ;ESTRUCTURA IO REQUESTER
          MOVEQ   #0,D0       ;UNIDAD
          MOVEQ   #0,D1       ;BANDERAS
          LEA     DEV,A0      ;NOMBRE PERIFERICO
          LEA  DISKPORT,A2    ;INSERTA DISKPORT EN IOREQ
          MOVE.L    A2,D2          ;
          MOVE.L    D2,$0E(A1)     ;
          JSR     -$1BC(A6)   ;ABRIR PERIFERICO
          RTS
******************** PROGRAMA: LECTURA BLOQUE 880
PROG     MOVEA.L 4,A6        ;RUTINA DE LECTURA DE BLOQUES
         MOVE.W  #2,$1C(A1)  ;2=READ
         MOVE.L  #$6E000,$2C(A1)  ;OFFSET
         MOVE.L  #$200,$24(A1)    ;LONGITUD
         MOVE.L  #$60000,$28(A1)  ;POSICION DE LECTURA
         JSR     -$1C8(A6)   ;DoIO DE LECTURA
         MOVE.W  #4,$1C(A1)  ;4=UPDATE=ESCRIBIR TODO BUFFER
         JSR     -$1C8(A6)   ;DoIO
         MOVE.W  #9,$1C(A1)  ;9=NONSTD=ERROR=PARA MOTOR
         MOVE.L  #0,$24(A1)  ;LONGITUD
         JSR     -$1CB(A6)   ;DoIO
         RTS
****************** FIN PROGRAMA, COMIENZO PUNTEROS
        CNOP 0,2
DOSBASE      DC.L 0
IOREQ        DS.B $30jDISKPORT   DS.B 22
TASK    DC.L 4
        CNOP 0,2
DOSLIB       DC.B 'dos.library',0
        CNOP 0,2
DEV     DC.B 'trackdisk.device',0
        END

El programa utiliza la estructura comentada anteriormente, IORequest, encargada de efecturar las funciones primarias de lectura, grabación, etc., de bloques. Observa cómo maneja en la parte del PROGRAMA esta estructura, insertando en los offsets $1C,$2C,$24 y $28 los valores respectivos de comando, ofset del bloque, longitud y posición de memoria del buffer para la lectura o para la grabación- En cuanto a los comandos en offset $1C existen los siguientes:

1 RESET
2 LECTURA
3 GRABACION
4 VACIADO DE BUFFER
5 BORRADO DE BUFFER
6 PARADA
7 COMIENZO después de la parada
8 ABORTAR proceso parada-comienzo
9 NO ESTANDAR

Los que se utilizan generalmente son el 2, 3, 4 y 9.

En cuanto al offset del bloque hay que tener en cuenta una formula: 512 x (S + 11 x H + 11 x 2 x C) en la que S es el sector, H es la cabeza y C el cilindro. Para el caso del bloque 880 (directorio principal) el sector es el 0, la cabeza 0 y el cilindro el 40. La operación nos da el valor 450560 (hexadecimal = $6E000), para luego insertarlo en el offset correspondiente. La longitud es en realidad el número de loques a leer a partir del bloque indicado. Para un bloque son 512 bytes (= $200), dos bloques 1024 (= $400), hasta realizar una multiplicación sencilla de 512 por el número de bloques. Lo último que queda es la posición de memoria en la que se hara el proceso. En nuestro caso no hemos tenido espacio para insertar una rutina de reservar memoria, utilizando por tanto una zona un poco alta ($60000), pero recomendamos reservarla en CHIP MEMORY pues el acceso a bloques se realiza en este rango. Ahora que ya sabemos todos los parámetros para la lectura de bloque basta simplemente ejecutar la función DoIO en el offset -$1C8 de la libreria EXEC. También hemos utilizado comandos adicionales (UPDATE y NO ESTANDARD) para dar paro al motor del drive. Para realizar una grabación de un bloque el proceso es exactamente el mismo, con la única diferencia del comando, en este caso el 3, y tener preparada la zona de memoria que va a ser llevada a disco. Nada más por el momento. Espero que este capítulo le haya sacado de algunas dudas que tenía hace tiempo.


Volver menú revistas Volver página anterior