File-System en sistemas operativos

El File-System en general lo vamos a encontrar siempre como parte del kernel en sistemas monolíticos. Es aquel que da servicio de uso de archivos al S.O, a los usuarios y a los aplicativos.

Características del File-System

-Es un módulo del sistema operativo

-Maneja el almacenamiento secundario (almacenamiento persistente) y terciario (almacenamiento que se “pone” y se “saca”, dicho rústicamente, como un USB por ejemplo).

-Sus principales funciones son la asignación de espacio a los archivos, la administración del espacio libre y del acceso a los datos

- Idealmente debe garantizar seguridad y coherencia

Archivo

Un archivo es una colección de información (datos / registros) relacionada, etiquetada con un nombre y almacenada en un medio secundario (en disco).

● Existencia de larga duración

● Compartible entre procesos -> archivo = nombre + permisos asociados

● Estructura

Sus principales atributos son:

● Nombre

● ID

● Tipo

● Ubicación

● Tamaño

● Protección

● Metadata

Un archivo se administra usando un FCB (File Control Block):

Operaciones básicas de un archivo:

● Crear -> Definir un nuevo archivo y posicionarlo dentro de la estructura de archivos

● Leer / Escribir

● Posicionar puntero (seek)

● Eliminar

● Truncar

● Abrir -> Se declara un archivo existente como “abierto” por un proceso, permitiendo al mismo realizar futuras operaciones sobre el archivo.

● Cerrar -> Se cierra el archivo con respecto a un proceso. Dicho proceso no realizará más operaciones sobre el archivo (a menos que lo vuelva a abrir).

Existen otras operaciones, que son combinaciones de las básicas, como por ejemplo: mover, copiar, y renombrar. A estas se las denomina operaciones compuestas.

Bloqueos / Locks

La idea de los bloqueos es poder tener algún tipo de permiso para regular el acceso a un archivo:

➢ Evitar que dos procesan escriban en un archivo simultáneamente

➢ Evitar que un proceso lea información desactualizada

➢ Mantener la integridad de los archivos

En los file-system se usan bloqueos y no semáforos, debido a que nosotros no conocemos lo que está haciendo otra persona sobre el archivo, si la otra persona está leyendo o escribiendo o haciendo otra cosa no lo sabemos. En el caso de los procesos yo si conozco los procesos que estoy manejando, por eso puedo usar semáforos.

Tipos de locks

➢ Compartido (lock de lectura) -> Muchos procesos pueden adquirirlo concurrentemente. Un proceso no puede adquirir un lock exclusivo mientras haya uno compartido.

➢ Exclusivo (lock de escritura) -> Un proceso puede adquirirlo por vez. Al adquirirlo, no permite que el resto accedan.

Implementaciones de los locks

➢ Obligatorio (Integridad -> S.O) -> El SO asegura que ningún otro proceso va a poder usar el archivo a menos que cumpla con el lock. Tiene un costo, más tiempo bloqueados los archivos, más tiempo tienen que esperar los procesos que lo necesitan.

➢ Sugerido (Integridad -> Usuario) -> El SO brinda la información del estado del lock pero la responsabilidad de garantizar su integridad queda del lado del programador. Los locks se deberán adquirir y liberar correctamente, sin generar deadlock (el S.O no valida).

Archivos abiertos

¿Qué pasa cuando abrimos un archivo?

Lo que hace el S.O es agarrar el FCB de ese archivo y llevarlo a memoria, para que se pueda acceder a toda esa metadata de manera rápida.

Por otro lado existe una tabla global de archivos abiertos, que se encuentra en memoria. Dicha tabla posee a su vez un contador de entradas que indica para cada archivo, cuántos procesos lo tienen abierto. Si este contador llega a 0, significa que ningún proceso está leyendo ese archivo y por lo tanto esa entrada al archivo se borra, es decir, se saca de memoria y vuelve a estar solo en disco.

A su vez, para cada proceso también tenemos una tabla de archivos abiertos, la cual nos va a indicar en qué modo fue abierto cada archivo (lecto-escritura, solo lectura, etc).

Directorio

Un directorio es un archivo pero especial, porque es de tipo directorio. Los archivos de los que veníamos hablando eran de tipo regular.

El directorio es aquel que contiene un listado de nombres de otros archivos (que también podrían ser directorios) y sus atributos asociados.

Ejemplo de directorio:

Un directorio permite realizar el mapeo entre nombres de archivos (conocidos por usuarios y aplicaciones) y los archivos en sí.

Características y estructuras de los directorios

El primer gráfico es un ejemplo de estructura de un único nivel, y el segundo de un nivel por usuario, que en general no se suelen usar.

Por ejemplo, cuando hacemos “ls” en la terminal de Linux, nosotros estamos leyendo el directorio y se nos está mostrando una representación del mismo.

Estructuras que sí se suelen usar son las siguientes:

La mayoría de los file-systems se inclinan por el lado de los grafos acíclicos, ya que lo que pasa con el grafo general es que pueden apuntar a los mismos directorios, como pasa con el 1er gráfico, lo cual no pasa con los acíclicos.

Implementación de directorios

Necesitamos poseer indispensablemente el nombre del archivo y el enlace al mismo -> también se puede requerir que se agreguen otros atributos para no tener que acceder al FCB.

Formas primitivas de encarar la implementación:

● Lista lineal:

o Es fácil de programar

o Tiene problemas al borrar una entrada, ¿cómo se reutiliza esta entrada? -> hay que escribir allí la última entrada

o Problemas para crear un archivo -> debo fijarme si no hay otro nombre igual

o Requiere lectura secuencial, no permite ordenamiento

● Lista enlazada:

o Mejora el problema de eliminar un archivo

o Puede estar ordenada -> facilita el estado -> pero difícil mantenimiento del orden (pueden haber diferentes criterios para ordenar)

En conclusión, la lista lineal es más sencilla pero tiene más complicaciones, como no poder ordenarse. La lista enlazada tiene como una lista de punteros que nos permite leer ordenadamente.

Formas actuales de encarar la implementación:

● Árbol-B

o Tiene una estructura más sofisticada

o Tiene menos niveles, por ende el acceso es más rápido

o Es fácil de eliminar y para agregar nodos

● Tabla Hash

o Entradas de directorio en una lista lineal + una estructura adicional = tabla hash

o F. Hash (nombre del archivo) -> entrada en la lista de directorio por lo cual el acceso termina siendo más rápido (no secuencial)

o Puede haber colisiones que se solucionan con una lista enlazada en vez de un valor único, en donde se compara el nombre del archivo

Protección de archivos Tipos de acceso:

➔ Acceso libre (no se aplica ninguna estrategia de protección)

➔ Acceso prohibido (solo el dueño puede acceder a sus archivos). El problema de este tipo de acceso es el cómo implementarlo, es complejo.

➔ Acceso controlado (indica quien puede operar y cómo)

o Lectura / Escritura / Ejecución / Adición / Borrado / Listado / etc, son operaciones que vamos a poder limitar.

Implementaciones:

➔ Para el acceso libre, se implementa “Propietario / Grupo / Universo”, ya que generalmente no se necesita saber todos los permisos de cada usuario, con que haya uno para el propietario, otro para el grupo, y otro para el universo, basta y sobra.

➔ Para el acceso prohibido se implementa una Matriz de acceso (usuario, recurso) -> permisos. Tiene mucha mayor granularidad pero desperdicia mucho espacio.

➔ Para el acceso controlado, se implementa una lista de control de acceso (ACL)

o Cada archivo tiene una lista de usuarios y permisos.

o Construir la lista es tedioso, complejo, aunque ocupa menos espacio que en la matriz de acceso. Es necesario conocer a todos los usuarios

Ejemplo de ACL:

Dato extra: Una ventaja que plantea PGU (Propietario / Grupo / Universo) es que ocupa bastante poco espacio comparado con la técnica de matriz de acceso que pierde mucho espacio dado que se utiliza una sola tabla para todos los archivos del sistema, aparte si se agrega un nuevo archivo siempre se debe agregar una nueva columna. Lo mismo si se agrega un nuevo usuario.

Una desventaja de PGU es que no tiene granularidad por usuario, si se quiere ser más específico se lo tiene que complementar con una ACL. La matriz de accesos permite Archivos mapeados a memoria

● Objetivo: Tratar la E/S de archivo como si fueran accesos a memoria.

● Asociar lógicamente con el archivo una parte del espacio virtual de direcciones del proceso

○ Se mapea cada bloque de disco sobre una página(s) de memoria.

● Una vez cargada la página, las subsiguientes lecturas y escrituras en el archivo se gestionarán como accesos normales de memoria

Mapear un archivo en memoria, es básicamente agarrar ese archivo, y así como está dividido en bloques (por lo general de 4KB), mapearlo a páginas de 4KB, y de esa manera tomar todo ese archivo como si fuera espacio de SWAP. De esta manera cuando el proceso me pida utilizar dicho archivo, lo voy a swapear a memoria y va a ser mucho más veloz que tenerlo en disco y tener que hacer todo el procedimiento que vimos antes de archivos abiertos.

Cuando se mapea un archivo se hace de cuenta que el archivo es parte de la memoria del proceso.

Organización de los datos en el discoCuando uno instala Windows en realidad lo que está haciendo es, sobre la partición de un disco, formatear un volumen. Ese volumen va a tener una metadata y va a estar organizado de tal manera dependiendo del file-system que tenga el S.O, en el caso de Windows será NTFS, y en el caso de Linux será alguna variación de Xtend.

Una razón por la que podríamos hacer particionamiento es para tener más filesystems a disposición.

En línea general, lo que guardamos en nuestras particiones son archivos. En Linux por ejemplo, se trata a todo como un archivo.

Asignación de espacio en disco a archivo

Las particiones en disco siempre son fijas.

Métodos de acceso a disco

Asignación de bloques Asignación de bloques - Contigua

Más ventajas:

➢ Lectura rápida, debido a que requiere menos reposicionamientos del cabezal (siempre que hablamos del cabezal nos vamos a referir al de los discos rígidos o también llamados HDD´s) para escritura y lectura

➢ Si se daña un bloque no pierdo al resto Más desventajas:

➢ La fragmentación externa es a causa de la asignación previa. A medida que se asignan y borran archivos, el espacio libre del disco se descompone en pequeños fragmentos

➢ Sufre de fragmentación interna en el bloque final

➢ Se ocupa espacio de los bloques para guardar los punteros allí Asignación de bloques – Enlazada / Encadenada

En vez de tener los bloques todos juntos, cada bloque tiene un puntero al final que me indica cuál es el próximo bloque, con lo cual solo necesito saber el bloque inicial. Esto me resuelve el problema de la fragmentación externa.

Dentro de las desventajas agregamos que: puede que tenga fragmentación interna (el último bloque puede que no se ocupe totalmente); hay un pequeño desperdicio de espacio por los punteros; es malo para el acceso directo porque hay que leer todos los bloques anteriores.

Esta asignación a nivel lógico es mejor pero a nivel físico es más lenta. El problema acá es que si me falla un bloque, no voy a poder saber cuáles son los bloques que le sigue.

Asignación de bloques – Indexada

Para resolver el problema de la asignación enlazada, en vez de tener un puntero en cada bloque, puedo tener un bloque de punteros.

Lo que pasa con esta asignación, es que por un lado va a aumentar un poco la fragmentación interna, y por otro nos va a limitar severamente el tamaño de nuestros archivos. Para suerte nuestra, esto último se puede solucionar teniendo bloques de punteros multinivel:

Gestión de espacio libre

Generalmente usamos un bit vector que por cada bloque lógico tiene un bit que me dice si está libre o está ocupado.

Journaling

Para mantener la integridad de cada archivo utilizamos Journaling. ¿De que nos va a servir? Bueno, básicamente si por ejemplo se nos apaga la PC mientras estamos escribiendo sobre un archivo (mientras realizo la OpA3), y no llegamos a commitear, cuando volvamos a encenderla, lo que va a ocurrir es que se va a generar el proceso inverso al que se vino haciendo, es decir, que si estábamos en la OpA3 y no llegamos a completarla, las operaciones OpA2 y OpA1 van a hacer el proceso inverso al que hicieron.

Si por ejemplo yo si llegué a commitear significa que quedé en un estado consistente, entonces cuando la PC se prenda de nuevo, no se va a generar el proceso inverso, sino que va a seguir con la OpB1.