Sistemas de archivos

"Un sistema de archivos es un mapa que el SO mantiene del caos magnético del disco."

Qué vas a aprender en este capítulo

Tu disco — sea HDD, SSD o NVMe — es físicamente un montón de bloques de 512 bytes (o 4 KB). El SO te muestra archivos con nombres, en directorios, con permisos, con fechas, con metadata. La pieza que hace ese mapeo es el sistema de archivos. Vas a entender la abstracción que da, cómo se almacenan los archivos por dentro (inodos, bloques, asignación), cómo los SO modernos garantizan consistencia ante fallos con journaling, y por qué ls o cat no son operaciones triviales.

6.1 La idea: nombres, jerarquía, metadata

💡 Intuición

Si te diéramos un disco crudo, sin SO, lo único que tendrías sería: "el bloque 0 vale tales bytes, el 1 tales, ..." y al apagar la máquina, todo desaparece o queda intacto sin organización útil. Encontrar tu carta de amor entre 100 millones de bloques sería imposible.

Necesitamos:

  1. Nombres simbólicos ("carta-mama.txt") en vez de números de bloque.
  2. Organización jerárquica (directorios anidados) para no tener un solo namespace gigante.
  3. Persistencia confiable — los archivos siguen ahí mañana, incluso si se cortó la luz.
  4. Concurrencia segura — varios procesos pueden leer/escribir sin pisarse.
  5. Metadata — quién es dueño, cuándo se creó, permisos.

Un sistema de archivos es una base de datos especializada para estos requisitos. Es la base de datos más usada del mundo: la usás cada vez que abrís cualquier programa.

Hay decenas: FAT32, exFAT, NTFS (Windows), ext2/3/4, XFS, Btrfs, ZFS, HFS+ y APFS (Mac). Cada uno tiene compromisos distintos: velocidad, robustez, capacidad máxima, soporte de features.

📜 Historia

El concepto moderno nació con el Multics File System (1965). Su diseño — archivos como secuencia de bytes, jerarquía de directorios, enlaces simbólicos y duros, permisos — pasó a Unix y de ahí al mundo entero.

FAT (1977, Microsoft) fue dominante por su simplicidad. Sirvió bien hasta los 2000s pero no escaló — discos de >2 TB lo desbordan. NTFS (1993) lo reemplazó en Windows. ext evolucionó en Linux: ext2 (1993), ext3 (2001) con journaling, ext4 (2008) extents, Btrfs y ZFS modernos con snapshots y checksums.

La cosa más interesante: POSIX estandarizó la API (open, read, write, close, paths con /) en los 80, así que tu programa funciona igual en ext4, NTFS (a través de WSL), HFS+ — el SO traduce. Esa es la fuerza de la abstracción.

6.2 La abstracción: archivo como secuencia de bytes

📐 Fundamento

En Unix, un archivo es una secuencia de bytes con nombre. Sin más estructura impuesta. Si vos querés que sea un CSV, JPG, mp3, EXE — el SO no sabe ni le importa. Es tu programa el que interpreta los bytes.

Otros SO (mainframes IBM antiguos) imponían registros estructurados — el SO te daba "el registro 5 del archivo de empleados". Era poderoso pero rígido. Unix eligió flexibilidad: bytes crudos, estructura por convención (extensiones, magic bytes).

API estándar (POSIX):

int fd = open("archivo.txt", O_RDONLY);
char buf[256];
ssize_t n = read(fd, buf, 256);
close(fd);

open te da un file descriptor (fd) — un entero pequeño que el kernel usa para identificar tu archivo abierto.

Operaciones básicas:

Syscall Para qué
open(path, flags) Abre archivo, devuelve fd
close(fd) Cierra
read(fd, buf, n) Lee n bytes en buf
write(fd, buf, n) Escribe n bytes desde buf
lseek(fd, off, whence) Mueve la "posición" actual
stat(path, &buf) Metadata (tamaño, permisos, fecha)
unlink(path) Elimina (decrementa enlaces)
rename(viejo, nuevo) Renombra
chmod(path, mode) Cambia permisos

File descriptors especiales:

  • 0 = stdin.
  • 1 = stdout.
  • 2 = stderr.

Por eso printf (que escribe a stdout) realmente es un wrapper alrededor de write(1, ...).

6.3 Directorios y paths

📐 Fundamento

Un directorio es un archivo especial que contiene una tabla de entradas: nombre + referencia a otro inodo (archivo o subdirectorio).

/
├── home/
│   ├── ana/
│   │   ├── docs/
│   │   │   └── tesis.pdf
│   │   └── fotos/
│   └── beto/
├── etc/
│   └── passwd
└── usr/
    └── bin/
        └── ls

Paths absolutos empiezan con /: /home/ana/docs/tesis.pdf. Paths relativos son respecto al directorio actual (pwd): docs/tesis.pdf.

Especiales:

  • . = directorio actual.
  • .. = directorio padre.
  • ~ = home del usuario (es del shell, no del FS).

Path resolution. Cuando hacés open("/home/ana/docs/tesis.pdf"), el kernel:

  1. Empieza en / (root, inodo conocido).
  2. Busca home en root → encuentra inodo de /home.
  3. Busca ana en /home → inodo de /home/ana.
  4. Busca docs → inodo de /home/ana/docs.
  5. Busca tesis.pdf → inodo del archivo.

Si en cualquier paso no encuentra, error (ENOENT, "no such file"). Si no tenés permisos para entrar a un directorio, error (EACCES).

Todo eso es caro — un acceso por nivel. Por eso el SO mantiene una dentry cache que recuerda traducciones de path → inodo recientes.

6.4 Inodos: la metadata real

📐 Fundamento

En Unix, cada archivo tiene un inodo (index node) — una estructura del FS que guarda toda la metadata del archivo, menos el nombre.

El nombre vive en el directorio que lo contiene. El inodo vive aparte. Esa separación permite:

  • Hard links: dos nombres distintos apuntando al mismo inodo.
  • Renombrar = cambiar el nombre en el directorio sin tocar el inodo.

Contenido típico de un inodo:

Campo Para qué
modo Tipo (archivo, directorio, link...) y permisos
uid, gid Dueño, grupo
tamaño Bytes
timestamps atime (acceso), mtime (modificación), ctime (cambio metadata)
número de enlaces Cuántos directorios apuntan a este inodo
bloques de datos Lista de bloques físicos donde está el contenido

Comando ls -li muestra el número de inodo:

$ ls -li
total 4
12345 -rw-r--r-- 1 ana users 1234 may  5 10:00 archivo.txt

12345 es el inodo. 1 es el conteo de hard links.

Hard link:

$ ln archivo.txt copia.txt
$ ls -li
12345 -rw-r--r-- 2 ana users 1234 may  5 10:00 archivo.txt
12345 -rw-r--r-- 2 ana users 1234 may  5 10:00 copia.txt

Mismo inodo (12345), dos nombres, conteo de enlaces = 2. Si borrás archivo.txt, copia.txt sigue accediendo al mismo dato. El inodo solo se libera cuando el conteo llega a cero.

Symbolic link (soft link):

$ ln -s archivo.txt simbolico.txt
$ ls -li
12345 -rw-r--r-- 1 ana users 1234 may  5 archivo.txt
12346 lrwxrwxrwx 1 ana users   12 may  5 simbolico.txt -> archivo.txt

simbolico.txt es otro archivo, con su propio inodo, cuyo contenido es la cadena "archivo.txt". Si borrás archivo.txt, el simbólico queda roto (apunta a algo inexistente).

Diferencia clave:

Hard link Symbolic link
Mismo inodo Inodo distinto
Solo dentro del mismo FS Puede cruzar FS
No puede a directorios Puede a directorios
Sigue valiendo si se borra el original Queda roto

6.5 Cómo se guarda un archivo en disco

📐 Fundamento

Un disco lo dividimos en bloques (típicamente 4 KB). El sistema de archivos asigna bloques a archivos. Hay varios esquemas:

Asignación contigua

Archivo en bloques consecutivos. Rapidísimo de leer (un solo movimiento de cabezal en HDD), pero fragmenta terrible — al borrar archivos quedan agujeros.

Solo se usa en CD-ROM (escritura única).

Lista enlazada

Cada bloque apunta al siguiente. Sin fragmentación, pero lectura aleatoria es lentísima — para llegar al bloque 100 hay que recorrer los 99 anteriores.

FAT (File Allocation Table)

Como lista enlazada, pero la tabla de "siguiente bloque" está concentrada en una zona del disco. Lectura aleatoria es manejable. Limitación: la tabla entera tiene que estar en RAM, lo cual restringe el tamaño máximo del FS.

FAT32: archivos máx 4 GB, FS máx 2 TB.

Inodos con punteros multinivel (Unix clásico, ext2)

El inodo guarda directamente los primeros bloques (típicamente 12). Si el archivo es más grande, usa bloques indirectos (un bloque que es un array de punteros), doble indirecto (un bloque que apunta a bloques indirectos), triple indirecto.

Inodo:
  bloques directos:    [b0, b1, ..., b11]
  indirecto simple:    [→ bloque que apunta a b12..bN]
  indirecto doble:     [→ bloque → bloques con punteros]
  indirecto triple:    [→ → → bloques de datos]

Resultado: archivos chicos son rapidísimos (solo el inodo), grandes funcionan razonablemente.

Extents (ext4, NTFS, XFS)

En lugar de listar bloque-por-bloque, registrar rangos: "del bloque 1000 al 1999, todo es de este archivo". Mucho más compacto para archivos grandes y contiguos.

ext4, NTFS y XFS usan extents. Es el estándar moderno.

6.6 Espacio libre

📐 Fundamento

¿Cómo lleva el FS la cuenta de qué bloques están libres?

Bitmap

Un array de bits, uno por bloque. 0 = libre, 1 = ocupado. Eficiente para encontrar libres consecutivos.

Para un disco de 1 TB con bloques de 4 KB → 250 millones de bits = 31 MB de bitmap. Razonable.

Lista enlazada de libres

Bloques libres encadenados. Más simple, peor para asignar contiguos.

ext4, NTFS, FAT — todos usan bitmaps (con variantes).

Comando df:

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       457G  234G  201G  54% /

6.7 Cache de páginas y buffer cache

🛠️ En la práctica

El disco es mil veces más lento que la RAM. Si cada read fuera al disco real, todo iría lento.

Solución: caché en RAM. Cuando leés un bloque, queda en RAM por si se vuelve a leer. Cuando escribís, se escribe primero a RAM (write-back) y eventualmente al disco.

En Linux, esto se llama page cache (porque usa páginas de 4 KB). Comando:

$ free -h
              total        used        free      shared  buff/cache
Mem:          7.7Gi       2.4Gi       3.1Gi       320Mi       2.2Gi

buff/cache es la memoria que el SO usa para cachear el disco. Es memoria "gratis" — el SO la libera si los procesos la necesitan.

Demo: la primera vez que leés un archivo grande, tarda. La segunda, instantáneo (está en cache).

$ time wc -l /var/log/syslog
real    0m0.523s    # primer acceso

$ time wc -l /var/log/syslog
real    0m0.014s    # del cache

Trade-off escritura. Write-back es rápido pero arriesgado: si se corta la luz antes que la escritura llegue al disco real, perdiste datos. Por eso programas críticos llaman a fsync() para forzar el flush.

6.8 Journaling — sobrevivir caídas

📐 Fundamento

Imaginate que estás escribiendo a un archivo y se corta la luz a medio camino. El estado puede quedar inconsistente:

  • Bloques de datos escritos pero el inodo no actualizado.
  • Inodo apunta a bloques basura.
  • Espacio en el bitmap marcado como libre cuando no lo está.

Eso era pesadilla en sistemas viejos. Ext2 y FAT eran vulnerables; al reiniciar tras corte, había que correr fsck (file system check) que escaneaba el disco entero — minutos u horas en discos grandes.

Journaling lo soluciona. Idea: antes de hacer cualquier cambio, escribir en un log (journal) lo que vas a hacer. Si la operación se interrumpe, al reiniciar el SO rehace o deshace desde el journal.

Pasos típicos para una escritura:

  1. Escribir en journal: "voy a hacer X, Y, Z".
  2. Marcar journal como commit (esta entrada es válida).
  3. Escribir los cambios reales al disco.
  4. Marcar journal como completado.

Si se corta entre 1 y 2: nada cambió, ignorar el journal. Si se corta entre 2 y 3: rehacer desde el journal. Si se corta entre 3 y 4: lo mismo.

ext3, ext4, NTFS, XFS, Btrfs — todos hacen journaling.

Modos en ext4:

  • journal: journaling de datos y metadata. Más seguro, más lento.
  • ordered (default): metadata en journal, datos directo. Buen balance.
  • writeback: solo metadata, ni siquiera ordenado. Más rápido, riesgoso.

Reemplazo moderno: filesystems copy-on-write como ZFS y Btrfs. Nunca sobreescriben — nuevas versiones a otro lado, atómicamente cambian "qué versión está activa". Más robustos pero más complejos.

6.9 VFS — la capa universal

📐 Fundamento

Linux soporta decenas de FS distintos. Tu read() no sabe qué FS estás usando — el kernel lo abstrae con la VFS (Virtual File System).

                     read(fd, buf, n)
                            ↓
                +-----------------------+
                |        VFS            |  ← capa común
                +---+--------+----------+
                    |        |        |
                  ext4      NTFS     XFS    ...
                    ↓        ↓        ↓
                Hardware (disk drivers)

Cada FS implementa una interfaz común (file_operations, inode_operations). VFS dispatcha a la implementación correcta según donde esté el archivo.

Resultado: tu programa funciona igual en ext4 que en NTFS-3g montado vía FUSE. Los detalles del disco están encapsulados.

Mount. Asociar un FS a un punto del árbol:

# mount /dev/sdb1 /mnt
$ ls /mnt   # ahora ves los archivos del segundo disco

/proc y /sys son pseudo-FS: no están en disco, los genera el kernel al vuelo. Por eso cat /proc/cpuinfo es realmente "preguntar al kernel".

6.10 Permisos en Unix

📐 Fundamento

Cada archivo tiene tres clases de usuarios y tres permisos:

permisos:    r w x   r w x   r w x
             dueño   grupo   otros
  • r = read.
  • w = write.
  • x = execute (ejecutar archivo o entrar a directorio).

Notación octal: rwx = 7, rw- = 6, r-- = 4. Por eso ves cosas como chmod 755 archivo:

  • 7 (dueño): rwx.
  • 5 (grupo): r-x.
  • 5 (otros): r-x.

Comando para verlo:

$ ls -l
-rwxr-xr-x 1 ana users 8192 may  5 script.sh
drwxr-xr-x 3 ana users 4096 may  5 docs

- al inicio = archivo regular. d = directorio. l = symlink.

Cambiar:

$ chmod 644 archivo       # rw-r--r--
$ chmod u+x archivo       # agrega execute al dueño
$ chown beto:devs archivo # cambia dueño y grupo (root)

Bit setuid. Si un archivo ejecutable tiene bit s en el dueño, se ejecuta con los permisos del dueño, no del que lo lanza. Eso es lo que permite que passwd (un programa común) modifique /etc/shadow (que solo root puede tocar). Es poderoso y peligroso — bug clásico de seguridad cuando está mal usado.

Permisos en directorios:

  • r = listar contenido.
  • w = crear/borrar archivos dentro.
  • x = entrar (acceder a archivos por nombre).

x sin r = "podés acceder a archivos si sabés el nombre, pero no podés listar". Patrón legítimo para directorios "privados pero accesibles".

6.11 Resumen visual

Concepto Una línea
Sistema de archivos Mapea nombres jerárquicos a bloques en disco.
Inodo Metadata (sin el nombre) + punteros a bloques.
Directorio Tabla de (nombre → inodo).
Hard link Otro nombre apuntando al mismo inodo.
Symbolic link Archivo cuyo contenido es un path.
Page cache RAM usada para cachear bloques de disco.
Journaling Log antes de cambios; permite recuperación tras crash.
VFS Abstracción que unifica todos los FS en Linux.
Permisos rwx Para dueño, grupo, otros.
Sistemas
FAT32 / exFAT — simples, portables, limitados
NTFS — Windows, journaling, ACLs
ext4 — Linux estándar, journaling, extents
XFS — alto rendimiento en grandes
Btrfs / ZFS — copy-on-write, snapshots, checksums

6.12 Ejercicios

✏️ Ejercicio 6.1 — Comparar hard y soft link

Creá un archivo original.txt con contenido "hola". Hacé:

a. Un hard link hard.txt. b. Un symbolic link simbolico.txt.

Después borrá original.txt. ¿Qué pasa con cada uno?

✏️ Ejercicio 6.2 — Tamaño máximo en ext2

Un FS ext2 con bloques de 4 KB. Cada inodo tiene 12 punteros directos, 1 indirecto simple, 1 indirecto doble. Cada puntero es de 4 bytes.

a. ¿Cuántos punteros caben en un bloque indirecto? b. ¿Cuál es el tamaño máximo de un archivo?

✏️ Ejercicio 6.3 — Permisos

¿Qué hace cada comando?

a. chmod 700 secreto/ b. chmod 644 archivo.txt c. chmod a+x script.sh d. chmod 755 ~

6.13 Para profundizar


Definiciones nuevas: sistema de archivos, archivo, directorio, inodo, hard link, symbolic link, path, file descriptor, asignación de bloques, FAT, extents, page cache, journaling, VFS, mount, permisos rwx, setuid.