Universidad de Las Palmas de Gran Canaria

Click here for Picture


Sistemas Operativos

Prácticas sobre sistemas de ficheros

Escuela Universitaria de Informática

E

ste documento define la primera fase de las prácticas de Sistemas Operativos sobre implementación de módulos funcionales de un sistema operativo.

Índice

1. Condiciones generales

1.1. Tareas de los alumnos

La práctica asignada consiste en la implementación de un componente funcional, según se describe en este documento. La implementación se llevará a cabo en C o en C++, cumpliendo estrictamente las interfaces propuestas.

El trabajo se llevará a cabo en dos etapas. En la primera se realizará un diseño preliminar de los algoritmos y estructuras de datos que se van a utilizar. Este diseño será sometido a revisión por el profesor tutor de la práctica, que dará su aprobación para la implementación definitiva, que será la segunda etapa.

La implementación habrá de seguir el método y los criterios de calidad expuestos en la práctica sobre programación modular. Se diseñarán ficheros de interfaz y de implementación, y se crearán los makefiles apropiados.

Una vez culminada la implementación, se elaborará un breve informe/memoria del trabajo realizado, que será entregado al profesor tutor para su evaluación.

1.2. Guía de lectura

Este documento no ha de leerse por completo. Cada práctica necesita de la documentación parcial de un número de capítulos. El grado de profundidad con la que ha de verse un capítulo depende también del tipo de práctica asignada. Así, el desarrollador de la práctica de Sistema de Ficheros Simbólico ha de leer el capítulo sobre el S.F. Básico, pero sólo para conocer la interfaz de funcionamiento.

En cualquier caso, es necesaria la lectura del capítulo 2 (Descripción general del sistema), donde se describe el sistema en general, las características comunes, y algunos detalles relacionados con el desarrollo en el laboratorio.

Esta es la documentación específica de cada práctica:

Simulación de disco            capítulo 3(Simulación de un disco)               
Caché de disco                 capítulos 4 (Sistema de Bloques Físicos) y 5     
                               (Caché de disco)                                 
Sistema de ficheros básico     capítulos 4 (Sistema de Bloques Físicos) y 6     
                               (Sistema de ficheros básico)                     
Sistema de ficheros simbólico  capítulos 4 (Sistema de Bloques Físicos), 6      
                               (Sistema de ficheros básico) y 7 (Sistema de     
                               ficheros simbólico)                              

2. Descripción general del sistema

Las cuatro prácticas que se proponen tienen por objeto la construcción de componentes de un sistema de ficheros. El sistema se ha estructurado en estas capas de software:

Sistema de Disco

Su misión consiste en proporcionar un archivo, manejado de manera análoga a como se hace con las unidades de disco, donde almacenar y posteriormente recuperar al información contenida en los archivos. La implementación de este sistema constituye la primera práctica.

Sistema de Bloques Físicos (SBF)

Su misión es principalmente ofrecer una interfaz simple de acceso al disco. Aparte, puede contener una caché de bloques para agilizar los accesos a los bloques físicos del sistema de disco. El desarrollo de la caché constituye la segunda práctica.

Sistemas de Ficheros

Este nivel es el más complejo, puesto que se pasa de trabajar con una secuencia de bloques a trabajar con ficheros. Debido a esta complejidad se ha descompuesto a su vez en dos niveles: Sistema de Ficheros Básico (SFB) y Sistema de Ficheros Simbólico (SFS), de alto nivel). En el SFB se resuelven los problemas de mantaner una organización simple de ficheros dentro del disco y gestionar el espacio libre. El SFE se encarga de trabajar con ficheros que tienen nombre simbólico y a los que se puede acceder byte por byte.

La tercera y cuarta prácticas consisten en la implementación de estos dos subsistemas.

La única práctica realmente de simulación es la primera; las tres restantes serían desarrollos de software similares a los correspondientes módulos de un sistema operativo real. Eso sí, serán módulos muy simples y con escasas prestaciones comparados con los sistemas reales.

2.1. Las distintas organizaciones de los datos

El objeto de este apartado es describir cómo son "vistos", y por lo tanto manejados, los datos en cada práctica (subsistema), sin olvidar que nuestro sistema va a ser construido y ejecutado sobre un sistema UNIX de desarrollo; por tanto, cualquier petición de acceso a los datos perteneciente a los archivos del sistema diseñado finalmente desembocará en la ejecución de una función del sistema UNIX.

2.1.1. El Sistema de Disco

Como ya se ha mencionado, éste simula una unidad de disco genérica mediante un fichero de nuestro sistema de desarrollo, por tanto en la implementación de este módulo, se deberá hacer uso de las primitivas de manejo de ficheros de bajo nivel de UNIX (open, creat, read, write, lseek y close). A partir de estas primitivas se desarrollarán unas funciones que accederán a los datos del fichero UNIX de simulación como si éste fuese una unidad de disco. Es decir, partiendo de una especificación en forma de superficie, cilindro y sector (proveniente de los niveles superiores del sistema a implementar y que conceptualmente está asociado a un sector del disco simulado) las convertirán en una dirección de bajo nivel para acceder a datos en un fichero UNIX en forma de desplazamiento del origen del fichero y número de bytes para transferir (este campo siempre tomará el mismo valor y será igual al tamaño de un sector del disco simulado).

2.1.2. El Sistema de Bloques Físicos

A este nivel, los datos se organizan en forma de bloques de tamaño fijo e igual al tamaño de sector del disco simulado por el nivel anterior. La diferencia radica en que los bloques son identificados por un valor entero comprendido entre 0 y el número total de sectores que posee el disco simulado menos 1. A este bloque de datos lo llamaremos bloque físico.

Si se utiliza una caché de bloques, las operaciones de lectura y escritura de bloques no siempre se corresponden con accesos al disco simulado.

2.1.3. El Sistema de Ficheros Básico

Este nivel ve los datos almacenados en el disco virtual, como conjuntos de bloques de tamaño fijo (bloque de sistema de ficheros o bloque de datos) pertenecientes a entidades (ficheros) que poseen cada una un identificador numérico único. Los bloques de datos poseen un tamaño que será múltiplo exacto del tamaño del bloque físico.

2.1.4. El Sistema de Ficheros Simbólico

Este nivel ve los datos como un conjunto de ficheros, cuyos nombres serán cadenas de caracteres. Los datos de cada fichero son secuencias de bytes, con lo que se pierde la conciencia de los bloques. En este nivel se tienen que traducir peticiones de acceso a bytes de un fichero, a operaciones de acceso a bloques completos.

2.2. El fichero de simulación

Como ya se ha mencionado, nuestro sistema, mediante el Sistema de Disco, hace uso de un fichero regular UNIX para simular una unidad de disco de unas características dadas. En el nivel más alto de abstracción, tenemos al Subsistema de Ficheros, para el cual este fichero UNIX contiene un conjunto de estructuras de datos (ficheros, tablas descriptoras, mapas de bits, etc.) El nivel intermedio, el Sistema de bloques físicos, asumirá que el fichero de simulación está organizado como una secuencia de registros de tamaño fijo.

La pregunta que surge a continuación es ¿Cómo se compatibilizan estas distintas visiones? y la respuesta es: de la misma manera que ocurre en los sistemas operativos reales y esto consiste en:

Al Sistema de Ficheros le llegan especificaciones simbólicas de usuario asociadas a:

Sistema de Ficheros que serán convertidas a especificaciones numéricas de sistemas de ficheros. En nuestro sistema cada sistema de ficheros se identificará por el nombre del fichero que simula a un disco y en el está almacenado, cuando se activa el sistema de ficheros (se avisa al sistema que va a ser accedido) entonces se abre dicho fichero y el descriptor de fichero devuelto pasará a ser el identificador numérico asociado al sistema de ficheros. Dicho descriptor de fichero será pasado como parámetro a las capas inferiores del sistema.

Ficheros en forma de cadena de caracteres que será convertida a identificador numérico asociado a la entrada de directorio que contiene la información de control del archivo.

Cadena de bytes en forma de posición de comienzo dentro del fichero y longitud, que será transformada a una especificación de bloque del sistema de ficheros básico y desplazamiento dentro del bloque, en primera instancia por parte del Sistema de Ficheros Simbólico o Básico, y posteriormente es transformado por parte del Sistema de Bloques Físicos a sector lógico y desplazamiento dentro del sector.

Al Sistema de Bloques Físicos le llegan especificaciones de sector lógico, provenientes del Sistema de Ficheros Básico. En el caso de que el sector especificado no se encuentre en memoria entonces solicitará al Sistema de Disco una transferencia de dicho sector lógico y en última instancia la especificación de sector lógico se traduce a una del tipo (superficie, cilindro, sector).

2.3. Las interfaces de programación

Cada uno de los servicios de los cuatro niveles de este sistema de ficheros tiene su propia interfaz. Las interfaces vienen impuestas; como desarrolladores, ustedes se limitarán a implementar las funciones de interfaz que se describen para la práctica que se les ha asignado.

La interfaces de los módulos se describen en capítulos subsiguientes: subsistema de disco (capítulo 3), caché de disco (capítulos ¡Error!No se encuentra la fuente de la referencia. y 5), sistema de ficheros básico (capítulo 6) y sistema de ficheros simbólico (capítulo ¡Error!No se encuentra la fuente de la referencia.).

En cualquier caso, todos los módulos tienen cosas en común, a saber: tipos de datos, tratamiento de errores y valores de retorno de las funciones. De ello se tratará en las siguiente líneas.

2.3.1. Tipos de datos comunes

En el fichero /prac/include/tipos.h se definen unos cuantos tipos de datos fundamentales que se utilizarán por todo el código de este sistema de ficheros.

Los tipos son:

typedef unsigned char BYTE;

typedef unsigned short WORD;

typedef unsigned long DWORD;

2.3.2. Resultados y errores

Aunque los módulos funcionales de los cuatro niveles son bastante diferentes, todas las funciones comparten características tanto en su sintaxis como en su funcionamiento.

La mayoría de las funciones devuelven un int, que valdrá cero (0) si la función se ha cumplido correctamente. Si se produce cualquier anomalía o error, todas las funciones devuelven un -1.

Hay un mecanismo para obtener más información sobre el último error producido, que está inspirado directamente en las llamadas al sistema del UNIX. Consiste en utilizar una variable global llamada Error_SF cuyo valor se corresponde con un código de error.

Los códigos de error están preestablecidos y se muestran en el fichero /prac/include/errores.h, que habrán de incluir en sus fuentes en C.

Los códigos de error son valores numéricos, pero tienen su expresión simbólica, en la forma ERR_tipo. Así, por ejemplo, existe un símbolo de nombre ERR_RANGO que se usará para indicar que algún parámetro de la última función llamada estaba fuera de rango.

Si la variable Error_SF vale cero, no se ha producido ningún error.

La macro HAY_ERROR se puede usar como una expresión booleana que es cierta si se ha producido un error.

Hay algunas funciones que devuelven un valor útil, como las que abren ficheros, que devuelven un índice al fichero abierto. Si estas funciones fallan, también devuelven un -1.

2.3.3. Funciones de informe

No bastará con que se implementen los servicios esenciales de cada nivel. También hará falta saber qué está ocurriendo en su interior. Por ello se propone, para cada práctica, que se implemente una función que imprima por la salida estándar un informe del estado del sistema que estén implementando. Lo más típico sería dar unas estadísticas de uso de recursos, recursos libres, fallos detectados, etc. etc.

Las funciones de informe quedan al libre albedrío de los implementadores, siempre que cumplan su misión, esta es, dar una panorámica en tiempo de ejecución del estado del servicio que están implementando.

Las funciones de informe tendrán un prototipo similar a éste:

void InformeXXX (void);

Estas funciones no devuelven ningún valor y, en principio, no tienen por qué producir errores.

2.4. Archivos en el laboratorio

Para realizar los trabajos prácticos se han depositado varios fuentes en C con las interfaces (ficheros cabecera) más algunos ficheros de implementación. También se irán introduciendo documentos sobre la práctica, como por ejemplo este mismo texto.

Todos ello pende del directorio /prac, estructurado de esta manera:

/prac/include            Ficheros cabeceras *.h                             
/prac/src                Fuentes de las implementaciones *.c                
/prac/lib                Bibliotecas: implementaciones compiladas *.a       
/prac/doc                Documentos relacionados                            

Para poder utilizar cómodamente las cabeceras y bibliotecas, habrán de modificar su fichero .profile, incluyendo una línea como la que sigue:

export CCOPTS=-Aa -D_HPUX_SOURCE -L/prac/lib -I/prac/include

Si, como es probable, ya tenían una línea similar en su fichero .profile, sustitúyanla por la que se muestra aquí.

3. Simulación de un disco

El objetivo de este trabajo práctico es la implementación de los conceptos teóricos vistos en clase sobre estructura física, organización de los datos y planificación de disco. En la práctica a realizar se distinguirán dos niveles lógicos bien definidos:

Nivel básico. Formado por todos aquellos módulos lógicos cuya misión es la de llevar a cabo las transferencias de datos solicitadas al más bajo nivel. Conceptualmente esta capa se correspondería con un manejador de disco.

Nivel de planificación. Cuya misión fundamental es la gestión de las peticiones de transferencia que llegan, por tanto en este nivel se encuentran implementadas las distintas políticas de planificación de disco. Otro aspecto importante es que en ella radica la interfaz con el sistema de memoria caché.

3.1. Cómo simular las unidades de disco

La aplicación a diseñar deberá ser capaz de manejar de forma simulada distintas unidades de disco hipotéticas con distintas características físicas. En nuestro modelo, una unidad de disco se simulará mediante un fichero que posee la siguiente estructura:

Bloque de parámetros físicos. Se trata de una tabla de tamaño fijo, en la que se distinguen los siguientes campos:

Número de cilindros.

Número de superficies.

Número de sectores por pista.

Tamaño en bytes del sector.

Tiempo de desplazamiento del brazo (seek).

Tiempo de Latencia.

Tiempo de transferencia.

Tiempo de recalibrado.

Área de sectores simulados. Se trata de una zona compuesta por registros de tamaño fijo, tanto el tamaño de los registros como el número de éstos se obtienen en base a la información contenida en el bloque anterior. Los registros de simulación de sectores de disco se disponen de la siguiente manera: en primer lugar los correspondientes a la primera superficie, luego a la segunda y así sucesivamente, para los asociados a una superficie dada, éstos se disponen por pistas, esto es: primero los asociados a la primera pista, a continuación los de la segunda y así sucesivamente hasta completar el número de pistas (cilindros) por superficie. Teniendo en cuenta esta organización, un aspecto que ha de resolver el diseñador o diseñadora es como pasar de una especificación del tipo (superficie, cilindro, sector) a número de registro de esta zona.

3.2. Nivel básico

Como ya se ha descrito, esta capa es la responsable de efectuar las transferencias de datos entre disco y la caché, para ello se deberán desarrollar, entre otras que usted considere, las siguientes funciones:

Inicializa_Disco que crea un fichero para simular una unidad de disco con unas características dadas.

Monta_Disco que prepara al sistema para poder acceder a una unidad de disco dada, ésta se ha de inicializar previamente.

Transfiere_Sector que realiza una trasferencia (lectura o escritura) de un sector lógico entre la caché y disco.

Posiciona_Cabeza que sitúa la cabeza de lectura y escritura de una unidad de disco sobre un cilindro dado.

Lee_Posicion que informa en qué cilindro se encuentra la cabeza

de lectura y escritura de una unidad dada.

Recalibra_Disco que efectúa una operación de recalibrado en una unidad de disco dada.

Este conjunto de funciones enunciado equivale a un manejador de disco elemental de un sistema operativo real. Seguidamente se pasa a describir con más detalle cada una de las rutinas anteriores.

3.3. Interfaz del nivel básico

Los ficheros de implementación de esta práctica habrán de incluir los siguientes ficheros cabeceras:

errores.h           códigos de error                                   
Tipos_DISCO.h       Tipos de datos del sistema de disco                
Consta_DISCO.h      Constantes del sistema de disco                    
disco.h             interfaz del sistema de disco                      

Todos estos ficheros se encuentran en el directorio /prac/include. Hay que resaltar que la interfaz de esta práctica ya está definida en disco.h y no se debería alterar.

Seguidamente pasamos a describir la interfaz del subsistema de disco.

3.3.1. Inicializa_Disco Crea un fichero de simulación de disco

#define "Consta_DISCO.h"

#define "Tipos_DISCO.h"

#define "Error_DISCO.h"

int Inicializa_Disco(char *nombre, bloq_param desc);

Inicializa_Disco inicializa un fichero, cuyo pathname se especifica mediante el parámetro nombre, para simular una unidad de disco cuyas características se especifican mediante el parámetro desc, si el fichero especificado no existe entonces lo creará y si ya existía, entonces lo reescribirá (perdiéndose por tanto la información contenida en él). Si la función se ejecuta con éxito, entonces devolverá un valor positivo igual al número de registros de simulación escritos en el fichero de simulación. En el caso de que se produzca un error durante su ejecución, entonces devolverá -1 y la variable global error_disco tomará un valor indicativo del motivo del error:

ERR_FORMAT Características inválidas de la nueva unidad.

ERR_ACCESO Error de lectura/escritura .

ERR_NOMBRE No se ha podido abrir/crear fichero.

3.3.2. Monta_Disco Accede a las datos de control de una unidad

#include "Consta_DISCO.h"

#include "Tipos_DISCO.h"

int Monta_Disco(char *nombre)

Monta_Disco lee el bloque de parámetros almacenado en el fichero (que simula a una unidad de disco) cuyo pathname se especifica mediante el parámetro nombre, una vez leído este bloque, ya la unidad virtual estará preparada para ser accedida por el sistema. Si la función se ejecuta con éxito retornará un valor no negativo que se corresponderá con un índice a la tabla de descriptores de unidades de disco del sistema, dicho índice deberá especificarse como identificador de unidad para las posteriores operaciones. En cambio, si se produce un error durante su ejecución entonces retornará -1 y la variable global error_disc tomará uno de los siguientes valores:

ERR_BLOQUE Error en la lectura del bloque de parámetros.

ERR_NOMBRE No se ha podido abrir/crear fichero.

ERR_MDISCO Alcanzado número máximo de unidades de disco.

3.3.3. Desmonta_Disco Desactiva una unidad de disco

#include "Consta_DISCO.h"

#include "Tipos_DISCO.h"

int Desmonta_Disco(unidad fd);

Desmonta_Disco desactiva una unidad de disco previamente activada mediante la función Monta_Disco, la unidad se especifica mediante el parámetro fd que es el descriptor de unidad. Si la función se ejecuta con éxito, entonces devuelve 0, en caso contrario, retornará -1 y la variable global error_disco tomará el valor ERR_DESCRI debido a que se ha pasado un descriptor de unidad de disco inválido.

3.3.4. Transferencia_Fisica Lee o escribe un sector físico de disco

#include "Consta_DISCO.h"

#include "Tipos_DISCO.h"

int Transferencia_Fisica(unidad fd, dir_fis direc, void *buff, int modo);

Tranf_Sector lleva a cabo la transferencia de datos entre la caché, a partir de la posición indicada por buff, y la unidad de disco especificada por fd, en el sector físico cuya dirección especifica direc. El tipo de transferencia se indicará mediante modo, pudiendo tomar este parámetro uno de los siguientes valores: LECTURA o ESCRITURA. Si la transferencia tiene éxito, entonces retornará 0, en caso contrario retornará -1 y la variable error_disco tomará alguno de los siguientes valores:

ERR_ACCESO Error de lectura/escritura .

ERR_DESCRI Descriptor de unidad de disco inválido.

ERR_DIRECC Dirección física de sector inválida.

ERR_MODO Modo de transferencia inválido.

Nota: Esta función simula los tiempos de latencia y de transferencia internamente mediante la llamadas a las funciones Tiempo_Late y Tiempo_Transf que proporciona la librería específica de esta práctica y que se les suministrará como material.

3.3.5. Posiciona_Cabeza Posiciona la cabeza de lectura y escritura

#include "Consta_DISCO.h"

#include "Tipos_DISCO.h"

int Posiciona_Cabeza(unidad fd, unsigned short int cil);

Posiciona_Cabeza sitúa la cabeza de lectura y escritura de la unidad de disco especificada por el descriptor fd en el cilindro especificado por cil. Si la función se ejecuta con éxito entonces retornará 0, en caso contrario retornará -1 y la variable global error_disco tomará alguno de los siguientes valores:

ERR_DESCRI Descriptor de unidad de disco inválido.

ERR_DIRECC Especificación de cilindro inválido.

Nota: Esta función simula el tiempo de posicionamiento del brazo internamente mediante la llamada a la función Tiempo_Seek y que proporciona la librería específica de esta práctica y que se les suministrará como material.

3.3.6. lee_Posicion Informa en qué cilindro se encuentra la cabeza

#include "Consta_DISCO.h"

#include "Tipos_DISCO.h"

int Lee_Posicion(unidad fd);

Lee_Posicion devuelve el número de cilindro en el que se encuentra la cabeza de lectura y escritura de la unidad asociada a fd. Si fd posee un valor incorrecto entonces devolverá -1 y la variable error_disco toma el valor ERR_DESCRI, en caso contrario, es decir si se ejecuta con éxito, entonces devolverá el cilindro en el que está ubicada la cabeza de lectura y escritura.

3.3.7. Recalibra_Disco Recalibra una unidad de disco

#include "Consta_DISCO.h"

#include "Tipos_DISCO.h"

int Recalibra_Disco(unidad fd);

Recalibra_Disco efectúa una operación de recalibrado en la unidad asociada a fd. Si fd posee un valor incorrecto entonces devolverá -1 y la variable error_disco toma el valor ERR_DESCRI, en caso contrario, es decir si se ejecuta con éxito, entonces devolverá el valor 0 y la cabeza estará en el cilindro 0.

Nota: Todo hardware de control de disco incorpora una facilidad de recalibrado de los elementos mecánicos de las unidades de disco, esta operación tiene por finalidad anular las inercias que se producen en dichos elementos. La función Recalibra_Disco simula el tiempo de recalibrado invocando a la función Tiempo_Recal que se proporciona en la librería específica de esta práctica.

3.3.8. Informe_Disco Da un informe del estado del disco

#include "Consta_DISCO.h"

#include "Tipos_DISCO.h"

int Informe_Disco (void);

Esta función imprimirá por la salida estándar un informe de estadísticas de uso y estado general del disco simulado.

No producirá errores en ningún caso.

3.4. Nivel de planificación

Como ya se ha comentado se trata del nivel superior de la aplicación, por tanto, a él le van a llegar las peticiones del subsistema de caché mediante la función de interfaz Transfiere_Sector definida en esta capa. Un aspecto importante a destacar es que las peticiones de transferencia que le llegan e este nivel contienen especificaciones de sectores lógicos, que serán convertidas aquí en especificaciones de sectores físicos y a continuación serán sometidas a una política de planificación. Toda petición seleccionada para ser atendida, se comunicará al nivel básico mediante la función Transfiere_Disco. Otras dos funciones perteneciente a este nivel que han de desarrollarse son: direccion_fisica, que obtiene el sector físico asociado a un sector lógico, y planifica_peticiones que gestiona las peticiones de disco según una determinada política.

3.4.1. Qué es un sector lógico

Se trata de un bloque de datos de tamaño igual al sector físico pero que se referencia de forma diferente, pues a este nivel el disco se "ve" como una secuencia lineal de sectores, por ejemplo, el primer sector será el sector 0, el segundo será el sector 1, y así sucesivamente. Como consecuencia de la existencia de este disco lógico (formado por una secuencia de sectores lógicos) se ha de definir una correspondencia biunívoca entre sector lógico y sector físico, la forma que adopta esta correspondencia no es un aspecto irrelevante, pues influye en la rapidez con que se acceden a los datos de los archivos almacenados en disco, específicamente se ha facilitar el principio de localidad de los sectores que contienen los datos de un archivo, es decir, se ha de procurar que los datos de un fichero estén ubicados en registros lógicos continuos y que estos a su vez se correspondan con registros físicamente próximos (de esta manera se miniminiza el impacto de los tiempos de traslación de la cabeza de lectura y escritura y de latencia). la regla que se ha de utilizar en esta practica es la siguiente: primero empezamos por asociar los sectores lógicos a sectores físicos del primer cilindro, cuando agotemos la primera superficie se pasa a la segunda, y así sucesivamente, cuando se agote la última superficie del primer cilindro, se pasa al segundo cilindro y se procede de la misma manera y así hasta cubrir todos los cilindros de la unidad. Un aspecto a resolver en esta práctica es implementar esta regla de correspondencia.

3.4.2. Transfiere_Sector Transfiere un sector lógico

#include "Consta_DISCO.h"

#include "Tipos_DISCO.h"

int Transfiere_Sector(unidad fd, sector_logico sect , void *buff, int modo);

Transfiere_Sector lleva a cabo la transferencia de un sector lógico, especificado por sect, entre la caché y la unidad de disco especificada por fd. El tipo de transferencia se especifica en modo pudiendo adoptar éste parámetro los valores LECTURA o ESCRITURA. Si la transferencia se ejecuta con éxito entonces esta función retornará 0, en caso contrario retornará -1 y la variable global error_disco tomará alguno de los siguientes valores:

ERR_DESCRI Descriptor de unidad de disco inválido.

ERR_DIRECC Dirección lógica de sector inválida.

ERR_ACCESO Error de lectura/escritura.

ERR_MODO Modo de transferencia inválido.

3.4.3. Direccion_Fisica Obtiene la dirección física de un sector

#include "Consta_DISCO.h"

#include "Tipos_DISCO.h"

int Dirección_Física(unidad fd, sector_logico sect, dir_fis *dir);

Dirección_Física obtiene la dirección del sector físico asociado al sector lógico, especificado por sect. El parámetro fd indica la unidad en donde se encuentra el sector. Si la función se ejecuta con éxito, entonces retornará 0 y el *dir contendrá la dirección física calculada, en el caso de que ocurra algún error, entonces retornará -1 y la variable global error_disco tomará alguno de los valores siguientes:

ERR_DESCRI Descriptor de unidad de disco inválido.

ERR_DIRECC Dirección inválida de sector lógico.

4. Sistema de Bloques Físicos

Los niveles llamados Sistema de Ficheros Básico y Sistema de Ficheros Simbólico se apoyan ambos en unos servicios que implementan un disco virtual. Estos servicios se suministran en el llamado Sistema de Bloques Físicos (SBF).

Dada su simplicidad, el SBF no constituye una práctica en sí mismo, aunque tres de los trabajos propuestos (Caché, S.F. Básico y S.F. Simbólico) han de utilizar sus servicios. Para ello se ha dispuesto un fuente en C con una implementación simple del SBF.

El disco virtual se define como un conjunto de bloques físicos contiguos de tamaño fijo, como muestra el diagrama. Los bloques físicos están numerados de cero en adelante.

4.1. El superbloque

El primer bloque de un disco virtual se llama superbloque y contiene una serie de campos que informan de las características del disco (longitud, tamaño de sector, etc.)

El superbloque sólo se puede utilizar para leer o escribir esta información. No se pueden utilizar los campos del superbloque con fines distintos a los que fueron pensados.

Así pues, el primer bloque físico realmente utilizable es el número 1 (uno), ya que el bloque cero tiene una finalidad específica: es el superbloque.

Los campos del superbloque se muestran en este esquema, en el mismo orden en que se guardan en el disco.

char  magico[8]                  Cadena de caracteres para verificar el       
                                 formato                                      
WORD  tam_bloque_fisico          Tamaño en bytes de un bloque físico          
DWORD num_bloques_fisicos        Cantidad total de bloques físicos            
WORD BF_bloque_datos             Bloques físicos por cada bloque de datos     
WORD bytes_bloque_datos          Tamaño efectivo en bytes de un bloque de     
                                 datos                                        
DWORD num_BF_reservados          Bloques físicos reservados por el SFS        
WORD tam_BFB                     Tamaño en bytes de un bloque de fichero      
                                 básico                                       
WORD num_BFB                     Número de BFBs reservados                    
WORD tam_DFE                     Tamaño en bytes de un DFE                    

Los distintos niveles del sistema de ficheros utilizarán y, en su caso, alterarán estos campos, según lo descrito en cada capítulo de este documento.

El único campo que es pertinente resaltar ahora es magico. Este campo es una cadena de ocho caracteres cuyo único propósito es dar un indicio de que el archivo UNIX puede ser un sistema de ficheros de esta práctica. Se supone que todos los discos formateados en las prácticas deberían tener la misma cadena en magico. El valor que se propone en los ficheros de interfaz es "SOPA123", que pueden cambiar para diferenciar sus sistemas de ficheros de los de otros compañeros, o entre distintas versiones de sus mismos programas.

Para manipular cómodamente el superbloque, se dispone de un tipo de datos, struct Superbloque, que se define en el fichero de interfaz <superblq.h>

Además, el Sistema de Bloques Físicos implementa sendas funciones para leer y escribir el superbloque: LeeSuperbloque y EscribeSuperbloque.

4.2. Funciones del nivel físico

Todos los accesos al disco virtual en esta práctica se han de basar en estas funciones, que aparecen en el fichero cabecera <sbfisico.h>

/* Instalación y montaje de discos */

int InstalaDisco ( const char* nombre_fichero,

WORD tam_bloque, DWORD num_bloques );

int MontaDisco ( const char* nombre_fichero, int nbloques_cache );

int DesmontaDisco (void);

/* Acceso al superbloque */

int LeeSuperbloque ( struct Superbloque* bloque );

int EscribeSuperbloque ( const struct Superbloque* bloque );

/* Lectura y escritura de bloques físicos */

int LeeBloqueFisico ( DWORD nbloque, void* bufer );

int EscribeBloqueFisico ( DWORD nbloque, void* bufer );

4.3. Interfaz de programación

A continuación se detallan las especificaciones de las funciones del Sistema de Bloques Físicos.

4.3.1. Instalación y montaje de discos

int InstalaDisco ( const char* nombre_fichero,

WORD tam_bloque, DWORD num_bloques );

Esta función crea un fichero de nombre nombre_fichero, estructurado como Sistema de Bloques Físicos, conteniendo num_bloques bloques físicos, cada uno con un tamaño en bytes igual a tam_bloque.

La función se encarga de crear un superbloque con los valores adecuados en los campos magico, tam_bloque_fisico y num_bloques_fisicos.

La función devuelve un -1 en caso de error, depositando en la variable Error_SF el código de error, que puede ser:

ERR_DISCO          Error al intentar crear el fichero UNIX                      
ERR_RANGO          Parámetros fuera de rango                                    
ERR_ESPACIO        No había espacio suficiente en disco                         

int MontaDisco ( const char* nombre_fichero, int nbloques_cache );

Esta función monta el fichero nombre_fichero para su uso como Sistema de Bloques Físicos. nombre_fichero debió haber sido creado previamente con la función InstalaDisco. Sólo puede haber un disco montado.

El parámetro nbloques_cache indica cuántos bloques de desea que contenga la caché de bloques físicos. El sistema de caché intentará habilitar una caché de ese tamaño; si no es capaz se creará una caché con el máximo tamaño que sea posible.

La función devuelve un -1 en caso de error, depositando el código de error en Error_SF. Algunos errores posibles son:

ERR_NOEXISTE       El fichero UNIX no existe                                    
ERR_DISCO          Error con el fichero UNIX                                    
ERR_MONTADO        Ya hay un SBF montado                                        
ERR_FORMATO        El fichero no tiene formato de SBF                           

int DesmontaDisco (void);

Desmonta el disco actualmente en uso. Esta función devuelve un -1 en caso de error. El código de error que se guarda en Error_SF puede ser:

ERR_DISCO          Error con el fichero UNIX                                    
 ERR_INACTIVO      No hay ningún SBF montado                                    

4.3.2. Acceso al superbloque

int LeeSuperbloque ( struct Superbloque* bloque );

int EscribeSuperbloque ( const struct Superbloque* bloque );

Estas dos funciones se encargan, respectivamente, de leer y escribir el superbloque del SBF montado. Las transferencias se efectúan sobre la estructura apuntada por bloque.

Ambas funciones devuelven un -1 si se ha producido un error. Los valores posibles de Error_SF son:

ERR_INACTIVO       No hay ningún SBF montado                                    

4.3.3. Lectura y escritura de bloques físicos

int LeeBloqueFisico ( DWORD nbloque, void* bufer );

int EscribeBloqueFisico ( DWORD nbloque, void* bufer );

Estas dos funciones se encargan, respectivamente, de leer y de escribir bloques físicos pertenecientes al SBF montado actualmente. El número de bloque físico para transferir viene dado por nbloque. La transferencia se efectúa sobre la zona de memoria apuntada por bufer. El llamador es responsable de que bufer apunte a una zona con espacio suficiente para un bloque físico.

Ambas funciones devuelven un -1 si se ha producido un error. Los valores posibles de Error_SF son:

ERR_INACTIVO       No hay ningún SBF montado                                    
ERR_RANGO          Bloque físico fuera de rango                                 

4.4. Fuentes en C

Los archivos del Sistema de Bloques Físicos son los siguientes:

/prac/include/sbfisico.h            Interfaz del SBF                         
/prac/include/superblq.h            Estructura del superbloque               
/prac/src/sbfisico.c                Implementación del SBF                   

La implementación de muestra en sbfisico.c es el punto de partida para la práctica del sistema de caché. Los desarrolladores de los trabajos de Sistema de Ficheros Básico y Simbólico harían bien en echar un vistazo al código de este fichero.

5. Caché de disco

Un sistema de ficheros es una estructura de datos compleja instalada sobre un soporte persistente, el disco, diferente de la memoria principal. Olvidando esta diferencia crucial, las similitudes entre las estructuras de datos en memoria y en disco son bastantes. Por ejemplo, el principio de localidad se cumple también con los ficheros: es muy probable que los accesos futuros a un fichero se hallen próximos a los hechos en el pasado reciente.

Por otro lado, en todo sistema de ficheros suele haber un conjunto de bloques de disco que contienen información descriptiva del sistema (en nuestro caso, puede ser el superbloque); o bien estructuras de gestión del sistema: mapas de bits, estructuras de directorio, bloques descriptores de fichero, etc. Si todos esos bloques se utilizan con mucha frecuencia, puede ser deseable mantenerlos en memoria de forma más o menos permanente.

La existencia del principio de localidad y de los bloques muy usuados invitan al desarrollo de un sistema de caché que almacene los bloques de disco que se consideren mejores candidatos a ser accedidos en un futuro inmediato.

Se propone así un trabajo práctico en el que se diseñará e implementará una caché de bloques físicos de disco, aplicando varias políticas de las estudiadas en la asignatura.

5.1. Interfaz con el disco manejado

La caché operará sobre discos virtuales, implementados sobre ficheros UNIX. La organización del disco y su interfaz funcional serán las definidas en el Sistema de Bloques Físicos (ver página 16). Los desarrolladores dispondrán de varios fuentes en C con la interfaz y la implementación de las funciones de manejo del disco.

Los archivos que han de manejar son:

sbfisico.h           Interfaz con el disco virtual                      
sbfisico.c           Implementación de las funciones del disco          
tipos.h              Tipos de datos básicos                             
errores.h            Constantes de error                                
superblq.h           Estructura del superbloque                         

5.2. Interfaz de la caché

En este trabajo, ustedes habrán de retocar las funciones de interfaz LeeBloqueFisico y EscribeBloqueFisico de manera que no accedan siempre al disco, sino que se trabaje con una caché que ustedes diseñarán.

La función MontaDisco ha de reconocer el parámetro nbloques_cache, que indica cuántos bloques físicos se desean para la caché. El sistema que implementen tomará este parámetro como una simple indicación. En caso de que, por problemas de espacio, no se pueda habilitar una caché del tamaño solicitado, se dispondrá del máximo tamaño posible. La función MontaDisco nunca producirá un error por problemas de espacio de caché.

Por último, es planteable, aunque no obligatorio, modificar las funciones LeeSuperbloque y EscribeSuperbloque de manera que el superbloque del disco sólo se cargue una vez y se mantenga en memoria de forma permanente.

Según se ve, el trabajo consiste principalmente en alterar la implementación de los servicios del Sistema de Bloques Físicos, dejando la interfaz intacta. El punto de partida será una implementación primitiva, sin caché, que encontrarán en /prac/src/sbfisico.c

Aparte de lo anterior, se tendrá que crear una función para imprimir un informe sobre la caché. Su prototipo será

void InformeCache (void);

y devolverá principalmente información sobre estadísticas de uso, como tasa de éxitos de la caché, número de accesos totales, de lectura y de escritura, etc.

5.3. Políticas de la caché

En el trabajo se han de probar tres políticas de sustitución. Implementarán la LRU (menos recientemente usada), la LFU (menos recientemente usada) y otra más de su elección.

Las tres políticas se implementarán en respectivas versiones del programa, para poder probarlas por separado.

6. Sistema de ficheros básico

En este trabajo se creará un sistema de ficheros muy primitivo a partir de un disco simulado. Partirán de unas funciones elementales que gestionan un disco virtual, definidas en el Sistema de Bloques Físicos. A partir de ellas, confeccionarán un módulo funcional que permita operar con ficheros muy simples, llamados ficheros básicos.

6.1. El fichero básico

Un fichero básico es una secuencia de bloques de datos, los cuales podrán leerse o escribirse con funciones que ustedes desarrollarán. El fichero básico se identifica mediante un número: carece de nombre simbólico. El número que identifica un fichero básico se llamará índice a fichero básico (IFB).

Un fichero básico estará compuesto de un bloque de fichero básico (BFB) y un conjunto de bloques de datos. El BFB contendrá la información necesaria para conocer qué bloques de datos pertenecen al fichero (los datos exactos dependerán del esquema de administración de espacio que ustedes escojan).

6.2. Operaciones con ficheros básicos

En la práctica, implementarán una API para trabajar con estos ficheros básicos. Las funciones que tendrán que construir básicamente son para:

· crear y destruir ficheros

· añadir bloques a un fichero o truncarlo

· leer y escribir bloques de un fichero al azar

Observen que los ficheros básicos no tienen nombre en forma de cadena de caracteres, sino un simple identificador numérico. Observen también que los accesos al cotenido de un fichero básico son por bloques completos, nunca por bytes individuales. El desarrollo de servicios más elaborados y próximos a la interfaz de UNIX o MS-DOS se realiza en el nivel del Sistema de Ficheros Simbólico, que es otra práctica de la asignatura.

La API está definida y se expone en estas páginas. Ustedes sólo tendrán que implementarla, eso sí, de forma que cumpla con las especificaciones dadas.

6.3. Organización física del disco

Desde la óptica del programador, el punto de partida del Sistema de Ficheros Básico es un conjunto de servicios para gestionar un disco virtual, estructurado como una secuencia de bloques físicos contiguos de tamaño fijo. Estos servicios se definen e implementan en el nivel llamado Sistema de Bloques Físicos (SBF). Tal nivel se describe en este texto a partir de la página 16. Lean atentamente el apartado para tener claras las funciones del SBF. También tienen disponibles una implementación de muestra del SBF, en el archivo /prac/src/sbfisico.c, que deberían estudiar y utilizar en sus compilaciones.

6.4. Estructura del sistema de ficheros básico

Aunque tendrán cierta libertad, el sistema de ficheros que diseñen se ceñirá a la estructura que se muestra en este diagrama.

Salvo el superbloque, las distintas áreas del disco tienen un tamaño variable, que se define cuando se formatea el disco. Una vez formateado, no se pueden alterar los tamaños de esas áreas.

La rutina de formateo se invoca de la siguiente forma:

FormateaSFB ( nombre_fichero, BF_reservados,
tamaño_DFE, tamaño_bloque_datos, número_de_ficheros );

6.4.1. Campos del superbloque

El primer bloque del SFB es el superbloque, lo cual viene impuesto por la interfaz del SBF. El Sistema de Ficheros Básico habrá de modificar en el formateo algunos campos del superbloque:

WORD BF_bloque_datos           Bloques físicos por cada bloque de datos      
WORD bytes_bloque_datos        Tamaño efectivo en bytes de un bloque de      
                               datos                                         
DWORD num_BF_reservados        Bloques físicos reservados por el SFS         
WORD tam_BFB                   Tamaño en bytes de un bloque de fichero       
                               básico                                        
WORD num_BFB                   Número de BFBs reservados                     
WORD tam_DFE                   Tamaño en bytes de un DFE                     

Según se avance en el documento, se irán manifestando los puntos donde se determinarán la razón de ser y los valores apropiados de cada uno de estos campos.

Habrá que insistir en que los restantes campos del superbloque no se deberían alterar directamente en este nivel.

6.4.2. Bloques físicos reservados

A continuación existirá un área de bloques reservados, que podría utilizar el Sistema de Ficheros Simbólico con fines particulares. Una vez formateado el disco, el Sistema de Ficheros Básico no debería trabajar con estos bloques reservados.

El tamaño de esta área viene dado por el argumento BF_reservados de la rutina de formateo. El área de bloques reservados puede ser nula (tamaño cero).

6.4.3. BFBs: Bloques de ficheros básicos

Los bloques de ficheros básicos (BFB) son una serie de estructuras donde se almacena la información de cada uno de los ficheros de este sistema. La cantidad total de BFB se determina en el momento del formateo (parámetro número_de_ficheros).

El contenido del BFB dependerá de la implementación que ustedes escojan. Campos típicos del BFB pueden ser: tamaño del fichero (en número de bloques), apuntadores a los bloques de datos del fichero, un campo que indique si el BFB está libre u ocupado, etc. Aparte, hay que reservar espacio para el DFE (ver siguiente apartado).

Cada BFB ocupará un tamaño fijo, que será determinado por ustedes. Habrán de calcular cuántos BFB caben en un bloque físico, así como cuántos bloques físicos hacen falta para albergar todos los BFB requeridos.

6.4.4. El DFE

El Sistema de Ficheros Básico no ofrece ninguna facilidad como nombres simbólicos de ficheros, permisos de uso, fechas de última modificación, etc. Previendo que otros usuarios quieran añadir campos a sus ficheros, se define un área por cada BFB llamada descriptor de fichero extendido (DFE).

El espacio reservado para el DFE en cada BFB es fijo y se declara en el momento del formateo del disco (argumento tamaño_DFE).

En esta práctica no se han de preocupar de qué contiene el DFE. Simplemente consideren que es un espacio adicional reservado en cada BFB.

6.4.5. Bloques reservados por el S.F. Básico

El sistema de ficheros básico puede necesitar una serie de bloques físicos para almacenar información de control. Por ejemplo, si eligen el mapa de bits como estructura para controlar el espacio libre, tendrán que reservar un espacio en disco para almacenarlo.

El número de bloques reservados queda a criterio de los implementadores, o sea, ustedes. Su situación siempre ha de ser posterior a los BFB, aunque no necesariamente contigua a ellos.

6.4.6. Bloques de datos

Lo restante del disco son bloques de datos utilizables para almacenar información. Todos los bloques de datos tendrán el mismo tamaño, que vendrá dado en número de bloques físicos necesarios para un bloque de datos. Un bloque de datos ocupa exactamente tamaño_bloque_datos bloques físicos.

Si emplean asignación de espacio enlazada, parte del bloque de datos se consume en los encadenamientos. Por ello hay un campo del superbloque que indica cuántos bytes de un bloque de datos son realmente utilizables: bytes_bloque_datos.

Los bloques de datos habrán de estar numerados de UNO en adelante.

6.5. Interfaz de programación

A continuación se describe la interfaz proporcionada por esta práctica.

Si usted va a realizar la práctica del Sistema de Ficheros Básico, deberá implementar las funciones aquí descritas.

Si usted va a realizar la práctica del Sistema de Ficheros Simbólico, ha de leer estas páginas como referencia de los servicios que se le proporcionan en el Sistema de Ficheros Básico.

6.5.1. Ficheros cabeceras

La interfaz queda accesible con el siguiente fichero cabecera:

#include <sfbasico.h>

A su vez, sfbasico.h tendrá que incluir lo siguiente:

#include <sbfisico.h>

#include <errores.h>

#include <superblq.h>

donde se declaran la interfaz con el Sistema de Bloques Físicos, los tipos básicos, las constantes de error y la estructura del superbloque.

Los ficheros cabeceras se encuentran instalados en el directorio /prac/include

6.5.2. Tipos de datos. Variables y constantes

La práctica puede implementar un almacenamiento de espacio contiguo. En tal caso, en el fichero sfbasico.h debería estar definido el símbolo CONTIGUO:

#define CONTIGUO

Otras constantes se utilizan para que niveles superiores sepan cuáles son ciertos límites del Sistema de Ficheros Básico:

#define MAX_BLOQUES_CONTIGUOS 1

#define MAX_FICHEROS 20000

La primera define cuántos bloques contiguos se pueden solicitar; sólo tiene sentido en almacenamiento contiguo. La segunda define la cantidad máxima de ficheros que puede tener un SFB.

Los valores expuestos son sólo a título de ejemplo.

Este es el tipo de datos para trabajar con ficheros básicos:

typedef unsigned short IFB;

6.5.3. Funciones de formateo y montaje

Tres funciones sirven para dar formato a discos, montarlos y desmontarlos. Para poder trabajar con un disco hay que montarlo con la función MontaSFB. Un disco montado ha de estar previamente formateado con FormateaSFB. El sistema se desmonta con DesmontaSFB.

Los servicios de este módulo sólo trabajarán con un único disco montado.

int FormateaSFB ( const char* nombre_fichero,

WORD BF_reservados, WORD tam_DFE,

WORD tam_bloque_datos, WORD num_ficheros );

Da formato a un disco virtual, especificado por nombre_fichero. El disco virtual ha de estar ya instalado con la función de bajo nivel InstalaDisco().

Si se produce un error, esta función devuelve -1 y la variable global Error_SF tomará alguno de estos valores:

ERR_DISCO          Error con el fichero UNIX                                    
 ERR_PARAMETRO     Parámetros no válidos                                        
ERR_FORMATO        Error de formato del fichero                                 

int MontaSFB ( const char* nombre_fichero, int nbloques_cache );

Esta función monta un sistema de ficheros ya formateado, que se encuentra en el fichero UNIX nombre_fichero. Para utilizar cualquier función que trabaje con ficheros básicos, hay que llamar previamente a esta función.

El parámetro nbloques_cache indica al SBF el número de bloques deseado para la caché. El SBF podría ignorar este parámetro.

No se puede mantener más de un sistema de ficheros activo. Si se llama a MontaSFB mientras se tiene un SFB activo, se considera un error.

Esta función devuelve -1 si hay un error. Algunos valores de la variable Error_SF pueden ser:

ERR_NOEXISTE       Fichero inexistente                                          
ERR_FORMATO        Error de formato del fichero                                 
ERR_MONTADO        Ya hay un SFB montado                                        

int DesmontaSFB (void);

Esta función se invoca para desmontar el SFB actualmente montado.

Esta función devuelve -1 si hay un error. Algunos valores de la variable Error_SF pueden ser:

ERR_DISCO          Error con operaciones de bajo nivel sobre el fichero UNIX    
ERR_INACTIVO       No hay un SFB montado                                        

WORD TamBloqueDatos (void);

Esta función retorna el tamaño efectivo en bytes de un bloque de datos. Es una forma simple de acceder a la configuración del superbloque, y es útil para que las rutinas del Sistema de Ficheros Simbólico puedan trocear sus transferencias.

La función devuelve un (unsigned)-1 si no hay un SFB montado, con lo que Error_SF pasaría a valer ERR_INACTIVO.

6.5.4. Creación y destrucción de ficheros

Dos funciones, CreaFB y DestruyeFB, se encargan crear y destruir ficheros básicos.

IFB CreaFB ( WORD num_bloques );

La función CreaFB ubica un bloque de fichero básico y le asigna num_bloques bloques de datos. El contenido de los bloques de datos es indefinido.

Esta función devuelve un entero que corresponde con el fichero básico seleccionado. Este valor puede usarse en las operaciones de lectura, escritura o manipulación de DFE. En caso de que no pueda completar la operación, devuelve un -1. Si no se consiguen los num_bloques, no se crea el fichero.

Algunos valores de la variable Error_SF pueden ser:

ERR_ESPACIO        No hay BFB libres o bloques de datos suficientes             
ERR_INACTIVO       No hay un SFB montado                                        

int DestruyeFB ( IFB fichero );

La función DestruyeFB desasigna todos los bloques de datos pertenecientes al fichero número fichero y libera el BFB correspondiente.

Esta función devuelve -1 si se produce un error. Algunos valores de la variable Error_SF pueden ser:

ERR_RANGO          IFB fuera de rango                                           
ERR_OCUPADO        El BFB ya estaba disponible                                  
ERR_INACTIVO       No hay un SFB montado                                        

6.5.5. Control del tamaño del fichero

Existen tres funciones para: averiguar el tamaño de un fichero; añadir bloques al fichero; recortar el tamaño de un fichero. La unidad de datos del fichero básico es el bloque de datos: no se puede trabajar con bytes ni fracciones del tamaño efectivo de un bloque de datos.

WORD LongitudFB ( IFB fichero );

Devuelve el número de bloques de datos asignados al fichero.

Si hay alguna anomalía, se devuelve un ((unsigned)-1). Algunos valores de la variable Error_SF pueden ser:

ERR_RANGO          IFB fuera de rango                                           
ERR_LIBRE          IFB no ocupado                                               
ERR_INACTIVO       No hay un SFB montado                                        

int ExpandeFB ( IFB fichero, WORD num_bloques );

Añade num_bloques al fichero. Los bloques se añaden siempre por el final (apéndice). El contenido de los nuevos bloques es indefinido. El contenido de los bloques de datos y asignados no ha de variar.

Si no hay espacio para satisfacer la petición, no se asigna ningún bloque y la función devuelve un -1.

ExpandeFB devuelve un -1 en caso de error. Algunos valores de la variable Error_SF pueden ser:

ERR_ESPACIO        Espacio insuficiente: no se asignan los bloques pedidos      
ERR_RANGO          IFB fuera de rango                                           
ERR_LIBRE          IFB no ocupado                                               
ERR_INACTIVO       No hay un SFB montado                                        

int TruncaFB ( IFB fichero, WORD num_bloques );

La función TruncaFB ajusta el tamaño de fichero a exactamente num_bloques. num_bloques ha de ser menor o igual al tamaño actual del fichero, y puede valer cero.

Los bloques de datos desasignados deberían quedar disponibles para otros ficheros.

La función devuelve un -1 en caso de error. Algunos valores de la variable Error_SF pueden ser:

ERR_PARAMETRO      num_bloques es mayor que el tamaño del fichero               
ERR_RANGO          IFB fuera de rango                                           
ERR_LIBRE          IFB no ocupado                                               
ERR_INACTIVO       No hay un SFB montado                                        

6.5.6. Lectura y escritura de bloques

Dos funciones primitivas se encargan de leer y escribir información en los ficheros básicos. Los accesos son por bloques de datos completos.

int LeeBloqueFB ( IFB fichero, WORD num_bloque, void* bufer );

Esta función lee el bloque de datos num_bloque perteneciente a fichero y lo deposita en la zona de memoria apuntada por bufer.

El llamador de esta función es responsable de que bufer apunte a un lugar correcto y que tenga espacio suficiente para albergar un bloque de datos, que viene determinado por el campo bytes_bloque_datos del superbloque del S.F.B. actualmente montado.

La función devuelve un -1 en caso de error. Algunos valores de la variable Error_SF pueden ser:

ERR_NOEXISTE       num_bloque está fuera del rango del fichero                  
ERR_RANGO          IFB fuera de rango                                           
ERR_LIBRE          IFB no ocupado                                               
ERR_INACTIVO       No hay un SFB montado                                        

int EscribeBloqueFB ( IFB fichero, WORD num_bloque, void* bufer );

La función EscribeBloqueFB copia la zona de memoria apuntada por bufer en el bloque de datos num_bloque perteneciente a fichero. Se copian exactamente los bytes que ocupa un bloque de datos del S.F.B. actualmente montado, según el campo bytes_bloque_datos del superbloque.

El llamador de esta función es responsable de que bufer apunte a un lugar correcto en la memoria.

La función devuelve un -1 en caso de error. Algunos valores de la variable Error_SF pueden ser:

ERR_NOEXISTE       num_bloque está fuera del rango del fichero                  
ERR_RANGO          IFB fuera de rango                                           
ERR_LIBRE          IFB no ocupado                                               
ERR_INACTIVO       No hay un SFB montado                                        

6.5.7. Acceso al descriptor de fichero extendido (DFE)

Se suministran dos funciones para que el Sistema de Ficheros Simbólico guarde o recoja información descriptora de los ficheros, que viene almacenada en el Descriptor de Fichero Extendido (DFE) del BFB (ver página 25).

int LeeDFE ( IFB fichero, void* estructura );

Esta función copia el DFE de fichero en la zona de memoria apuntada por estructura. El llamador de la función es el responsable de que estructura apunte a una dirección de memoria correcta, capaz de albergar un DFE.

El tamaño del DFE ha de estar almacenado en el campo tam_DFE del superbloque del S.F. actualmente montado.

La función devuelve un -1 en caso de error. Algunos valores de la variable Error_SF pueden ser:

ERR_RANGO          IFB fuera de rango                                           
ERR_LIBRE          IFB no ocupado                                               
ERR_INACTIVO       No hay un SFB montado                                        

int EscribeDFE ( IFB fichero, void* estructura );

Esta función copia la zona de memoria apuntada por estructura en el DFE de fichero. Se copian exactamente los bytes que ocupa un DFE del S.F.B. actualmente montado, según el campo tam_DFE del superbloque. El llamador de la función es el responsable de que estructura apunte a una dirección de memoria correcta.

La función devuelve un -1 en caso de error. Algunos valores de la variable Error_SF pueden ser:

ERR_RANGO          IFB fuera de rango                                           
ERR_LIBRE          IFB no ocupado                                               
ERR_INACTIVO       No hay un SFB montado                                        

6.5.8. Gestión elemental de los bloques de datos

Las funciones anteriores se bastan para proporcionar una interfaz al Sistema de Ficheros Simbólico. Sin embargo, se ha de suministrar un par de funciones para administrar los bloques de datos.

Sería muy recomendable que las funciones que gestionan bloques de datos: CreaFB, DestruyeFB, ExpandeFB, TruncaFB; llamaran a estas dos funciones.

DWORD ReservaBloqueDatos (void);

Esta función reserva un bloque de datos libre.

Devuelve un número que identifica el bloque de datos asignado.

Si se da algún error o no hay más espacio disponible, se devuelve (unsigned)-1. Algunos valores de la variable Error_SF pueden ser:

ERR_ESPACIO        No hay más bloques de datos libres                           
ERR_INACTIVO       No hay un SFB montado                                        

int LiberaBloqueDatos ( DWORD bloque );

Esta función devuelve al espacio libre el bloque de datos indicado por bloque.

Devuelve un -1 en caso de error. Algunos valores de la variable Error_SF pueden ser:

ERR_RANGO          Bloque de datos fuera de rango                               
ERR_LIBRE          Bloque de datos ya disponible                                
ERR_INACTIVO       No hay un SFB montado                                        

6.5.9. Funciones sólo implementadas en asignaciones contiguas

En caso de que se implemente una asignación de espacio contigua, se han de implementar dos funciones para tomar conjuntos de bloques de datos.

DWORD ReservaSegmentoDatos ( DWORD num_bloques );

Reserva num_bloques bloques de datos contiguos. Devuelve el número de bloque del primer bloque de datos del conjunto.

int LiberaSegmentoDatos ( DWORD bloque_inicial, DWORD num_bloques );

Devuelve al espacio libre un conjunto de bloques de datos, un total de num_bloques a partir de bloque_inicial.

Las condiciones de error serían similares a las dos anteriores funciones.

6.5.10. Función de informe

void InformeSFB (void);

Esta función imprimirá por la salida estándar un informe con las estadísticas de uso y el estado general del sistema de ficheros actualmente montado, si lo hay.

Esta función no retorna nada y no genera ningún error.

6.6. Ejemplos de uso

En este apartado se muestran algunos ejemplos simples del uso de los servicios del Sistema de Ficheros Básico.

Los ejemplos son fragmentos de código para insertar en un programa. Por mayor legibilidad, los ejemplos están escritos en C++.

6.6.1. Instalación y formato de un sistema

En este ejemplo, se instala y formatea una unidad de disco.

#include <sfbasico.h>

// ...

// Se instala un disco de 12000x512 = 6Mbytes

InstalaDisco ( "disco.dat", 512, 12000 );

// Estos dos valores provendrían de un nivel superior

int reservados = 24; // bloques físicos reservados

int dfe = 64; // espacio reservado para el DFE

// Se da formato al disco con

// tamaño de bloque = 2 bloques físicos(1Kbyte)

// y un máximo de 500 ficheros

FormateaSFB ( "disco.dat", reservados, dfe, 2, 500 );

6.6.2. Utilización de un disco

Este ejemplo muestra cómo se ha de montar y desmontar un disco para poder trabajar con él.

#include <sfbasico.h>

// ...

MontaSFB ("disco.dat");

// Crea un fichero tonto de 10 bloques

int fich = CreaFB (10);

// añade un bloque más

ExpandeFB(fich,1);

// Desmonta el disco

DesmontaSFB();

6.6.3. Acceso a bloques de datos

Este último ejemplo de uso muestra, entre otras cosas, cómo recuperar la información del superbloque para saber en este caso el tamaño en bytes del bloque de datos.

#include <sfbasico.h>

// ...

struct Superbloque SB; // Guardará los parámetros del disco

MontaSFB ("disco.dat");

LeeSuperbloque (&SB);

// Reserva espacio para escribir en un bloque

char* bufer = new char[SB->bytes_bloque_datos];

// Otra forma de hacerlo

char* bufer = new char[TamBloqueDatos()];

// Escribe algo en el primer bloque del fichero 33

strcat(bufer,"Hola, mundo\n");

EscribeBloqueFB (33,0,bufer);

// lee el tercer bloque del fichero 98

LeeBloqueFB (98,2,bufer);

for (int i=0; i<SB->bytes_bloque_datos; i++)

{

printf ( "bufer[%d] = %d\n", i, bufer[i] );

}

// Desmonta el disco, que ya no hace falta

DesmontaSFB();

7. Sistema de ficheros simbólico

Esta práctica consiste en el desarrollo de una parte de un sistema de ficheros, concretamente se refiere a lo que hemos denominado Sistema de Ficheros Simbólico, en el que se soportan las siguientes funciones:

Crea_Sistema           Crea un sistema de ficheros                              
Activa_Sistema         Prepara al sistema de ficheros para ser accedido.        
Crea_Fichero           Crea un archivo en un sistema de ficheros.               
Abre_Fichero           Abre un archivo.                                         
Lee_Fichero            Lee una ristra de bytes de un archivo.                   
Escribe_Fichero        Escribe una ristra de bytes en un archivo.               
Posiciona_Puntero      Actualiza el puntero de acceso de un archivo.            
Cierra_Fichero         Cierra un archivo.                                       
Borra_Fichero          Borra un archivo                                         

El conjunto de funciones anterior configuran la interfaz con el usuario, por tanto, en su desarrollo deberán estar contempladas como mínimo, se verá a medida que se avance en la exposición de la práctica que éstas además requerirán el apoyo de otras funciones que, o bien, se han de implementar en este nivel, o bien, forman parte del Sistema de Ficheros Básico, que proporciona servicios de gestión de bloques y ficheros sencillos (ver la siguiente práctica). Desde el punto de vista del usuario, el sistema de ficheros que se ha de diseñar posee las siguientes características:

1. Los archivos se identifican por una cadena de 12 caracteres.

2. Los archivos se organizan linealmente.

3. De cara al usuario, un archivo es visto como una cadena de bytes.

El módulo del sistema de ficheros que abarca esta práctica, Sistema Simbólico, será el responsable de soportar la visión del usuario anteriormente descrita y transformarla de manera que:

1. Los archivos se identifican por un identificador numérico del sistema, dícho identificador está asociado a una estructura de control que posee los atributos de los archivos, por tanto, en este nivel se llevarán a cabo la transformación de identificador simbólico en forma de cadena de cacarecteres (de usuario) a indentificador númerico del sistema.

2. Los archivos se constituyen como secuencia de bloques de datos de tamaño fijo.

7.1. Estructuras de control del Sistema de Ficheros Simbólico

Este nivel utilizará las siguientes estructuras de datos: bloque de control de fichero en disco, que llamaremos Bcfd, bloque de control de fichero en memoria, Bcfm, bloque de control de fichero de usuario, Bcfu, bloque de sistema de ficheros activo, Bsfa. Para cada una de las estructuras anteriores, se tendrá asociada una tabla, así se tendrán: con la estructura Bcfm se tendrá la Tabla de ficheros activos del sistema, Tfas, con la estructura Bcfu se tendrá la Tabla de ficheros de usuario, Tfu, y por último, con la estructura Bsfa se tendrá la Tabla de sistemas de ficheros activos, Tsfa. Seguidamente se pasa a describir la información contenida en cada una de las estructuras de control mencionadas.

7.1.1. El Bcfd

Se trata de la estructura de control que todo archivo en nuestro sistema debe poseer, se corresponde con lo que en la bibliografía se denomina entrada de directorio (en DOS), inode (en UNIX), etc. Mediante esta estructura el sistema podrá manipular los datos pertenecientes a los ficheros, internamente contiene la siguiente información:

- Nombre del archivo: 12 caracteres pertenecientes a [a ... z, A ... Z].

- Fecha de creación/modificación: 3 caracteres asociados a año (a partir de 1900, mes y día.

- Hora de creación/modificación: 3 caracteres asociados a hora, minuto, segundo.

- Tamaño: 4 bytes sin signo.

- Atributo de acceso: 1 byte que especifica si es de sólo lectura o sólo escritura.

- Dirección de bloques de datos: 40 bytes que localizan los bloques de datos del archivo.

Asociada a la estructura Bcfd se tendrá el directorio del sistema (que será el único existente pues los archivos se organizan en un sólo nivel) formado por un número dado de elementos del tipo Bcfd, el número específico de entradas de este tipo que se tienen para un sistema de ficheros se establece en el momento de la creación de éste (función Crea_Sistema).

7.1.2. El Bcfm

Se trata de una estructura que todo fichero activo (sobre el que se ha realizado al menos una operación de apertura) tiene asociado, contiene la imagen actualizada en memoria de la estructura Bcfd descrita anteriormente e información adicional necesaria para manipular el archivo mientras esté activo. Concretamente esta estructura contiene la siguiente información:

- Índice al directorio del sistema donde se encuentra su Bcfd: 2 bytes sin signo.

- Imagen actualizada del Bcfd: tamaño en bytes de la estructura Bcfd.

- Número de referencias: 1 byte sin signo.

- Flag de bloqueo: 1 byte

Asociada a la estructura Bcfm se tiene la tabla de ficheros activos del sistema, Tfas, que consiste en un conjunto de entradas del tipo Bcfm, cuyo número, MAX_TFAS, es una constante del sistema definida en el fichero Consta_SF.h que se proporcionará como apoyo para el desarrollo de la práctica. Cuando se abre por primera vez un fichero, entonces se asigna una entrada de la Tfas, cuando se cierra un fichero y además el contador de referencias del fichero cerrado es 1, entonces es cuando se libera la entrada asociada al fichero cerrado.

7.1.3. El Bcfu

Se trata de una estructura que todo que usuario que ha realizado una operación de apertura de un fichero (función Abre_Fichero) posee en su área de memoria, contiene la siguiente información:

- Índice a la entrada en la Tfas que el fichero tiene asociada: 2 bytes sin signo.

- Copia de los atributos de acceso: 1 byte.

- Puntero de acceso al fichero: 4 bytes sin signo.

Asociada a esta estructura se tiene la Tabla de ficheros de usuario, Tfu, que está constituida por un conjunto de entradas del tipo Bcfu, el número de estas entradas, MAX_TFUS, es una constante del sistema definida en Consta_S.h . Por cada operación de apertura realizada sobre un fichero se tendrá una entrada del tipo Bcfu en la tabla que el usuario que realizó la operación posee. Cuando se ejecuta un cierre de fichero por parte de un usuario (Cierra_Fichero) se libera la entrada asociada al fichero cerrado.

7.1.4. El Bsfa

Lo utiliza el sistema para controlar los sistemas de ficheros activos (sobre los que se han ejecutado una operación de Activa_Sistema), teniendo cada uno de ellos un Bsfa asociado

7.2. Interfaz del Sistema de Ficheros Simbólico

Las funciones utilizadas por el SFS se pueden clasificar en tres tipos:

· Funciones de interfaz con el usuario.

· Funciones de uso interno.

· Funciones de interfaz con el Sistema de Ficheros Básico.

Las primeras son las que vamos a describir en este apartado y son aquellas que pueden ser invocadas directamente por el usuario desde la línea de órdenes de la aplicación a desarrollar. Las segundas son invocadas por la funciones anteriores y sirven para efectuar ciertas tareas elementales del SFS, la definición de tales tareas y la forma de implementarlas es un aspecto importante a resolver por los diseñadores y diseñadoras de esta práctica.

Por último, las funciones de interfaz con el nivel inferior más cercano al SFS, el Sistema de Ficheros Básico (SFB en adelante) pertenecen a este último y se han de conocer a nivel funcional (como cajas negras) por los diseñadores y diseñadoras de esta práctica, puesto que son los puntos de entrada al resto del sistema de ficheros. Habrán de consultar la definición de estas funciones, que se halla a partir de la página 26.

También podrán necesitar las funciones provistas en el Sistema de Bloques Físicos. Por otra parte, existen tres ficheros cabeceras que definen la interfaz con el sistema de ficheros simbólico. Con ello, los ficheros de implementación de esta práctica habrán de incluir los siguientes ficheros cabeceras:

errores.h           códigos de error                                   
sfbasico.h          interfaz del sistema de ficheros básico            
sbfisico.h          interfaz del sistema de bloques físico             
Tipos_SF.h          Tipos de datos del S.F. simbólico                  
Consta_SF.h         Constantes del S.F. simbólico                      
sfsimb.h            interfaz del sistema de ficheros simbólico         

Todos estos ficheros se encuentran en el directorio /prac/include. Hay que resaltar que la interfaz de esta práctica ya está definida en sfsimb.h y no se debería alterar.

Seguidamente pasamos a describir la interfaz del sistema de ficheros simbólico.

7.2.1. Crea_Fichero Crea un fichero

#include "Consta_SF.h"

#include "Tipos_SF.h"

int Crea_Fichero (Nombre *nomb, Atributo atrib);

Crea_Fich crea un fichero, cuyo nombre está contenido en una cadena de caracteres apuntada por nomb, y con atributos de acceso especificados por el parámetro atrib, dichos atributos se encuentran definidos en el archivo cabecera Consta_SF.h y son: SOLO_LECTURA y SOLO_ESCRITURA. Si la función tiene éxito, entonces retornará 0, en caso contrario, retornará - 1 y la variable global Error_SF tomará alguno de los valores siguientes:

ERRFS_EXISTE Fichero ya existente.

ERR_MAXFICH Todas las entradas de directorio ya están asignadas.

ERRFS_EATRIB Especificación de atributo inválida.

7.2.2. Borra_Fichero Borra un fichero

#include "Consta_SF.h"

#include "Tipos_SF.h"

int Borra_Fichero(Nombre *nomb);

Borra_Fichero borre un fichero, cuyo nombre está contenido en una cadena de caracteres apuntada por nomb. Si la función tiene éxito, entonces retornará 0, en caso contrario, retornará - 1 y la variable global Error_SF tomará alguno de los valores siguientes:

ERR_NOEXISTE Fichero ya existente.

ERR_MAXFICH Todas las entradas de directorio ya están asignadas.

ERRFS_EATRIBU Especificación de atributo inválida.

7.2.3. Abre_Fich Abre un archivo

#include "Consta_SF.h"

#include "Tipos_SF.h"

int Abre_Fich(Nombre *nomb, Acceso acces);

Abre_Fichero realiza una apertura de un fichero, cuyo nombre se especifica mediante nomb, para ser accedido según el modo especificado por el parámetro acces, los modos de acceso básicos permitidos son:

LECTURA Sólo lectura.

ESCRITURA Sólo escritura.

BLOQUEO Apertura con bloqueo.

También se permiten combinaciones de los modos anteriores mediante el uso del operador | (OR), por ejemplo: LECTURA | BLOQUEO (apertura para accesos sólo lectura con bloqueo) o LECTURA | ESCRITURA (apertura para accesos de lectura y escritura). Si la función se ejecuta con éxito, entonces retornará un valor entero no negativo que se corresponde con el índice a una entrada en la Tfu que se ha asociado al archivo abierto como consecuencia de la ejecución sin error. Si se produce un error durante la ejecución de esta función, entonces retornará - 1 y la variable global Error_SF tomará alguno de los siguientes valores:

ERR_NOEXISTEE Fichero no existente.

ERR_RANGO No hay entradas disponibles en la tabla Tu.

ERR_RANGO No hay entradas disponibles en la tabla Tfas.

ERRFS_EACC Tipo de acceso inválido o no permitido.

ERR_ACCESO Fichero bloqueado.

7.2.4. Posiciona_Puntero Actualiza el puntero de fichero para un próximo acceso

#include "Consta_SF.h"

#include "Tipos_SF.h"

int Posiciona_Puntero(int fd, Posfich punt);

Posiciona_Puntero actualiza el valor de puntero de acceso del archivo referenciado por fd a la posición indicada por el parámetro punt. Si la función se ejecuta sin error, entonces devolverá un valor no negativo igual a punt, en cambio si un error ocurre, entonces devolverá - 1 y la variable global del sistema Error_SF tomará alguno de los valores siguientes:

ERR_EFD Descriptor de fichero inválido.

ERR_PUNTERO Nuevo valor del puntero de acceso inválido.

7.2.5. Escribe_Fichero Escribe una cadena de bytes en un fichero

#include "Consta_SF.h"

#include "Tipos_SF.h"

int Escribe_Fichero(int fd, void *buff, int nbytes);

Escribe_Fichero transfiere nbytes desde la posición apuntada por buff al fichero asociado al descriptor de fichero fd, los bytes escritos se ubicarán a partir de la posición indicada por el valor actual del puntero del fichero y el puntero a fichero se incrementará en un valor de nbytes. Si la función se ejecuta con éxito, entonces retornará un valor entero positivo igual a nbytes, en caso contrario retornará -1 y la variable global Error_SF tomará alguno de los siguientes valores:

ERR_EFD Descriptor de fichero inválido.

ERRFS_EACC Acceso no permitido.

ERR_ESCRIBE Error en la escritura del fichero.

7.2.6. Escribe_Fichero Escribe una cadena de bytes en un fichero.

#include "Consta_SF.h"

#include "Tipos_SF.h"

int Lee_Fichero(int fd, void *buff, int nbytes);

Lee_Fichero transfiere nbytes desde el fichero asociado al descriptor de fichero fd a la posición de memoria apuntada por buff, los bytes se leerán a partir de la posición indicada por el valor actual del puntero del fichero. Si la función se ejecuta con éxito, entonces retornará un valor entero positivo igual a nbytes y el puntero del fichero se incrementará en nbytes, en caso contrario retornará -1 y la variable global Error_SF tomará alguno de los siguientes valores:

ERR_EFD Descriptor de fichero inválido.

ERRFS_EACC Acceso no permitido.

ERR_LEE Error en la lectura del fichero.

7.2.7. Cierra_Fichero Cierra un archivo

#include "Consta_SF.h"

#include "Tipos_SF.h"

int Cierra_Fichero( int fd );

Cierra_Fichero cierra el fichero referenciado por fd, procediéndose a actualizar tanto los datos como la información de control de memoria a disco. Si la función se ejecuta con éxito, entonces retornará 0, en cambio si un error se produce devolverá -1 y la variable global Error_SF tomará el valor ERR_EFD.

7.2.8. Crea_Sistema Crea un sistema de ficheros

#include "Consta_SF.h"

#include "Tipos_SF.h"

int Crea_Sistema(char *nomb, u_short nbloq, u_short tbloq, u_short nfich);

Crea_Sistema construye un sistema de ficheros en el archivo cuyo nombre se especifica mediante nomb. El parámetro nbloq indica el número de bloques de sistema de ficheros, tbloq especifica la longitud del cada bloque y nfich especifica el número de entradas de ficheros del directorio del sistema a crear. Si la función se ejecuta con éxito, entonces devolverá 0, en caso contrario, retornará -1 y la variable global Error_SF tomará alguno de los valores siguientes:

ERR_FICHERO Error fichero inválido.

ERR_PARAMETRO Número de bloques inválido.

ERR_PARAMETRO Longitud de bloque inválida.

ERR_PARAMETRO Número de entradas de directorio inválido.

7.2.9. Activa_Sistema Activa un sistema de ficheros

#include "Consta_SF.h"

#include "Tipos_SF.h"

int Activa_Sistema( char *nomb, int nbloques_cache );

Activa_Sistema activa un sistema de ficheros contenido en el archivo cuyo nombre se especifica mediante nomb y que fue creado previamente por Crea_Sistema. El parámetro nbloques_cache indica cuántos bloques se desean para la caché.

Si la función se ejecuta con éxito, entonces devolverá el índice a la tabla de sistemas de ficheros activos asignado, en caso contrario, retornará -1 y la variable global Error_SF tomará alguno de los valores siguientes:

ERR_FICHERO Error fichero inválido.

7.2.10. InformeSFS Imprime un informe sobre el sistema

#include "Consta_SF.h"

#include "Tipos_SF.h"

void InformeSFS (void);

InformeSFS imprime en la salida estándar un informe con las estadísticas y estado general del Sistema de Ficheros Simbólico.

Esta función no devuelve nada y no produce ningún error.