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:
- Nombres simbólicos ("carta-mama.txt") en vez de números de bloque.
- Organización jerárquica (directorios anidados) para no tener un solo namespace gigante.
- Persistencia confiable — los archivos siguen ahí mañana, incluso si se cortó la luz.
- Concurrencia segura — varios procesos pueden leer/escribir sin pisarse.
- 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:
- Empieza en
/(root, inodo conocido). - Busca
homeen root → encuentra inodo de/home. - Busca
anaen/home→ inodo de/home/ana. - Busca
docs→ inodo de/home/ana/docs. - 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:
- Escribir en journal: "voy a hacer X, Y, Z".
- Marcar journal como commit (esta entrada es válida).
- Escribir los cambios reales al disco.
- 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?
Solución
$ echo hola > original.txt
$ ln original.txt hard.txt
$ ln -s original.txt simbolico.txt
$ cat hard.txt; cat simbolico.txt
hola
hola
$ rm original.txt
$ cat hard.txt
hola # sigue funcionando
$ cat simbolico.txt
cat: simbolico.txt: No such file or directory # roto
El hard link mantiene el contenido porque hay otro nombre apuntando al mismo inodo. El symbolic link es un archivo de texto que contenía "original.txt" — pero ese path ya no existe.
✏️ 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?
Solución
a. Bloque = 4096 bytes / 4 bytes/puntero = 1024 punteros.
b.
- 12 directos: .
- Indirecto simple: .
- Indirecto doble: punteros, cada uno apunta a un bloque de 4 KB → .
Total: ~4 GB + 4 MB + 48 KB ≈ 4 GB.
(Por eso ext4 agrega indirecto triple → más TB. Pero también introdujo extents que son aún más eficientes.)
✏️ 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 ~
Solución
a. Solo el dueño puede entrar/listar/modificar el directorio. Nadie más.
b. Dueño puede leer y escribir; grupo y otros solo leen.
c. Agrega execute para todos (a = all).
d. Tu home: tú (rwx), grupo y otros pueden entrar y listar pero no modificar. Default razonable.
6.13 Para profundizar
- Tanenbaum & Bos cap. 4.
- OSTEP caps de "Persistence". El cap de journaling es excelente.
- Robert Love, Linux Kernel Development — sección VFS.
- Próximo capítulo: E/S y dispositivos — cómo el SO habla con periféricos: discos, redes, GPUs, USB.
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.