Prácticas de Periféricos

Anuncio
Prácticas de Periféricos
F. Javier Gil
Gabriel J. Garcı́a Gómez
DFISTS
Curso 2009-10
1
1.
Sobre las prácticas
Esta sección fué escrita en el momento en que decidı́ cambiar el modelo de
prácticas, que anteriormente era un modelo de enunciados abiertos. La mantengo porque las consideraciones que hice entonces siguen siendo instructivas.
1. Durante el curso pasado detecté algunas deficiencias en programación.
Estas deficiencias se han ido acentuando con el paso de los años. En el
curso pasado se rebasó, hacia abajo, el umbral que marca la diferencia
entre poder o no poder hacer las prácticas.
La causa fundamental, a mi juicio, ha sido el abandono de los lenguajes
de programación de bajo nivel en favor de lenguajes de más alto nivel.
De Pascal se pasó a C, y luego a C++ y Java. Como resultado, se
han perdido competencias de programación a bajo nivel, que en la
programación de sistemas son imprescindibles.
Algunas de las deficiencias o errores tı́picos encontrados entre los alumnos del curso pasado son los siguientes:
Desconocimiento de las operaciones de bits
Desconocimiento de la representación interna de los números. Por
ejemplo, creencia de que el número ’5’ ocupa un byte mientras que
el número ’1234’ ocupa 4 bytes.
Incapacidad para usar correctamente los punteros: declararlos,
asignarlos, usar moldeadores de tipo con ellos, uso de punteros
a punteros, manejo de memoria dinámica, punteros a funciones.
Desconocimiento de la diferencia entre números de 8 bits, tipo
de dato ’carácter’, código ASCII y figura asociada para su representación en pantalla.
Manejo deficiente de cadenas de caracteres y su relación con el
tipo ’puntero a carácter’
2. A estos problemas causados por el cambio en el paradigma de programación se une otro, y es el cambio en el paradigma del entorno de
programación. Muchos alumnos no comprenden que las herramientas
de alto nivel funcionan sobre sistemas completos. Cuando se programa
a bajo nivel, el sistema aún no existe y por tanto no existen aplicaciones sofisticadas, como depuradores de código o editores elaborados,
para ayudar en la tarea.
2
Es preciso que el alumno se familiarice con un entorno de programación
más austero. Y es preciso que el alumno se familiarice con herramientas que han sido diseñadas para funcionar bien en entornos austeros:
compiladores en lı́nea de órdenes, editores ligeros, ensamblador y desensamblador.
3. Otro problema, consecuencia de los anteriores, viene del hecho de que se
han abandonado lo que siempre fueron consideradas ’buenas prácticas’
de programación. En particular, he encontrado con inusitada frecuencia
los siguientes defectos:
Código con formato deficiente e incluso sin formato alguno
Muy deficiente factorización del código, con funciones de cientos
de lı́neas.
Desconocimiento de la existencia de distintas capas en que se organiza el código del sistema. Esto lleva, por ejemplo, a pretender
usar la función printf() dentro del controlador de pantalla.
Una actitud casi unánime de precipitación hacia el teclado, cuando
el trabajo importante se hace antes, con papel y lápiz.
Uso de abstracciones innecesarias. Por ejemplo, cuando se necesita
una pila, que no es más que un vector cuyo primer elemento se
usa como ı́ndice, casi todos los alumnos de prácticas en el curso
anterior usaron una lista doblemente enlazada, que es algo ası́ como diez veces más compleja que la representación obvia de una
pila.
4. Todo esto me obliga a replantear el contenido y objetivos de las prácticas de Periféricos, una vez constatadas estas deficiencias. Durante los
años anteriores, he usado preferentemente un pequeño sistema operativo (mı́nimo) con una arquitectura que permitı́a escribir los controladores de dispositivo en C, sin tener que recurrir al ensamblador. Este
sistema podı́a ejecutar varias tareas y permitı́a ’entrar’ en el controlador de pantalla, teclado y reloj, reasignar interrupciones y ejercitarse
con un par de métodos de acceso al hardware, como son acceso a través
de puertos y acceso a través de memoria convencional.
Una vez cruzado hacia abajo el umbral de las competencias de programación en lenguaje C 1 , es preciso dedicar al menos parte del curso
1
En ninguna caso se vea aquı́ un reproche a los alumnos: no es responsabilidad de ellos
que se abandonase primero el ensamblador, luego el lenguaje Pascal (ideal para adquirir
3
a ejercitar los conceptos y la forma de pensar para programar a bajo
nivel, y esperar, en las sesiones finales, poder escribir alguna aplicación
sencilla de sistema.
Esto tiene otra implicación práctica, y es que ya no puedo plantear un
enunciado abierto con las lı́neas generales de lo que se quiere hacer y
el resto quede al arbitrio del alumno, sino que será preciso plantear
enunciados ’cerrados’. Cada sesión consistirá en una serie de puntos
que habrá que completar. Siendo ası́, esto implica a su vez la necesidad
de realizar un examen final de prácticas, que consistirá en un ejercicio
de programación a bajo nivel sobre algún aspecto de los tratados en el
curso.
5. Con estas consideraciones presentes, sigue el contenido de cada una de
las sesiones en que se desarrollarán las prácticas.
2.
Sesión I
1. Entorno. Las prácticas se realizarán en entorno DOS. Las razones son
que existen herramientas de buena calidad para él, que está disponible bien de
forma nativa o bien mediante emulación tanto en Windows como en Linux
y que al no proteger el hardware es posible el acceso a los puertos y a la
memoria asignada a dispositivos.
2. Compilador. Se instalará el compilador ’Turbo C’ de la compañı́a Borland, que en su versión 2.01 se encuentra en el dominio público. ’Turbo C’
incluye tanto un compilador como un entorno para editar, compilar y depurar.
3. Editor. Como se ha dicho, el compilador propuesto incluye un entorno
de programación con su editor. No obstante, recomendamos el uso de un
editor externo y el uso del compilador en lı́nea de órdenes ’tcc’
4. Tareas. Las tareas a desarrollar durante esta primera sesión serán:
1. Crear un entorno de programación portable. Es decir, un directorio que
contenga las herramientas que van a usarse durante el curso y del que
buenos hábitos de programación), y después el lenguaje C para pasar a C++ y luego a
Java. Os recomendamos el artı́culo Volviendo a lo básico, cuyo enlace se encuentra en la
página del Prof. Gil
4
pueda tenerse una copia de seguridad, por ejemplo, en una llave de
memoria usb. La base del entorno puede ser:
a) El entorno DOS que ofrece Windows
b) Un emulador DOS como DOSBOX, corriendo sobre Windows.
Ésta es la opción que recomiendo.
c) Un sistema DOS auténtico con el cual arrancar el sistema desde
disco flexible
2. Descargar e instalar el compilador ’Turbo C’.
3. Descargar e instalar el ensamblador, necesario para alguna situación
concreta. Este ensamblador será ’Turbo Assembler’ también de Borland.
4. Opcional: descargar e instalar un editor ’decente’, como ’vi’ que se encuentra disponible para DOS, tanto en 16 como en 32 bits. Aunque la
elección de un editor es un asunto de gustos personales y una cuestión
para nosotros irrelevante, no está de más recordar la importancia de elegir bien una herramienta que se usará a diario, durante mucho tiempo,
en varios entornos distintos y para una gran variedad de tareas.
5. Familiarizarse con la ayuda del compilador.
6. Escribir, compilar y ejecutar algún programa sencillo con el entorno
creado. Ajustar los parámetros del editor para asegurarse de que el
código que se escribe tiene el formato deseado y de que éste formato
será el mismo durante todo el curso.
7. Guardar la instalación para la sesión siguiente.
3.
Sesión II
1. C a bajo nivel. Comenzaremos por escribir algunas funciones para manipulación de bits, necesarias para después escribir y leer de los puertos. Estos
son los prototipos:
char UTIL_caracter_consultar_bit(char *p, char n)
void UTIL_caracter_bit_a_0(char *p, char n)
void UTIL_caracter_bit_a_1(char *p, char n)
5
La primera devuelve 1 o 0 según que el bit n del carácter apuntado por
p sea 1 o 0. La segunda establece a 0 el bit n del carácter apuntado por p.
La tercera establece a 1 el bit n del carácter apuntado por p.
Tras ésto, se escribirán otras tres funciones similares que trabajen con
enteros en lugar de caracteres. Se llamarán igual, salvo el cambio de ’caracter’
por ’entero’ y tomarán como primer argumento un puntero a entero
2. La segunda parte de la práctica consiste en el uso de la macro MK FP()
para apuntar un puntero a carácter a la primera posición de la memoria de
video, ubicada en (0xb800,0) En esta posición comienza un vector de 4000
bytes = 80 columnas x 25 filas x 2 bytes por carácter.
El primer byte de un carácter es el código ascii del mismo. El segundo son
los atributos. Los cuatro bits bajos del byte de atributos codifican el color del
texto. Los bits 4, 5 y 6 codifican el color del fondo. Se escribirá un pequeño
programa que escriba directamente en memoria de video un rectángulo de 16
filas y 8 columnas un carácter cualquiera escrito con todas las combinaciones
posibles de texto y fondo.
4.
Sesión III
1. Punteros en la programación de sistemas Las tareas a desarrollar durante esta sesión serán:
1. declarar punteros a un carácter, a un entero y a un entero largo
2. reservar memoria para ellos
3. imprimir los tamaños de los tres punteros
4. imprimir los propios punteros (no el contenido del lugar al que apuntan). Aquı́ puede ser útil el formato %p de printf().
5. sumar 1 a cada puntero y repetir el paso anterior. Interpretar qué ha
ocurrido
6. declarar un vector de 100 caracteres
7. escribir un carácter A en la posición 23
8. escribir el entero 1004 en las posiciones 45-46
9. leer el carácter de la posición 23 y el entero de las posiciones 45-46
6
10. declarar un puntero a un vector de enteros y comprobar su uso
11. declarar un vector de punteros a entero, y comprobar su uso
12. declarar un puntero a un puntero a carácter, reservar memoria para él
y escribir en el lugar apuntado un puntero a la pantalla de vı́deo
13. declarar un puntero a una función que devuelve un entero
14. declarar un vector de punteros a funciones que devuelven un entero
5.
Sesión IV
1. Acceso a la ROM para obtener los patrones de bits de los caracteres.
En esta sesión pondremos en práctica algunos de los conceptos vistos en las
sesiones anteriores explorando uno de los aspectos de la memoria de vı́deo.
Como sabemos, la memoria de vı́deo VGA en modo 80x25, color, comienza
en la direccin (0xb800,0), o lo que es igual, en la dirección fı́sica 0xb8000000.
Es un vector de 80x25x2=4000 caracteres agrupados de parejas (carácteratributo). Nos centraremos en el código que escribimos para el carácter y
cómo se transforma en un patrón de bits que en pantalla representa la figura
del carácter. Por ejemplo, al escribir A en memoria de vı́deo no escribimos el
carácter A en realidad, sino el valor 65.
Este valor indexa una tabla que se encuentra en ROM y que contiene el
((dibujo)) de la letra A. Cada dibujo consta de 16 bytes, y como cada byte
consta de 8 bits, tenemos una matriz de 16 filas x 8 columnas. El patrón de
((unos)) representa el dibujo del carácter. Por ejemplo, el carácter = se codifica
ası́:
00000000
00000000
00000000
00000000
00000000
00000000
00000000
01111110
01111110
00000000
00000000
01111110
01111110
->
->
->
->
->
->
->
->
->
->
->
->
->
0
0
0
0
0
0
0
126
126
0
0
126
126
7
00000000
00000000
00000000
->
->
->
0
0
0
La práctica consistirá en acceder a la tabla donde están los patrones de
los caracteres, leerlos y volcarlos a un archivo de texto donde puedan ser consultados. Para visualizar mejor los patrones, los ceros pueden representarse
mediante espacio en blanco, y los unos mediante algún carácter ((lleno)), como
por ejemplo #.
2. Acceso a la tabla. Existe una función de la BIOS que ofrece la dirección de la tabla. Las funciones de la BIOS se invocan de la siguiente forma:
unos valores convenidos se escriben en los registros de la CPU, se llama a la
función adecuada y ésta deja el resultado en los registros de la cpu. Nuestro
compilador ofrece algunas maneras de hacer esto. La más sencilla es:
#include <dos.h>
struct REGPACK rp ;
rp.r_ax=0x1130;
rp.r_bx=0x0600;
intr(0x10,&rp);
REGPACK es una estructura predefinida cuyos miembros son variables que
hacen las veces de pseudoregistros. El compilador generará el código preciso
para
1. salvar los registros de la cpu
2. tomar los valores de los pseudoregistros y colocarlos en los registros
verdaderos
3. llamar a la función correspondiente de la BIOS, cuyo resultado queda
almacenado igualmente en los registros de la cpu
4. copiar los registros verdaderos en los pseudoregistros
Consultar la ayuda del compilador para ver qué contiene struct REGPACK.
En nuestro caso, a las funciones de la BIOS de vı́deo se accede a través de la
interrupción 0x10.
8
IMPORTANTE: estas no son interrupciones hardware, sino interrupciones software. El nombre de ((interrupción)) con que se las
designa es muy desafortunado. Pensemos en ellas simplemente como funciones pre-escritas en la ROM.
Como resultado de la interrupcin, el segmento y el desplazamiento donde se
encuentra la tabla se depositan en los registros ES,BP. Por tanto, declaramos
un puntero a carácter y lo hacemos apuntar a la dirección indicada:
char *p=(char *)MK_FP(rp.r_es,rp.r_bp);
La tabla contiene 256 caracteres x 16 bytes/caracter = 4096 bytes = 4k
Usando la función que se escribió hace dos sesiones para consultar un bit
de un byte, generar para cada carácter su patrón de ceros y unos usando los
caracteres ’ ’ y ’#’ y escribir ese patrón en un archivo de disco que pueda
después ser consultado mediante un editor cualquiera. La legibilidad mejora
si se escribe el propio carácter antes del patrón y se deja una lı́nea en blanco
entre patrón y patrón. Por ejemplo
A
####
## ##
## ##
## ##
## ##
## ##
######
######
## ##
## ##
## ##
## ##
## ##
## ##
B
....
9
3. Modificar la tabla. La tabla en ROM puede copiarse a RAM, ((decirle)) a
la tarjeta la dirección de la copia y, puesto que se encuentra en RAM, poder
modificarla. Es la forma de activar juegos de caracteres nuevos. Una forma
sencilla de comprobar el cambio es alterando algún carácter. Por ejemplo, si
p es un puntero que apunta a la nueva tabla, alteramos el patrón de bits de
la letra A colocándole una barra horizontal en la parte superior. Ası́:
*(p+65*16)=255
El código que establece la nueva tabla de caracteres es el siguiente:
/* activar nuevo juego de caracteres */
reg.r_ax=0x1110;
reg.r_bx=(16<<8)|0;
reg.r_cx=256;
reg.r_dx=0;
reg.r_es=FP_SEG(*p);
reg.r_bp=FP_OFF(*p);
intr(0x10,&reg);
Ahora bien, por alguna razón (antigüedad del compilador, emulación del
DOS, algún cambio en la BIOS...) el código anterior no funciona correctamente, por lo que es preciso llamar a la interrupción directamente, insertando
el fragmento en ensamblador siguiente:
/* activar nuevo juego de caracteres, usando ensamblador */
asm push ax ;
asm push bx ;
asm push cx ;
asm push dx ;
asm push es ;
asm push bp ;
asm mov ax, 1110h ;
asm mov bx, 1000h ;
asm mov cx, 0100h ;
asm mov dx, 0 ;
asm les bp, p ;
asm int 10h ;
asm pop bp ;
asm pop es ;
asm pop dx ;
asm pop cx ;
asm pop bx ;
asm pop ax ;
10
6.
Sesión V
1. Continuando con las llamadas a funciones de la BIOS, en esta práctica
usaremos la interfaz ya conocida para realizar algunas tareas:
1. Alteración del tiempo de espera y ritmo de repetición del teclado. Interrupción 0x16
Entrada:
ah=0x03
al=0x05
bh=retardo
bl=factor de repeticion
Las tablas de valores para bh y bl se encuentran en las transparencias
de clase
2. Comprobacio’n del estado del teclado
Entrada:
ah=0x02
Salida:
al=estado del teclado
bit
0
1
2
3
4
5
6
7
1
1
1
1
1
1
1
1
->
->
->
->
->
->
->
->
tecla derecha may. pulsada
tecla izquierda may. pulsada
tecla control pulsada
tecla alt pulsada
bloq. desp. pulsado
bloq. num. pulsado
bloq. may. pulsado
insert pulsado
Puede escribirse un pequeño bucle que llame ininterrumpidamente a la
interrupción y muestre continuamente el estado del teclado en pantalla
3. Fecha y hora: lectura del reloj de tiempo real. Interrupción 0x1a
11
Entrada:
ah=0x02
Salida:
ch=hora
cl=minuto
dh=segundo
Se tendrá en cuenta que los valores para las horas, minutos y segundos
está codificados en formato BCD. Por ejemplo, si las 16 horas se codifican usando los cuatro bits bajos de ch para representar el ’6’ y los
cuatro bits altos para representar el ’1’.
4. Fijar modo de vı́deo. Interrupción 0x10
Entrada:
ah=0x00
al= 0x01 40*25, 16 colores
0x03 80*25, 16 colores
0x05 gra’fico, 320*200 pixels, 4 colores
0x13 gra’fico, 320*200, 256 colores
...etc.
El modo 0x13 es especialmente interesante. La resolución no es muy
alta, pero con 256 colores pudieron en su dı́a escribirse muchos buenos
juegos. Algunos de ellos se han portado para jugarse en los móviles.
Además, 320*200*1 byte/pixel = 64000 bytes. Es decir, la pantalla
cabe ı́ntegra en un segmento, y puede accederse a cualquier pixel simplemente sumando un desplazamiento al puntero que apunta al primer
pixel. La dirección de la pantalla de vı́deo en este modo es 0xa000.
Cada lı́nea consta de 320 pixels = 320 bytes luego para acceder al pixel
que está en la fila f columna c hay que sumar al puntero un desplazamiento 320*f+c. Escribir una función que active un pixel y que tome
como argumentos la fila, la columna y el color que se desee.
7.
Sesión VI
1. Acceso a la tabla de vectores de interrupción. Se leerán los valores
depositados en dicha tabla, y se aprenderá a modificarlos de forma que los
12
eventos de hardware provoquen la ejecución de rutinas escritas por el propio
alumno.
Tareas a desarrollar durante esta sesión:
1. Acceder mediante punteros a la TVI. Se usará MK FP para acceder a
dicha tabla.
2. Uso de cli y sti: necesidad del lenguaje ensamblador e interfaz ofrecida por ’Turbo C’
3. Escritura de una pequeña RSI propia que acceda mediante inportb()
al puerto de teclado y lea los códigos de las teclas.
4. Modificación de la entrada correspondiente al teclado en la TVI para
que apunte a la rutina propia.
5. Restauración de la rutina original
8.
Sesiones VII-IX
1. Objeto. El objeto de estas tres sesiones será el de escribir una versión de
la orden dir, que muestre en pantalla los archivos y directorios de un disco,
junto con información relevante como tamaño, fecha y atributos.
Por seguridad, trabajaremos con discos flexibles de 1.44MB. Ahora bien,
dado que muchos trabajáis ya con portátiles que carecen de unidad de disco
flexible, podrı́amos trabajar con alguna de las alternativas siguientes:
trabajar en los ordenadores del laboratorio, usando las unidades de
disco de estos ordenadores
trabajar mediante emulador (vmware, dosbox, etc) montando como
unidad de disco flexible una imagen
trabajar desde windows o linux con una imagen, a la que se accede
abriéndola como un archivo cualquiera
trabajar en DOS pero usando un disco RAM, que se creará en el arranque. Requiere el programa RAMDRIVE
Por simplicidad, usaremos una imagen de un disco flexible de 1.44 MB.
Una imagen no es más que un archivo de disco que se abre con fopen() y
se cierra con fclose(). El archivo se ha generado leyendo uno por uno los
sectores de 512 bytes del disco original y escribiéndolos uno a continuación de
13
otro en el archivo. Por tanto, la imagen se considera como un conjunto consecutivo de bloques de 512 bytes. Por consiguiente, leer un sector n equivale a
desplazarse 512 × n a partir del origen del archivo mediante lseek() y hacer
una lectura de un 512 bytes mediante fread(). Recomendamos que hagáis
la práctica sobre Linux, con el compilador gcc, ya que en años anteriores tcc
ha dado algunos problemas relacionados con la gestión del buffer intermedio
que usa fread(). Pero recuérdese que gcc es un compilador de 32 bytes (1
entero=4 bytes) mientras que tcc es un compilador de 16 bytes (1 entero=2
bytes).
2. Fundamentos. Un disco es una colección de sectores. En el caso de los
discos flexibles de 1.44MB se trata de una colección de 2880 sectores de 512
bytes.
De este conjunto de sectores se reserva un espacio para implementar en
él una base de datos de sectores, que indique qué sectores se encuentran
libres u ocupados y qué sectores pertenecen a qué archivos (o a la inversa,
de qué sectores consta un archivo dado).
A esta base de datos es a lo que se conoce como ((sistema de archivos)).
Hay muchas formas de implementarla: fat12, fat16, fat32, ext2-3, reiserfs,
minix, vfat, ntfs, ufs, iso ...
La elección influye de forma determinante en el rendimiento de las operaciones de acceso a disco, aunque después haya otros factores relevantes
que tiendan a difuminar las diferencias, como por ejemplo la existencia de
grandes cachés, tanto en la propia unidad de disco como en el propio sistema
operativo.
3. FAT. El sistema FAT, en sus distintas versiones, es una de las formas
de implementar la base de datos de sectores. FAT proviene de File Allocation
Table, es decir, tabla de localización de ficheros.
La idea consiste en tener una lista enlazada por cada archivo. Si el disco
tiene N sectores (o bien agrupaciones de un número fijo de sectores que llamamos ((bloques))), entonces la lista contendrá N elementos. Como el número
de elementos es fijo, se pueden almacenar en un vector. Cada elemento del
vector contiene un número. Si el número es de x bits, entonces tenemos una
((FAT x)).
Por ejemplo, en referencia a la figura, supongamos que un determinado
archivo ocupa los sectores 1,2,6,9. Entonces, la FAT tiene el aspecto que se
muestra:
0
1
2
3
4
5
6
7
8
9
10 11 12 13 ...
--------------------------------------------------------------14
|
| 2 | 6 |
| -1|
| 9 |
|
| 0 |
|
|
|
| ...
--------------------------------------------------------------El primer sector del archivo es el número 1. Entonces, en la entrada 1
de la FAT se escribe el número del siguiente sector ocupado por el archivo,
en este caso el 2. En la entrada 2 se escribe el número del siguiente sector
ocupado, que es el 6. En la entrada 6, el número del siguiente sector, que
es el 9. Como el 9 es el último sector, en esa entrada escribimos un número
especial, el 0 u otro que se convenga, que indica el final.
Los espacios en blanco en la figura pueden estar ocupados por las listas
que correspondan a otros archivos distintos. Por otro lado, un número especial
convenido (por ejemplo el -1) puede indicar que el sector correspondiente a
esa entrada está libre. Por ejemplo, el sector 4 de nuestra figura está libre, y
puede asignarse a un archivo nuevo que se cree o a uno ya existente si crece
su tamaño.
Surge la pregunta ¿cómo sabemos cuál es el primer sector de un archivo
dado? En el ejemplo anterior, ¿cómo sabemos que el primer sector ocupado
por el archivo es el número 1? O dicho de otra forma ¿cómo sabemos dónde
empieza la lista para un archivo dado?
Lo sabemos porque aparte del espacio reservado para la FAT ha de haber
otro espacio reservado, donde se escriba, esencialmente, el nombre de cada
archivo y el primer sector que ocupa. Entonces, dado un nombre, se busca
en este espacio (que se llama ((directorio raı́z))) y allı́ se consulta cual es ese
primer sector.
Al conjunto de los distintos espacios contenidos en un disco es a lo que
se llama ((volumen)). Los datos que guardamos en un disco ocupan sólo una
parte del volumen. El resto del espacio, se emplea en ((apuntar)) dónde han
ido a parar los datos.
4. FAT 12. Vayamos a los detalles del sistema FAT-12, que es el usado por
MS-DOS en discos de 1.44MB. Un disco de esta capacidad es un ((volumen)),
y ese volumen se organiza en varias partes:
sector de arranque
FAT
una o más copias de la FAT
directorio raı́z
datos
15
La tabla siguiente indica qué rango de sectores lógicos ocupa cada sección.
Los sectores lógicos se indexan desde 0:
Sector o rango
0
1-9
10-18
19-32
33-2879
Contenido
Sector de arranque
FAT
Copia de la FAT (por seguridad)
Directorio raı́z
Datos
4.1. Sector de arranque. Es el primer sector del disco (número 0). De entre
los 512 bytes estos campos son relevantes:
Desplazamiento
0x0B
0x0D
0x0E
0x10
0x11
0x13
0x15
0x16
0x18
0x1A
Contenido
bytes por sector
sectores por bloque
sectores reservados
número de ejemplares de FAT
entradas del D.R.
número sectores del volumen
código tipo de disco
número de sectores por FAT
sectores por pista
cabezales
Tamaño
2
1
2
1
2
2
1
2
2
2
Dividiendo el número total de sectores (0x13) entre el número de sectores
por bloque se tiene el número de unidades de asignación, y de ahı́ puede
deducirse el número de bits usados en la FAT. En nuestro caso ya sabemos
que son 12.
4.2. FAT. Las dos primeras entradas (=24 bits = 3 bytes) están reservadas.
El resto de entradas pueden contener los siguientes valores (para FAT-12):
0x000
0xff0-0xff6
0xff7
0xff8-0xfff
otro
libre
reservado sistema
bloque defectuoso
último bloque de un archivo
siguiente bloque de un archivo
La FAT obviamente indica sectores de datos. Como los sectores 0 al 32
no contienen datos, la entrada 0 de la FAT indicarı́a el sector lógico 33. Pero,
16
como las dos primeras entradas de la FAT están reservadas, es la tercera
entrada de la FAT (número 2) la que hace referencia al sector 33. Por consiguiente, la entrada X de la FAT hace referencia al sector lógico S=X+33-2.
Ası́, la entrada 2 al sector lógico 33, la 3 al 34 y sucesivamente.
4.3. Directorio raı́z. Está formado por entradas de 32 bytes, en número predeterminado (posición 0x11 del sector de arranque). Por este motivo, pudiera
darse el caso de que aún quedando espacio libre en disco un archivo no pudiese crearse, al encontrarse lleno el directorio raı́z. Ésta es la estructura de
cada entrada del D.R.:
Desplazamiento
0x00
0x08
0x0B
0x0C
0x0E
0x10
0x12
0x16
0x18
0x1A
0x1C
Contenido
Nombre del archivo
Extensión del archivo
Atributos del archivo
Reservado
Hora de creación
Fecha de creación
Fecha del último acceso
Hora última modificación
Fecha última modificiación
Primer bloque del archivo
Tamaño del archivo (bytes)
Tamaño
8
3
1
2
2
2
2
2
2
2
4
El primer byte del nombre puede tener algunos valores especiales, indicando:
Valor
0x00
0x2E
0xE5
Indica
última entrada del directorio
archivo se refiere al directorio actual
archivo se borró
En el byte de atributos del archivo, cada bit tiene el siguiente significado:
bit
0
1
2
3
4
5
6
7
significa
sólo lectura
oculto
archivo de sistema
etiqueta de volumen
subdirectorio
bit de backup
sin usar
sin usar
17
La palabra de hora tiene la siguiente estructura
bit o rango
0-4
5-10
11-15
significa
segundos/2
minuto
hora
Y para la palabra de fecha
bit o rango
0-4
5-8
9-15
significa
dı́a del mes
mes
año, relativo a 1980
4.4. Explicación de los subdirectorios. El bit 4 del byte de atributos indica
que la entrada corresponde a un subdirectorio. El tamaño se establece siempre
a cero. Las entradas de un subdirectorio tienen la misma estructura que las
entradas del directorio raı́z, es decir, entradas de 32 bytes. Un directorio se
trata como un archivo normal, y por tanto en 0x1A se encuentra el número
del primer bloque que contiene al subdirectorio. Si los bloques son de un
sector, es decir, 512 bytes, tenemos 16 entradas por subdirectorio. Cuando se
agotan, se añaden nuevos sectores, de la misma forma a como se aumentarı́a
el tamaño de un archivo cualquiera. Cuando se crea un subdirectorio, se crean
automáticamente dos entradas, la entrada ((.)), que apunta a su mismo bloque
y la entrada ((..)), que apunta al subdirectorio padre.
4.5. Funciones relevantes. La función 0x02 de la interrupción 0x13 de la
BIOS se usa para leer sectores de la unidad de disco flexible, y colocar el
resultado de la lectura en un buffer intermedio.
Entradas
........
ah=0x02
dl=nu’mero de unidad
dh=nu’mero de cara
ch=nu’mero de pista
cl=nu’mero de sector
al=nu’mero de sectores a leer
es:bx= puntero lejano al buffer
Salida
18
......
acarreo=0; en este caso ah=0 y al=nu’mero de sectores lei’dos
acarreo=1; en este caso ah=co’digo de error
Una unidad de disco flexible de 1.44MB tiene 2880 sectores lógicos, distribuidos en las dos caras. En cada cara hay 80 pistas, y en cada pista 18
sectores. Ası́, dado un número de sector lógico, es preciso encontrar la cara,
pista y sector en que se encuentra antes de hacer la llamada. Lo dejo como
ejercicio (sencillo). Téngase en cuenta que la BIOS numera las caras desde
0, las pistas también desde 0 pero los sectores dentro de una pista desde 1.
Si se va a trabajar con una imagen de un disco, abriéndola como un archivo ordinario, se requieren las funciones fopen(), fread() y fseek(); consúltese
la bibliografı́a o el manual. Es importante desactivar todo buffer intermedio para las operaciones de lectura mediante la función setbuf(). También
es importante abrir el archivo en modo binario.
4.6. Interfaz. Con objeto de simplificar la práctica, la interfaz de la orden
dir será sencilla: No tomará argumento alguno, sino que mostrará en pantalla
el contenido completo del disco, marcando de alguna forma los directorios con
objeto de separar visualmente los archivos pertenecientes a un directorio de
los pertenecientes a otro.
4.7. Tres observaciones finales. Trabajamos con entradas de 12 bits. Esto
implica que en la FAT hay dos entradas por cada 3 bytes, o lo que es lo
mismo, que cada entrada ocupa un byte y medio. No es preciso leer la FAT si
lo que se desea es sólo hacer dir del directorio raı́z, pero sı́ que hay que leerla
si se quieren listar los subdirectorios. Por tanto, pensad bien cómo localizar
la entrada x en la FAT y cómo leerla. Ya que las entradas son de 12 bits,
cada dos entradas ocupan 3 bytes. Si representamos con dı́gitos o letras cada
bit de los tres bytes, tal y como muestra la figura:
76543210
abcdefgh
xyztuvwp
la primera entrada se forma tomando los cuatro bits bajos del byte intermedio y usándolos como bits altos del primer byte: efgh76543210. La
segunda entrada se forma tomando los cuatro bits altos del byte intermedio
y usándolos como bits bajos del tercer byte: xyztuvwpabcd.
Por otro lado, la estructura de un subdirectorio es igual que la estructura
del directorio raı́z. Seguir la pista a los subdirectorios, no importa hasta
qué profundidad, es entonces sencillo si se hace recursivamente.
19
Para terminar: las funciones que escribáis para esta práctica son todo lo
que necesitáis para hacer otras operaciones, que resultarán casi ((gratis)). Por
ejemplo, listar los sectores que ocupa un archivo concreto, buscar un archivo,
calcular el espacio libre que queda en el disco o calcular el tamaño total de
los archivos contenidos en un subdirectorio.
Desfragmentar el disco o comprobar su consistencia lleva algo más de
trabajo.
4.8. Ejemplo de lectura del primer sector en C e interpretación de sus
contenidos.
/*
lectura del primer sector de un disco fat-12
para leer sus campos. Compilador tcc 2.01
*/
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
/* fabricante y version */
void fab_y_ver(char *buffer){
int j;
char *p=buffer+0x03;
printf("\nFabricante y version: ");
for(j=0;j<8;++j) printf("%c",*(p+j));
printf("\n");
return;
}
/* bytes por sector */
int bps(char *buffer){
int *p=(int *)(buffer+0x0b);
return(*p);
}
/* sectores por pista */
int spb(char *buffer){
char *p=buffer+0x0d;
return((int)(*p));
}
20
/* sectores reservados */
int sr(char *buffer){
char *p=buffer+0x0e;
return((int)(*p));
}
/* copias de la fat */
int copias_fat(char *buffer){
char *p=buffer+0x10;
return((int)(*p));
}
/* entradas del directorio raiz */
int entradas_dr(char *buffer){
int *p=(int *)(buffer+0x11);
return(*p);
}
/* numero de sectores */
int numero_sectores(char *buffer){
int *p=(int *)(buffer+0x13);
return(*p);
}
/* tipo de disco */
int tipo_disco(char *buffer){
unsigned char *p=buffer+0x15;
return((int)(*p));
}
/* sectores ocupados por las fat */
int sectores_fat(char *buffer){
int *p=(int *)(buffer+0x16);
return(*p);
}
/* sectores por pista */
int sectores_pista(char *buffer){
int *p=(int *)(buffer+0x18);
return(*p);
}
21
/* numero de cabezales */
int cabezales(char *buffer){
int *p=(int *)(buffer+0x1a);
return(*p);
}
main()
{
char buffer[512];
char *p;
int *n;
int fd=open("floppy.img",O_RDONLY,0);
lseek(fd,0L,0);
read(fd,buffer,512);
fab_y_ver(buffer);
printf("
bytes por sector: %4d\n",bps(buffer));
printf(" sectores por bloque: %4d\n",spb(buffer));
printf(" sectores reservados: %4d\n",sr(buffer));
printf("
copias de la FAT: %4d\n",copias_fat(buffer));
printf("
entradas del d.r.: %4d\n"\’entradas_dr(buffer));
printf("
total de sectores: %4d\n"\~numero_sectores(buffer));
printf("codigo tipo de disco: %4d\n",tipo_disco(buffer));
printf("
sectores por fat: %4d\n",sectores_fat(buffer));
printf(" sectores por pista: %4d\n",sectores_pista(buffer));
printf(" cabezales: %4d\n",cabezales(buffer));
close(fd);
}
9.
Sesión X
Esta sesión se plantea como una aplicación trivial de las funciones de lectura de la FAT desarrolladas en la práctica anterior. Se trata de representar
en pantalla el estado de todos los sectores de datos del disco. Por ejemplo,
22
cada sector se puede representar mediante un bloque sólido de un color distinto: rojo para los defectuosos, amarillo para los ocupados y verde para los
libres.
El único problema que puede presentarse es que en modo texto de 80 columnas y 25 filas disponemos de sólo 2000 caracteres, que no son suficientes para
todo el disco. Hay cuatro soluciones, y podéis elegir la que más os convenga:
1) pasar a un modo texto de mayor resolución usando la función adecuada
de la BIOS; b) Usar el modo de 80 × 25 habilitando algún mecanismo de
desplazamiento que permita ver el disco completo; c) Pasar a modo gráfico
de 320 × 200 y representar cada sector con un pixel, o un cuadradito de cuatro pixels, o como se crea conveniente; d) En lugar de escribir en pantalla,
escribir lı́neas a un archivo de disco, que luego puede editarse.
10.
Sesión XI
En la sección 12.7.2 de los apuntes se encuentra una descripción de cómo
se codifican los colores en el modo texto de la VGA. En particular, cómo el
byte de atributos indexa la tabla DAC cuyas entradas a su vez apuntan a los
registros de paleta que contienen las componentes de rojo, verde y azul de
cada atributo. También se da una pequeña librerı́a para acceder y cambiar
estos valores.
Una vez leı́da y entendida esa sección, se escribirá el siguiente pequeño
programa: Dibújese un rectángulo sólido en el centro de la pantalla. Por
ejemplo, de 8 × 8 caracteres, en el color que se desee. Resérvense tres pares
de teclas. Cada pareja de teclas, sirve para modificar una de las componentes
de color del atributo elegido para el bloque (rojo, verde, azul). Una tecla de
la pareja incrementa el valor, la otra lo decrementa.
Accionando las teclas se variarán interactivamente las componentes, y se
verá cómo el color del recuadro en pantalla varı́a de forma continua.
11.
Sesión XII
Esta práctica consiste en unos pocos ejercicios en lenguaje PostScript. En
toda distribución Linux se incluye un intérprete de este lenguaje llamado
Ghostscript. El intérprete se presenta como un terminal donde se teclean
23
instrucciones y una ventana que representa la salida del programa. Los ejercicios propuestos son los siguientes:
1. Escribir una función que tome como parámetro un número n y dibuje un polı́gono regular de n lados inscrito en una circunferencia. Se
pasarán también como parámetros las coordenadas del centro de la
circunferencia y su radio.
2. Escribir un pequeño programa que genere una hoja de papel milimetrado. Una hoja de papel milimetrado tiene divisiones de 1 cm., medio
centı́metro y 1 mm, con grosores distintos: menor para las divisiones de
milı́metro y mayor para las de un centı́metro, siendo intermedio para
las de medio.
3. Escribir un pequeño programa que genere una hoja de papel polar, o
semilogarı́tmico (a elección).
4. Escribir un programa en C que lea un archivo de texto y genere un
programa PostScript. El archivo texto contendrá dos columnas, con
parejas de valores (x, y). El programa C leerá esos valores y los introducirá en una matriz (hará una primera pasada para conocer el número
de lı́neas en el archivo, y por tanto las dimensiones de la matriz que
ha de usar). A continuación, escribirá en un archivo de texto un programa PostScript. Ese programa PostScript dibujará un recuadro en la
página, cuya esquina inferior izquierda tendrá coordenadas (6, 11), en
centı́metros, y cuya esquina superior derecha tendrá coordenas (15, 17),
en centı́metros. Una vez hecho esto, dibujará la gráfica a partir del conjunto de valores (x, y), uniendo cada punto con el siguiente por medio
de una lı́nea. Se trata esencialmente de hacer una transformación lineal
que lleve del rango (xmin , xmax ) al rango (6, 15) (para las coordenadas
x) y del rango (ymin , ymax ) al rango (11, 17) (para las coordenas y).
24
Descargar