Capítulo 6. Sistemas de Archivos

Windows NT soporta cuatro tipos de sistemas de archivos (SF) disintos, y puede trabajar con los cuatro a la vez (un disco con varias particiones, en cada una un SF distinto). Son los siguientes:

Sistema de Archivos

 Sistemas Operativos Soportados

FAT

 DOS, Windows NT, y OS/2

HPFS

 OS/2 y Windows NT

NTFS

 Windows NT

CDFS

 Windows NT

NTFS (New-Technology File System): Es el sistema de archivos nativo de Windows-NT. Como características podemos señalar:

FAT (File Allocation Table): El que usa MS-DOS y Windows 16 bits. Es el SF más pobre, y es se mantiene para dar soporte a las aplicaciones DOS.


Figura 3. Propiedades de los Sistemas de Archivos FAT y NTFS

HPFS (High-Performance File System): Es el que usa el sistema operativo OS/2. Se ha incluido para dar soporte a las aplicaciones OS/2 y complementar así al subsistema del mismo nombre. No es capaz de recomponerse del todo bien después de una caída del sistema ni de asegurar la no corrupción de los datos.

CDFS (CD-ROM File System): Es un SF que Microsoft ha desarrollado exclusivamente para montarse sobre los CD-ROM.

Vamos a centrarnos en el más importante de los cuatro: NTFS. Este sistema de archivos lleva incorporados muchos conceptos de teoría de bases de datos relacionales. 

Proporciona la seguridad, recuperación y tolerancia a fallos en base a la redundancia de datos. De hecho, implementa los cinco primeros niveles del seudo-estándar RAID. RAID significa Redundant Arrays of Inexpensive Disks, algo así como "vectores redundantes de discos baratos". Actualmente, el costo de los almacenamientos masivos o secundarios es ínfimo, y ya se pueden encontrar discos de varios Gb por menos de S/.500.00.

A eso se refieren, a mi entender, las siglas. RAID es una "norma" de facto que se basa fundamentalmente en conseguir la integridad de los datos a base de dividirlos en pedazos y repartir los pedazos entre varios discos, junto con informaciones redundantes de comprobación de errores. Cada nivel ofrece una estrategia distinta para conseguir la integridad. Los niveles son:

En NTFS, al igual que en los SF de UNIX, existe una serie de permisos sobre archivos y directorios, que son los siguientes: lectura (R), escritura (W), ejecución (X), borrado (D), cambio de permisos (P) y ser el nuevo propietario (O). Todo archivo y directorio tienen un propietario, que puede conceder permisos sobre ellos. El Administrador del sistema puede tomar la propiedad de cualquier archivo o directorio sobre NTFS, pero no transferirla de nuevo a ningún otro usuario, a diferencia de UNIX, ni siquiera a su dueño original.

Sistemas de Archivos

 Ventajas

 Desventajas

FAT

Poco consumo de sistema 
El mejor para discos y/o particiones de menos de 200MB

El rendimiento decrece con particiones de más 200MB. No se pueden aplicar permisos sobre archivos y directorios.

HPFS

El mejor para discos y/o particiones entre 200 y 400 MB 
Elimina la fragmentación almacenando en un solo bloque el archivo completo

 No es eficiente para menos de 200MB.No soporta hot fixing. No se pueden aplicar permisos sobre archivos y directorios.

NTFS

 El mejor para volúmenes de 400MB o más Recuperable (registro de transacciones), diseñado para no ejecutarle utilerías de reparación. 
Es posible establecer permisos y registro de auditoria sobre archivos y directorios

 No recomendable para volúmenes de menos de 400MB. Consume de 1 a 5 MB de acuerdo al tamaño de la partición.

 

Ventajas y Desventajas de los Sistemas de Archivos

A continuación vamos a comentar las llamadas al sistema más usuales para crear archivos, leer de ellos, escribir en ellos, etc.

A. Creación/Apertura de Archivos

Para crear/abrir un archivo se usa la llamada al sistema

HANDLE CreateFile (LPTSTR lpszName, DWORD fdwAccess, DWORD fdwShareNode, LPSECURITY_ATTRIBUTES lpsa, DWORD fdwCreate, DWORD fdwAttrsAndFlags, HANDLE hTemplateFile);

lpszName: nombre del archivo a crear o abrir.

fdwAccess: especifica el modo de acceso al archivo: lectura (GENERIC_READ), escritura (GENERIC_WRITE), o ambos.

fdwShareMode: permisos que tendrá la compartición del archivo a abrir: 0 (ningún proceso podrá abrirlo hasta que nosotros lo cerremos), FILE_SHARE_READ (otros procesos pueden abrirlo pero sólo para leer), FILE_SHARE_WRITE (sólo para escribir; no se suele usar), o una combinación de ambos.

lpsa: la típica estructura de seguridad de todo objeto en Windows NT. Sólo tendrá sentido si el archivo se crea en un SF que soporte seguridad, como NTFS.

fdwCreate: una serie de indicadores que especifican una acción:

CREATE_NEW: crea un archivo nuevo, y da error si ya existe


CREATE_ALWAYS: crea un archio nuevo,y si existe lo machaca

OPEN_EXISTING: abre un archivo, y da error si no existe

OPEN_ALWAYS: abre un archivo y si no existe lo crea

TRUNCATE_EXISTING: si el archivo exisye, lo abre pero truncando su tamaño a 0 bytes; si no existe, da una error.

fdwAttrsAndFlags: sirve para dar atributos al archivo (sólo si lo estamos creando) y activar ciertas banderas. Veamos algunos.
Atributos:

FILE_ATTRIBUTE_HIDDEN: archivo oculto
FILE_ATTRIBUTE_NORMAL: archivo sin atributos especiales
FILE_ATTRIBUTE_READONLY: archivo de sólo lectura
FILE_ATTRIBUTE_SYSTEM: archivo del sistema
FILE_ATTRIBUTE_TEMPORARY: archivo temporal; el Executive intentará mantenerlo en RAM tanto como le sea posible
FILE_ATTRIBUTE_ATOMIC_WRITE: para indicar que los datos de este archivo son críticos; eso hará que el Executive aumente la frecuencia con que escribe estos datos de RAM a disco.

A propósito de los dos últimos indicadores, comentar que el Executive no escribe a disco inmediatamente los cambios realizados a un archivo en RAM, pues degradaría las prestaciones del sistema. Usa un mecanismo de escritura diferida, de manera que se escribe a disco cuando se cierra el archivo, o cuando el sistema está desocupado, o cuando es necesario hacer swapping. Para los archivos cuyos datos son críticos, necesitamos que las modificaciones sean escritas rápidamente a un soporte no volátil, por si el sistema se cayera.


Banderas:

FILE_FLAG_NO_BUFFERING: con esta bandera le indicamos al Executive que no gestione buffers de memoria con relación a la entrada/salida de este archivo, sino que lea y escriba directamente a disco.

FILE_FLAG_RANDOM_ACCESS: queremos acceso directo al archivo.

FILE_FLAG_SEQUENTIAL_SCAN: acceso secuencial.

FILE_FLAG_WRITE_THROUGH: con este indicador, el SO enviará a disco los datos que hayan sido modificados en memoria, pero los mantendrá en memoria para acelerar las lecturas

FILE_FLAG_POSIX_SEMANTICS: acceso al archivo según el estándar POSIX (por ejemplo, sensible al tipo de letra)

FILE_FLAG_BACKUP_SEMANTICS: cuando un proceso solicita abrir un archivo, el SO normalmente realiza ciertos tests de seguridad para comprobar si el proceso tiene o no los permisos necesarios. Con este indicador se anulan ciertos tests, de manera que el SO comprueba si el proceso tiene permiso para acceder al archivo, y si es así, le permite el acceso pero sólo para realizar una copia de seguridad. Aunque tenga permiso de escritura, le será anulado.

FILE_FLAG_OVERLAPPED: los accesos a los archivos suelen hacerse de manera síncrona (el subproceso duerme hasta que se consuma el acceso). Con este indicador se permite realizar E/S asíncrona, con lo que el subproceso seguirá ejecutándose y el SO le informará cuando la E/S finalice.

hTemplate: el descriptor de un archivo ya abierto; si no es NULL, los atributos y banderas de dicho archivo serán asignados a los de nuestro archivo.

La llamada retorna el descriptor al objeto archivo, o -1 si hubo algún error.

B. Cierre de Archivos

Se usa la misma llamada que para cerrar un descriptor a cualquier objeto. El Sistema Operativo decrementará el contador de uso del archivo, y si es 0 lo cerrará definitivamente.

BOOL CloseHandle (HANDLE hObject);

C. Lectura/escritura a archivos

Windows NT permite que los subprocesos hagan E/S a archivos de manera síncrona o asíncrona. EL modo síncrono es el habitual: un subproceso inicia una operación de E/S sobre un archivo; el Executive lo pondrá a dormir hasta que esa E/S de complete. En cambio, en el modo asíncrono, el subproceso que inicia la operación puede seguir ejecutándose, y cuando necesite los datos de la E/S se pondrá voluntariamente a esperar, de manera que si para ese tiempo la E/S se ha completado, obtendrá los resultados inmediatamente.

Se usan las mismas llamadas al sistema para ambos tipos de acceso. En dichas llamadas existirá un parámetro de entrada (lpOverlapped) que, si es NULL, indicará que la llamada es acceso síncrono; si no, indicará acceso asíncrono, dando la dirección de una estructura OVERLAPPED que tiene el siguiente formato:

typedef struct _OVERLAPPED{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
DWORD hEvent;
} OVERLAPPED;

Internal: cuando la E/S se completa, el sistema guarda en esa palabra un estado interno.

InternalHigh: cuando la E/S se completa, el sistema guarda ahí el número de bytes transferidos.

Offset,OffsetHigh: indican la posición del byte del archivo donde queremos comenzar el acceso.

hEvent: el descriptor (opcional) de un objeto suceso.

Vamos a suponer que un subproceso A desea realizar E/S asíncrona sobre un archivo, y para ello inicializa el parámetro lpOverlapped de la llamada con la dirección correspondiente a una estructura OVERLAPPED. Entonces sigue ejecutándose. Cuando al Executive le llega la petición de E/S sobre el archivo, pone el estado de este objeto a no señalado. Cuando la E/S finaliza, lo pone a señalado (esto ya lo vimos en el capítulo de los procesos). En algún momento, A necesita los datos de la E/S que inició, con lo que se pone a esperar a que el objeto archivo se ponga a estado señalado (para lo cual usará una llamada tipo WaitFor...Object(s), como ya explicamos). Si, en el momento de hacer la llamada a E/S, el subproceso A hubiera especificado en hEvent un descriptor a un suceso, el Executive pondría a señalados tanto el objeto archivo como dicho objeto suceso, con lo cual el subproceso A tendría la facilidad de esperar por cualquiera de los dos.

La utilidad de esto es en la situación de que el archivo es compartido, y varios subprocesos pueden estar haciendo E/S sobre él y, por consiguiente, poniéndose a estado señalado/no señalado múltiples veces. Especificando un objeto suceso propiedad de A, dicho subproceso sabrá que cuando el suceso esté señalado, seguro que se ha completado su operación de E/S y no la de otro.

Una vez explicados estos matices, pasamos sin más a describir el perfil de las llamadas de lectura/escritura, que son muy parecidas a las que usa UNIX:

BOOL ReadFile (HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);

hFile: es el descriptor al archivo.

lpBuffer: apunta a un área de memoria donde la llamada colocará los datos leídos

nNumberOfBytesToRead: es el número de bytes a leer.

nNumberOfBytesRead: es un parámetro de salida que indica el número de bytes que se leyeron en realidad.

lpOverlapped: elige modo síncrono/asíncrono; ya comentado

BOOL WriteFile(HANDLE hFile, CONST VOID * lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped);

hFile: es el descriptor al archivo.

lpBuffer: apunta a un área de memoria donde se encuentran los datos a escribir.

nNumberOfBytesToWrite: número de bytes a escribir.

nNumberOfBytesWritten: número de bytes escritos en realidad.

lpOverlapped: elige modo síncrono/asíncrono; ya comentado.

Nos gustaría señalar que en los accesos síncronos existe un puntero de lectura/escritura sobre un archivo para cada subproceso que esté accediendo al mismo. Ese puntero lo asigna el SO en el momento de apertura del archivo cuando un subproceso lo abre para accesos síncronos (o sea, sin especificar el indicador FILE_FLAG_OVERLAPED). El puntero se modifica al leer y al escribir. En el caso de que el acceso síncrono sea también acceso directo, entonces es posible cambiar el puntero usando la llamada SetFilePointer, que no merece la pena comentar.


Nótese que en los accesos asíncronos no existe el puntero, por lo que hemos de indicar la dirección de inicio de cada acceso (recordemos que se indica en la estructura OVERLAPPED).

Existen unas llamadas de lectura/escritura extendidas (ReadFileEx y WriteFileEx), que son muy útiles a la hora de realizar una E/S asíncrona. Permiten que se les pase la dirección de una función de forma que, cuando la E/S asíncrona se complete, se salte a la ejecución de esa función. Para ello, el subproceso ha de estar esperando por el fin de la E/S con una función WaitFor...Object(s) extendida (WaitForSingleObjectEx o WaitForSingleObjectsEx). De hecho, WaitForSingleObject está construida internamente como una llamada a WaitForSingleObjectEx pasándole un parámetro que indica que no queremos uso extendido.

D. Atributos de Archivos

Los atributos que se indican al crear un archivo en el parámetro fwdAttrsAndFlags pueden ser consultados con una llamada a:

BOOL GetFileInformationByHandle (HANDLE hFile, LPBY_HANDLE_FILE_INFORMATION lpFileInformation);

Esta llamada devuelve en la estructura apuntada por lpFileInformation toda la información relativa al archivo cuyo descriptor es hFile. La estructura tiene los siguientes campos:

typedef struct _BY_HANDLE_FILE_INFORMATION {
DWORD dwFileAtributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD dwVolumeSerialNumber;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD nNumberOfLinks;
DWORD nFileIndexHigh;
DOWRD nFileIndexLow;
} BY_HANDLE_FILE_INFORMATION;

dwFileAttributes: los atributos del archivo que se pasaron en el parámetro fdwAttrsAndFlags en el momento de su creación (ver CreateFile)

ftCreationTime: fecha de creación del archivo

ftLastAccessTime: fecha del último acceso al archivo


ftLastWriteTime: fecha de la última escritura al archivo

dwVolumeSerialNumber: número de serie del volumen donde se encuentra el archivo

nFileSizeHigh, nFileSizeLow: 64 bits que indican el tamaño del archivo; por tanto, Windows NT permite archivos de hasta 264 bytes

nNumberOFLinks: número de enlaces del archivo; este parámetro asegura la compatibilidad con el estándar POSIX. Recordemos que en UNIX cada archivo tiene un número de enlaces que indican los distintos nombres que referencian al mismo archivo dentro del sistema de archivos. Así se evita la redundancia de datos.

nFileIndexHigh, nFileIndexLow: es un identificador único que el Executive asocia a un archivo en el momento de que algún subproceso lo abre. Si el sistema se apaga y se enciende, el identificador puede no ser el mismo. Sin embargo, en la misma sesión, procesos distintos leerán el mismo identificador. Esto es útil para determinar si dos descriptores distintos referencian en realidad al mismo archivo dentro de un volumen (basta hacer sendas llamadas a GetFileInformationByHandle y ver si el identificador y número de volumen coinciden en las estructuras devueltas por cada una).

E. Bloqueo de Archivos

Otra llamada muy importante es la que permite el bloqueo de todo o parte de un archivo, de manera que ningún otro subproceso pueda acceder a la región bloqueada. 

La llamada para bloquear es:

BOOL LockFile (HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFIleOffsetHigh, DWORD cbLockLow, DWORD cbLockHigh);

hFile: descriptor al archivo a bloquear.

dwFileOffsetLow, dwFileOffsetHigh: dirección de comienzo de la región a bloquear.

cbLockLow, cbLockHigh: tamaño de la región a bloquear.

La llamada para desbloquear es:

BOOL UnlockFile (HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFIleOffsetHigh, DWORD cbUnlockLow, DWORD cbUnlockHigh);

Los parámetros son análogos a los anteriores. Es necesario que un subproceso desbloquee un área que previamente bloqueó antes de que finalice. En caso contrario, estará impidiendo el acceso al archivo a todos los demás subprocesos.

Existen versiones extendidas de ambas llamadas (LockFileEx y UnlockFileEx) que permiten establaces bloqueos pero sólo de escritura (otros subprocesos podrán leer el área bloqueada, pero no escribir en ella).