La lliçó anterior va acabar amb un cap solt deliberat. Sabem que l'inode 1180934 és el fitxer de lectures del 31 d'agost: guarda la seva mida, el seu propietari, les seves dates i els seus blocs. I sabem que no guarda el seu nom. Però aleshores, quan escrius cat /var/lib/meteora/lectures/2026-08-31.dat, qui converteix aquesta cadena de 43 caràcters en el número 1180934?

La resposta és el directori, i mereix una lliçó sencera per tres raons. La primera és que el seu disseny explica comportaments que desconcerten tothom: per què esborrar un fitxer es diu «desenllaçar», per què un fitxer esborrat continua ocupant 17 GB, per què un enllaç simbòlic es trenca i un de dur no, i per què no pots enllaçar durament un directori. La segona és que la resolució de camins és una de les operacions més executades de tot el sistema —milions de vegades per segon en un servidor amb càrrega— i entendre'n el cost explica decisions de disseny reals. I la tercera és que l'organització dels noms és un problema clàssic d'estructures de dades amb conseqüències mesurables: un directori amb 100.000 fitxers de lectures es comporta de manera radicalment diferent segons com estigui organitzat per dins.

Anem a obrir un directori i veure què hi ha a dins, recórrer l'evolució històrica de les organitzacions fins a arribar a l'arbre d'UNIX, seguir pas a pas la conversió d'un camí en un inode comptant els accessos a disc, distingir amb precisió els dos tipus d'enllaç, mesurar el cost de buscar en directoris enormes, i situar els camins de Meteora a l'estàndard FHS.

Contingut

  1. El directori és un fitxer que conté parells (nom, inode)
  2. Evolució de les organitzacions de directoris
  3. Camins absoluts i relatius, . i ..
  4. El directori de treball: chdir, getcwd i per què és per procés
  5. Resolució de camins pas a pas, amb el cost comptat
  6. La memòria cau de dentries: per què això no costa el que sembla
  7. Enllaços durs: un inode, diversos noms
  8. Enllaços simbòlics: un fitxer que conté un camí
  9. Organització interna i el cost dels directoris enormes
  10. L'estàndard FHS i per què Meteora és on és
  11. Esborrat: què fa realment unlink

El directori és un fitxer que conté parells (nom, inode)

Comencem per l'afirmació central, que sol sonar estranya la primera vegada:

Un directori és un fitxer normal el contingut del qual és una taula de parells (nom, número d'inode). La seva única particularitat és que el tipus del seu inode és d, i que el nucli es reserva el dret d'escriure'l ell mateix.

Té el seu inode, la seva mida, els seus permisos, les seves dates i els seus blocs de dades, exactament igual que 2026-08-31.dat. El que canvia és què hi ha dins d'aquests blocs.

Anem a demostrar-ho. Primer, el directori té mida i blocs com qualsevol fitxer:

$ stat /var/lib/meteora/lectures
  Fitxer: /var/lib/meteora/lectures
  Mida: 4096          Blocs: 8           Bloc d'E/S: 4096   directori
Dispositiu: 9,0      Node-i: 1180929     Enllaços: 3
Accés: (0750/drwxr-x---)  Uid: (990/meteora)   Gid: (990/meteora)

4.096 bytes, 8 blocs de 512 B, inode 1180929. És un fitxer. Ara provem de llegir-lo com a tal:

$ cat /var/lib/meteora/lectures
cat: /var/lib/meteora/lectures: És un directori

Falla, i no perquè no hi hagi contingut, sinó perquè des del 1988 Linux prohibeix read() sobre un directori: si cada programa interpretés el format binari a mà, canviar l'estructura interna d'ext4 trencaria mig sistema. En el seu lloc hi ha una crida al sistema específica, getdents64(), que retorna les entrades en un format independent del sistema de fitxers. És la mateixa idea d'indirecció que veurem com a VFS a Particions, Muntatge i Sistema de Fitxers Virtual.

Amb strace es veu que ls la fa servir:

$ strace -e trace=openat,getdents64 ls /var/lib/meteora/lectures
openat(AT_FDCWD, "/var/lib/meteora/lectures", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
getdents64(3, 0x5586a2f1b0d0 /* 8 entries */, 32768) = 240
getdents64(3, 0x5586a2f1b0d0 /* 0 entries */, 32768) = 0

ls obre el directori amb O_DIRECTORY, demana les seves entrades amb getdents64 —vuit de cop, 240 bytes— i torna a cridar fins a rebre 0, que significa «no n'hi ha més». Tot ls, tot find i tot os.listdir() acaben aquí.

Per veure el contingut cru cal debugfs:

$ sudo debugfs -R "ls -l /var/lib/meteora/lectures" /dev/md0
 1180929  40750 (2)    990   990    4096 1-Sep-2026 00:00 .
 1180928  40750 (2)    990   990    4096 1-Sep-2026 00:00 ..
 1180934 100640 (1)    990   990 17280000 31-Aug-2026 23:59 2026-08-31.dat
 1180935 100640 (1)    990   990 17280000  1-Sep-2026 12:40 2026-09-01.dat
 1180936 120777 (7)    990   990      14  1-Sep-2026 00:00 avui.dat
 1180940  40750 (2)    990   990    4096  1-Sep-2026 00:00 arxiu

Aquí hi ha la taula: sis parells (nom, inode). La primera columna és el número d'inode i l'última el nom; entremig, el mode en octal amb el tipus davant (40 directori, 100 regular, 120 enllaç simbòlic).

El format real d'una entrada a ext4, que és d'una senzillesa gairebé decebedora:

struct ext4_dir_entry_2 {
    __le32  inode;        /* 4 B: número d'inode, 0 = entrada esborrada */
    __le16  rec_len;      /* 2 B: longitud TOTAL d'aquesta entrada      */
    __u8    name_len;     /* 1 B: longitud del nom (màx. 255)           */
    __u8    file_type;    /* 1 B: 1=regular 2=dir 7=symlink...          */
    char    name[];       /* el nom, sense terminador nul               */
};

Vuit bytes de capçalera més el nom, arrodonit a múltiple de 4. L'entrada de 2026-08-31.dat (14 caràcters) ocupa 8 + 14 = 22 → 24 bytes. Dos detalls amb conseqüències:

  • rec_len és la longitud total, no la del nom. Recórrer el directori consisteix a avançar rec_len bytes des de cada entrada. Quan s'esborra una entrada, el sistema no mou res: simplement suma el seu rec_len al de l'entrada anterior, que se l'«empassa». Per això un directori que va tenir un milió de fitxers i ara en té tres continua ocupant megabytes: els forats existeixen però el fitxer no encongeix.
  • file_type duplica informació que ja és a l'inode. És una optimització deliberada: permet a find -type f filtrar sense llegir l'inode de cada entrada, i estalvia un accés per fitxer.

I el detall que més conseqüències té, ja visible a dalt: el mateix número d'inode pot aparèixer en diverses entrades. Això són els enllaços durs de l'apartat 7.

Evolució de les organitzacions de directoris

L'arbre d'UNIX no va caure del cel. És el quart intent de resoldre el mateix problema, i conèixer els tres anteriors explica per què l'actual és com és.

Nivell únic. Tots els fitxers en un sol espai de noms pla; és el que tenien els primers sistemes per lots i el que tenen encara molts dispositius encastats. Trivial d'implementar, però amb un problema fatal: les col·lisions de noms. Si dos usuaris volen un dades.dat, un hi perd.

Dos nivells. Un directori per usuari penjant d'un directori mestre. Resol les col·lisions i aporta aïllament, però és rígid: un usuari no pot organitzar els seus 400 fitxers en subgrups, i compartir amb un altre és incòmode. Meteora no podria separar lectures/ d'arxiu/.

Arbre jeràrquic. Un directori pot contenir directoris, sense límit de profunditat. És l'estructura d'UNIX i de Windows: organització natural per temes, noms únics garantits pel camí complet, i exactament un camí des de l'arrel fins a cada fitxer. La seva limitació és que no permet compartir de debò: si 2026-08-31.dat també ha d'aparèixer a /srv/publicacio/, l'única opció és copiar-lo —34 MB en lloc de 17, i divergència tan bon punt un dels dos canviï—.

Graf acíclic. L'arbre més la possibilitat que diversos noms apuntin al mateix fitxer, mentre no es formin cicles. És el que UNIX aconsegueix amb els enllaços. Aporta compartició real, però porta dos problemes nous: el recorregut pot visitar un fitxer dues vegades (find, du i tar porten el compte dels inodes ja vistos), i l'esborrat deixa de ser obvi —si dos camins són el mateix inode, esborrar-ne un destrueix el contingut?—. La solució és el comptador d'enllaços de l'apartat 7.

Graf general. Si a més es permeten cicles —un directori que es conté a si mateix per un camí indirecte—, apareixen dos problemes greus:

  1. Recorreguts infinits. Un find sense protecció entraria en un bucle etern. Caldria detectar cicles a cada recorregut, amb cost i memòria.
  2. Recol·lecció d'escombraries. El comptador d'enllaços deixa de servir. Si A/ conté un enllaç a B/ i B/ conté un enllaç a A/, i esborrem tots dos noms des de fora, els dos comptadors continuen valent 1 —se sostenen mútuament— però ningú no hi pot arribar. Aquell espai quedaria perdut per sempre llevat que s'executés un recol·lector d'escombraries per accessibilitat, recorrent el disc sencer des de l'arrel.

I per això UNIX pren una decisió taxativa que ara entens:

No es permeten enllaços durs a directoris. Només . i .., que crea el mateix nucli i són cicles controlats i coneguts.

Amb aquesta única prohibició, l'estructura de directoris queda garantida com a acíclica, i el comptador d'enllaços n'hi ha prou com a recol·lector d'escombraries: mai no pot haver-hi una illa inabastable amb comptador positiu. És una restricció petita a canvi d'eliminar dues classes senceres de problemes.

Organització Col·lisions Compartició Cicles Esborrat segur Feta servir avui
Nivell únic Sí No No Trivial Encastats simples
Dos nivells No Difícil No Trivial Sistemes històrics
Arbre No No No Trivial FAT (sense enllaços)
Graf acíclic No Sí No Comptador d'enllaços UNIX, Linux, NTFS
Graf general No Sí Sí Necessita recol·lector Cap de seriós

Camins absoluts i relatius, . i ..

Un camí és la recepta per arribar a un fitxer recorrent el graf.

  • Absolut: comença per / i parteix de l'arrel. /var/lib/meteora/lectures/2026-08-31.dat. Significa el mateix l'executi qui l'executi i des d'on l'executi.
  • Relatiu: no comença per / i parteix del directori de treball actual del procés. lectures/2026-08-31.dat significa coses diferents segons on siguis.

Tot directori conté sempre dues entrades que crea el nucli en crear-lo:

Entrada Apunta a Per a què serveix
. El seu propi inode Referir-se al directori actual: ./programa, cp fitxer .
.. L'inode del seu pare Pujar un nivell: cd .., ../config/

A l'arrel hi ha una excepció elegant: /.. apunta a /. L'arrel és el seu propi pare, així que cd /../../../.. et deixa a / sense error. És el que impedeix sortir de l'arbre per dalt.

Aquestes dues entrades expliquen el comptador d'enllaços dels directoris, que confon molta gent:

$ stat -c '%h %n' /var/lib/meteora/lectures
3 /var/lib/meteora/lectures

Per què 3? Perquè tres noms apunten a aquest inode:

  1. lectures, l'entrada al directori pare /var/lib/meteora/.
  2. ., dins del mateix lectures/.
  3. .., dins del seu únic subdirectori, arxiu/.

D'aquí la regla: el comptador d'enllaços d'un directori és 2 + el nombre de subdirectoris. Un truc pràctic que en surt: per saber quants subdirectoris té un directori sense recórrer-lo, n'hi ha prou amb stat -c %h i restar 2. Amb un directori de 100.000 entrades, això és instantani enfront dels segons que trigaria ls -l | grep ^d.

El directori de treball: chdir, getcwd i per què és per procés

Cada procés té el seu directori de treball actual (current working directory, cwd), desat al seu task_struct —l'estructura que vam disseccionar a Gestió de Processos—. És un punter a un inode de directori, no una cadena de text.

Es veu a /proc, com tota la resta:

$ ls -l /proc/2841/cwd
lrwxrwxrwx 1 meteora meteora 0 set  1 12:44 /proc/2841/cwd -> /var/lib/meteora

I es manipula amb dues crides al sistema:

#include <unistd.h>

char buf[PATH_MAX];

if (chdir("/var/lib/meteora/lectures") == -1)   /* canvia el cwd del procés  */
    perror("chdir");

if (getcwd(buf, sizeof buf) == NULL)            /* reconstrueix el camí actual */
    perror("getcwd");
printf("Treballant a %s\n", buf);               /* → /var/lib/meteora/lectures */

chdir() canvia el punter d'inode del procés —requereix permís d'execució a la destinació, com veurem a Seguretat i Permisos— i getcwd() fa el contrari: parteix de l'inode del cwd i puja per .. fins a l'arrel, buscant a cada pare el nom que correspon a l'inode del fill, i compon la cadena a l'inrevés. Per això getcwd() és sorprenentment cara i per això pot fallar amb ENOENT si algú ha esborrat un directori del camí mentre hi eres a dins.

Per què el cwd és per procés i no global mereix resposta explícita. Si fos global, dos processos que fessin cd es trepitjarien: meteo-api canviaria de directori i l'agregador escriuria al lloc equivocat, una condició de cursa de manual (03-01) sobre estat compartit. En ser per procés, s'hereta al fork() —el fill comença on era el pare, i per això cd /tmp && ls funciona com esperes— i s'explica el desconcert clàssic: un script que fa cd /var/lib/meteora no canvia el directori del teu intèrpret d'ordres, perquè corre en un procés fill el cwd del qual mor amb ell. Perquè afecti l'intèrpret cal executar-lo amb source o ..

Un detall pràctic important per a serveis: un dimoni que manté el seu cwd en un directori impedeix desmuntar aquell sistema de fitxers (el «target is busy» de 04-03). Per això els dimonis ben escrits fan chdir("/") en arrencar, i per això systemd ofereix WorkingDirectory=.

Resolució de camins pas a pas, amb el cost comptat

Aquesta és l'operació central de la lliçó. Quan meteo-api executa:

int fd = open("/var/lib/meteora/lectures/2026-08-31.dat", O_RDONLY);

el nucli ha de convertir 43 caràcters en l'inode 1180934. L'algorisme, anomenat resolució de camins (path resolution), és un bucle:

graph TB
    A["Camí: /var/lib/meteora/lectures/2026-08-31.dat"] --> B{"Comença per /?"}
    B -->|Sí| C["inode actual = inode de l'arrel (núm. 2)"]
    B -->|No| D["inode actual = inode del cwd del procés"]
    C --> E["Prendre el component següent"]
    D --> E
    E --> F{"És un directori?<br/>Hi ha permís x?"}
    F -->|No| G["Error: ENOTDIR o EACCES"]
    F -->|Sí| H["Buscar el nom a les seves entrades"]
    H --> I{"Trobat?"}
    I -->|No| J["Error: ENOENT"]
    I -->|Sí| K["inode actual = l'inode trobat"]
    K --> L{"És enllaç simbòlic?"}
    L -->|Sí| M["Substituir per la seva destinació<br/>i reiniciar (màx. 40 vegades)"]
    M --> E
    L -->|No| N{"Queden components?"}
    N -->|Sí| E
    N -->|No| O["Retornar l'inode final"]

Executat sobre el nostre camí, amb el compte d'accessos suposant memòries cau buides:

Pas Component Què fa el nucli Accessos a disc
0 / Agafa l'inode 2 (l'arrel sempre és l'inode 2 a ext4) 1 (inode)
1 var Llegeix els blocs de dades de /, busca var → inode 131073 1 (dades) + 1 (inode)
2 lib Llegeix les dades de /var, busca lib → inode 262145 1 + 1
3 meteora Llegeix les dades de /var/lib, busca meteora → inode 1180928 1 + 1
4 lectures Llegeix les dades de /var/lib/meteora → inode 1180929 1 + 1
5 2026-08-31.dat Llegeix les dades de lectures, busca el nom → inode 1180934 1 + 1
6 — Llegeix l'inode final per comprovar permisos i tipus (ja comptat)
Total 11 accessos

Onze accessos a disc per obrir un fitxer. En un NVMe, amb 80 µs cadascun, són uns 0,9 ms; a l'HDD de 02-05, amb 8 ms de latència mitjana, serien 88 mil·lisegons. I tot això abans de llegir ni un sol byte de dades.

Tres conseqüències directes que val la pena interioritzar:

  1. Cada component del camí costa. /a/b/c/d/e/f/g/fitxer.dat és més car d'obrir que /dades/fitxer.dat. Camins absurdament profunds tenen un cost real, encara que petit.
  2. El permís d'execució es comprova a cada directori del camí, no només al fitxer final. És el tema de 04-06, però el mecanisme és aquest bucle.
  3. Obrir una vegada i reutilitzar el descriptor és molt més barat que obrir el fitxer a cada operació. És la raó per la qual l'ingestor obre 2026-08-31.dat en començar el dia i el manté obert, en lloc d'obrir-lo i tancar-lo a cada lectura rebuda.

Si algun component intermedi és un enllaç simbòlic, se substitueix per la seva destinació i el bucle es reinicia. Linux limita a 40 les resolucions encadenades; en superar-ho retorna ELOOP («Massa nivells d'enllaços simbòlics»), que és la protecció contra un enllaç que apunta a si mateix.

La memòria cau de dentries: per què això no costa el que sembla

Onze accessos per open() seria inacceptable: un servidor obre milers de fitxers per segon i la majoria comparteixen els primers components del camí. La solució del nucli és la memòria cau de dentries (dentry cache o dcache).

Una dentry (directory entry) és una estructura en memòria que associa (inode del directori pare, nom) → inode fill. La dcache és una taula hash de dentries, amb el camí parcial com a clau. El seu efecte és demolidor: si /var/lib/meteora/lectures és a la memòria cau —i hi serà, perquè es fa servir constantment—, la resolució del nostre camí passa d'11 accessos a disc a un, el de l'inode final, i sovint a zero si també està desat a la memòria cau.

$ sudo slabtop -o | grep -E 'dentry|inode_cache'
 OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME
418320 411927  98%    0,19K  19920       21     79680K dentry
 96432  94215  97%    0,58K   3444       28     55104K ext4_inode_cache

En aquest meteo-01 hi ha 418.320 dentries a la memòria cau que ocupen 78 MiB, amb un 98 % d'actives. Aquest consum de RAM és el que fa que el sistema de fitxers sembli ràpid, i és també part del que free compta com a memòria «disponible»: són memòries cau reclamables, exactament com les pàgines de 02-04.

Dos detalls que es veuen a la pràctica:

Dentries negatives. La memòria cau guarda també els «no existeix»: si un intèrpret busca un mòdul per una llista de vint camins, la primera consulta de cadascun costa accessos a disc i les següents retornen ENOENT des de memòria.

Es pot buidar per mesurar. Per comparar el cost real amb i sense memòria cau:

sync                                       # abocar escriptures pendents
echo 3 | sudo tee /proc/sys/vm/drop_caches  # 3 = dentries + inodes + pàgines
time cat /var/lib/meteora/lectures/2026-08-31.dat > /dev/null   # fred
time cat /var/lib/meteora/lectures/2026-08-31.dat > /dev/null   # calent

Els valors de drop_caches són 1 (memòria cau de pàgines), 2 (dentries i inodes) i 3 (totes dues). Només en proves: en producció buidar les memòries cau provoca un pic de latència mentre tot es torna a llegir. La diferència entre l'execució freda i la calenta sol ser d'un a dos ordres de magnitud, i és la mesura directa del valor de la dcache.

Enllaços durs: un inode, diversos noms

Un enllaç dur és simplement una altra entrada de directori que apunta al mateix número d'inode. No és una còpia ni una referència especial: és un nom més, amb exactament la mateixa categoria que l'original.

$ cd /var/lib/meteora/lectures
$ ls -li 2026-08-31.dat
1180934 -rw-r----- 1 meteora meteora 17280000 ago 31 23:59 2026-08-31.dat

$ ln 2026-08-31.dat /var/lib/meteora/arxiu/agost-final.dat

$ ls -li 2026-08-31.dat /var/lib/meteora/arxiu/agost-final.dat
1180934 -rw-r----- 2 meteora meteora 17280000 ago 31 23:59 2026-08-31.dat
1180934 -rw-r----- 2 meteora meteora 17280000 ago 31 23:59 .../agost-final.dat

Fixa't en les dues coses que han canviat: el mateix inode 1180934 als dos, i el comptador d'enllaços ha passat d'1 a 2. I el que no ha canviat: l'espai al disc.

$ df -h /var/lib/meteora | tail -1
/dev/md0    196G   41G  146G  22% /var/lib/meteora     ← idèntic que abans

Els 17 MB no s'han duplicat perquè no hi ha dos fitxers: hi ha un fitxer amb dos noms. Si escrius per un, ho veus per l'altre, perquè és el mateix inode, els mateixos blocs i els mateixos bytes.

Ara, esborrar-ne un:

$ rm 2026-08-31.dat
$ ls -li /var/lib/meteora/arxiu/agost-final.dat
1180934 -rw-r----- 1 meteora meteora 17280000 ago 31 23:59 agost-final.dat

El fitxer continua allà, amb el comptador tornat a 1. rm no destrueix res: treu un nom i decrementa el comptador. Només quan el comptador arriba a 0 el sistema allibera els blocs.

Les dues restriccions dels enllaços durs, amb el seu perquè:

No poden creuar sistemes de fitxers. Un enllaç dur és un número d'inode, i els números d'inode són únics només dins del seu sistema de fitxers (04-01). L'inode 1180934 existeix a /dev/md0 i probablement també a /dev/sda1, sent coses diferents. Una entrada de directori no té espai per dir «de quin dispositiu»:

$ ln /var/lib/meteora/lectures/2026-09-01.dat /tmp/prova.dat
ln: no es pot crear l'enllaç dur '/tmp/prova.dat' => ...: Enllaç creuat no vàlid

L'error EXDEV («Invalid cross-device link») és el mateix que fa que mv entre discos diferents hagi de copiar i esborrar en lloc de reanomenar, amb la diferència de rendiment que això suposa: moure 17 GB dins del mateix sistema de fitxers és instantani; entre dos, triga minuts.

No poden enllaçar directoris. És la prohibició de l'apartat 2: garanteix que el graf sigui acíclic i que el comptador d'enllaços n'hi hagi prou com a recol·lector d'escombraries.

$ sudo ln /var/lib/meteora/lectures /var/lib/meteora/drecera
ln: /var/lib/meteora/lectures: Operació no permesa

Enllaços simbòlics: un fitxer que conté un camí

Un enllaç simbòlic és un fitxer de tipus l el contingut del qual és una cadena de text amb un camí. Res més. Quan el nucli el troba durant la resolució, substitueix l'enllaç per aquella cadena i continua.

$ ln -s 2026-09-01.dat avui.dat
$ ls -li avui.dat
1180936 lrwxrwxrwx 1 meteora meteora 14 set  1 00:00 avui.dat -> 2026-09-01.dat

Tres observacions carregades d'informació: té inode propi (1180936), per tant és un fitxer diferent; la seva mida és 14, exactament els caràcters de 2026-09-01.dat, que són tot el seu contingut; i els seus permisos lrwxrwxrwx són sempre 777 i no signifiquen res, perquè els que s'apliquen són els de la destinació.

Un detall d'implementació bonic: ext4 aprofita els 60 bytes que l'inode reserva per a punters de blocs i, si el camí ocupa menys de 60 caràcters, l'hi guarda allà mateix. Se'n diu fast symlink i no consumeix cap bloc de dades: resoldre'l no costa ni un accés extra. Gairebé tots els enllaços simbòlics reals hi caben.

La diferència crucial amb l'enllaç dur és que el simbòlic apunta a un nom, no a un inode. D'aquí les seves dues propietats característiques:

Pot creuar sistemes de fitxers i enllaçar directoris, perquè una cadena de text no té aquestes limitacions:

$ ln -s /var/lib/meteora/lectures /srv/publicacio/dades     # un altre FS i un directori

Pot quedar-se trencat. Si la destinació desapareix o es reanomena, l'enllaç continua existint i apuntant a un camí que ja no resol:

$ rm 2026-09-01.dat
$ ls -l avui.dat
lrwxrwxrwx 1 meteora meteora 14 set  1 00:00 avui.dat -> 2026-09-01.dat
$ cat avui.dat
cat: avui.dat: No existeix el fitxer o el directori

L'enllaç està perfectament sa; el que falta és la destinació. Els enllaços trencats es localitzen amb find /var/lib/meteora -xtype l.

I un parany que mossega tothom: els enllaços relatius es resolen respecte al directori de l'enllaç, no respecte a qui el fa servir.

$ ln -s 2026-09-01.dat /tmp/avui.dat        # MALAMENT!
$ cat /tmp/avui.dat
cat: /tmp/avui.dat: No existeix el fitxer o el directori

/tmp/avui.dat apunta a 2026-09-01.dat, que el nucli busca a /tmp/. Regla pràctica: fes servir camins absoluts als enllaços llevat que enllaç i destinació siguin al mateix directori o vulguis que el conjunt es pugui moure.

La taula comparativa completa:

Aspecte Enllaç dur Enllaç simbòlic
Què és Una altra entrada amb el mateix inode Fitxer amb un camí a dins
Inode El mateix que l'original Propi i diferent
Tipus a ls -l - (indistingible) l
Comptador d'enllaços de l'original Augmenta No canvia
Creua sistemes de fitxers No (EXDEV) Sí
Enllaça directoris No Sí
Sobreviu a l'esborrat de l'original Sí (el contingut continua) No (queda trencat)
Sobreviu a reanomenar l'original Sí No
Espai ocupat 24 bytes al directori Un inode (+0 blocs si < 60 B)
Cost en resoldre Cap Una resolució extra
Quin és l'«original»? Cap: són idèntics La destinació, clarament
Ordre ln origen nou ln -s origen nou
Ús típic Còpies de seguretat incrementals, deduplicació Versions (avui.dat), /usr/bin, biblioteques

La fila de «quin és l'original?» és la que més costa acceptar: després de ln a b, no hi ha manera de saber quin es va crear primer. Les dues entrades són igual de legítimes; l'inode no guarda cap preferència.

Meteora fa servir tots dos, i cadascun on toca: un simbòlic avui.dat que es reapunta cada mitjanit al fitxer del dia (els clients sempre demanen avui.dat i l'enllaç es refà amb ln -sfn, que és atòmic), i enllaços durs a les còpies de seguretat diàries, on els fitxers que no han canviat s'enllacen en lloc de copiar-se —és exactament el que fa rsync --link-dest i per això trenta còpies diàries de 17 GB poden ocupar 18 GB en total.

Organització interna i el cost dels directoris enormes

Fins ara hem dit que un directori «conté una taula». Com està organitzada aquesta taula? És una decisió d'estructures de dades amb efectes mesurables.

Llista lineal. Les entrades, una darrere l'altra, en l'ordre en què es van crear. Buscar un nom significa recórrer des del principi comparant cadenes.

  • Buscar: O(n). Crear: O(n), perquè cal comprovar que el nom no existeix.
  • És el que feia ext2 i el que continua fent ext4 en directoris petits.

Fem el càlcul amb un cas realista. Suposa que Meteora, en lloc d'un fitxer per dia, hagués guardat un fitxer per cada deu minuts durant dos anys: 105.120 fitxers a lectures/. Amb entrades de 24 bytes de mitjana:

  • Mida del directori: 105.120 × 24 ≈ 2,5 MB, és a dir 616 blocs de 4 KiB.
  • Buscar un nom: llegir una mitjana de 308 blocs i comparar 52.560 cadenes.
  • I el pitjor: crear un fitxer nou obliga a recórrer els 616 blocs sencers per verificar que el nom no existeix.

El resultat és la patologia clàssica: ls triga segons, i crear els fitxers es torna cada vegada més lent a mesura que el directori creix. És un comportament quadràtic en conjunt: crear n fitxers costa O(n²).

Taula hash. S'aplica una funció hash al nom i se salta directament al calaix que li correspon.

  • Buscar: O(1) en el cas mitjà. És el que fan servir ext4 (dir_index) i NTFS amb variants.
  • Inconvenient: el recorregut complet retorna les entrades en ordre de hash, que sembla aleatori.

Arbre B / B+. Les entrades es mantenen ordenades en un arbre equilibrat.

  • Buscar: O(log n), i a més el recorregut surt ordenat i els rangs són eficients.
  • És el que fan servir XFS, Btrfs i NTFS.

A ext4, la solució concreta es diu htree (hashed tree) i s'activa amb la característica dir_index. És un híbrid: aplica una variant d'MD4 al nom i fa servir el hash com a clau d'un arbre d'un o dos nivells, cadascun amb 4 KiB d'índex. Amb dos nivells adreça de l'ordre de milions d'entrades amb com a màxim tres accessos: node arrel, node fulla i bloc de dades.

La comparació, sobre el directori hipotètic de 105.120 fitxers:

Organització Buscar un nom Crear un fitxer Llistar-ho tot Ordre de sortida
Llista lineal 308 blocs (mitjana) 616 blocs 616 blocs Creació
htree (ext4) 3 blocs 3 blocs 616 blocs Hash (atzar aparent)
Arbre B+ (XFS) ~3 blocs ~3 blocs 616 blocs Alfabètic

De 308 a 3 accessos: cent vegades menys. Es comprova si està actiu amb:

$ sudo tune2fs -l /dev/md0 | grep features
Filesystem features:  has_journal ext_attr dir_index extent 64bit metadata_csum

dir_index hi és, i en qualsevol ext4 creat els últims quinze anys hi serà. Si per la raó que sigui no hi fos, s'activa amb tune2fs -O dir_index /dev/md0 seguit d'e2fsck -D per reconstruir els índexs.

Dues conseqüències pràctiques de l'htree que convé conèixer. La primera és que ls surt desordenat i ordenar-lo costa: el recorregut retorna les entrades en ordre de hash i ls les ordena en memòria abans d'imprimir res, així que amb 105.120 entrades ls -f o find . -maxdepth 1 són molt més ràpids. La segona és que llistar en ordre de hash destrossa la localitat dels inodes: processar-los en l'ordre que retorna readdir() significa llegir-los en ordre aleatori, cosa que en un HDD són cerques contínues. La solució clàssica —i el que fan tar i diverses eines de còpia de seguretat— és llegir tots els noms, ordenar-los per número d'inode i processar-los així.

I la conclusió de disseny, que és la que importa: fins i tot amb htree, un directori amb cent mil fitxers és una mala idea. El llistat continua sent O(n), les còpies de seguretat s'alenteixen, ls amb * pot desbordar la línia d'ordres, i qualsevol eina que ordeni consumeix memòria. La pràctica correcta és repartir en subdirectoris, que és exactament el que fa Meteora amb el seu arxiu històric:

/var/lib/meteora/lectures/2026-08-31.dat     ← el dia en curs i els recents
/var/lib/meteora/arxiu/2026/08/2026-08-01.dat ← històric per any/mes

Amb aquesta jerarquia, cap directori no passa de 31 entrades. És el mateix patró que fan servir Git per als seus objectes (ab/cdef...) i les memòries cau web de tothom.

L'estàndard FHS i per què Meteora és on és

El Filesystem Hierarchy Standard (FHS) és la convenció que diu què va a cada directori de l'arrel a Linux. No l'imposa el nucli: és un acord que fa que un administrador sàpiga on mirar en qualsevol distribució.

Directori Què conté Persistent? Compartible?
/bin, /sbin Binaris essencials del sistema (avui, enllaços a /usr) Sí Sí
/boot Nucli, initramfs i carregador d'arrencada Sí No
/dev Fitxers de dispositiu (devtmpfs, 04-03) No No
/etc Configuració del sistema, específica d'aquesta màquina Sí No
/home Directoris personals dels usuaris Sí Sí
/lib Biblioteques compartides essencials Sí Sí
/mnt, /media Punts de muntatge temporals i de mitjans extraïbles — —
/opt Programari de tercers autocontingut Sí Sí
/proc Informació de processos i del nucli (procfs, 04-03) No No
/root Directori personal del superusuari Sí No
/run Estat en execució: PID, sòcols, FIFO (tmpfs) No No
/srv Dades servides per aquest sistema (web, ftp) Sí Sí
/sys Interfície amb el model de dispositius (sysfs, 04-03) No No
/tmp Temporals de qualsevol usuari, es buida en arrencar No No
/usr Programes i dades de només lectura, no essencials Sí Sí
/var Dades variables: registres, cues, memòries cau, bases de dades Sí No

La lògica de fons són dos eixos: variable enfront d'estàtic (/var canvia constantment, /usr només en actualitzar) i compartible enfront d'específic (/usr es podria muntar per xarxa i compartir-se entre màquines, /etc no).

Amb això, els camins de Meteora deixen de ser arbitraris:

Camí de Meteora Per què allà
/var/lib/meteora/lectures/ /var/lib és el lloc canònic de les dades d'estat d'una aplicació: informació que l'aplicació crea, modifica i necessita conservar entre reinicis. No és /srv perquè no és contingut servit tal qual, ni /opt perquè això és per al programari, no per a les seves dades
/etc/meteora/meteora.conf /etc és configuració específica d'aquesta màquina, editable per l'administrador i que s'ha de conservar a les còpies de seguretat i controlar per versions
/var/log/meteora/meteo-api.log /var/log és la destinació estàndard dels registres, amb rotació gestionada per logrotate
/run/meteora/lectures.fifo /run és tmpfs: es buida en arrencar. Perfecte per a una FIFO i un sòcol, que no han de sobreviure a un reinici: un sòcol orfe d'una execució anterior impediria arrencar el servei
/dev/shm/meteora-cache tmpfs per a memòria compartida POSIX (03-03). Volàtil per definició: és una memòria cau
/tmp/ Només per a temporals de vida curta, amb mkstemp (04-04). Mai per a dades que hagin de durar

La raó per la qual /run i /dev/shm són tmpfs, i /var/lib no, resumeix el capítol sencer: la ubicació d'un fitxer declara les seves garanties de persistència. Posar el sòcol a /var/run (que avui és un enllaç simbòlic a /run) o les lectures a /tmp no són qüestions de gust: canvien el que passa després d'un reinici.

Esborrat: què fa realment unlink

Arribem al comportament que més desconcerta, i que ara té una explicació completa. La crida al sistema que hi ha darrere de rm no es diu delete:

int unlink(const char *pathname);   /* "desenllaçar", no "esborrar" */

Els seus passos exactes:

  1. Resoldre el camí fins al directori pare i comprovar el permís d'escriptura al directori (no al fitxer: 04-06).
  2. Eliminar l'entrada (nom, inode) del directori, absorbint el seu rec_len a l'anterior.
  3. Decrementar el comptador d'enllaços de l'inode.
  4. Si el comptador arriba a 0 i cap procés no té el fitxer obert: marcar l'inode com a lliure al seu mapa de bits i alliberar tots els seus blocs.
  5. Si el comptador arriba a 0 però algun procés el té obert: no alliberar res. L'inode passa a una llista d'orfes i s'alliberarà quan es tanqui l'últim descriptor.

El pas 5 és el que produeix l'incident clàssic. Algú veu que /var/log/meteora/meteo-api.log ha crescut fins a 17 GB, l'esborra amb rm, i l'espai no apareix:

$ ls -l /var/log/meteora/
total 0                                      ← el fitxer ja no hi és

$ df -h /var/log
Sist. fitxers          Mida   Usat  Disp  Ús% Muntat a
/dev/sda3               20G    19G  340M  99% /var/log     ← encara està ple!

El fitxer ja no té nom, però meteo-api el té obert. El comptador d'enllaços val 0, però el comptador de descriptors oberts val 1, així que els blocs continuen reservats i —pitjor encara— el procés continua escrivint en un fitxer que ja ningú no pot obrir.

El diagnòstic:

$ sudo lsof +L1
COMMAND    PID    USER   FD   TYPE DEVICE   SIZE/OFF NLINK NODE NAME
meteo-api 2841 meteora    5w   REG    8,3 18253611008     0 4457 /var/log/meteora/meteo-api.log (deleted)

+L1 filtra els fitxers oberts amb menys d'un enllaç, és a dir, els esborrats. La columna NLINK val 0 i el nom porta (deleted). Aquí hi ha el culpable.

Les tres solucions, de millor a pitjor:

# 1. Recarregar el servei: tanca i reobre els seus fitxers (03-03, SIGHUP)
sudo systemctl reload meteo-api        # o kill -HUP 2841

# 2. Si no admet recàrrega, reiniciar-lo
sudo systemctl restart meteo-api

# 3. Buidar el fitxer SENSE esborrar-lo (a través de /proc, sense reiniciar res)
sudo truncate -s 0 /proc/2841/fd/5

La tercera és un truc excel·lent: /proc/<pid>/fd/5 és un enllaç a l'inode orfe, així que s'hi pot accedir encara que no tingui nom, i truncar-lo a zero allibera els blocs a l'instant amb el servei en marxa. És el mateix /proc/<pid>/fd que explorarem a Gestió de Fitxers.

I la lliçó de fons: la manera correcta de buidar un registre és truncate -s 0 o : > fitxer, mai rm. És exactament el que fa logrotate amb l'opció copytruncate.

Un corol·lari feliç del mateix mecanisme: esborrar un fitxer obert és una tècnica legítima i molt utilitzada. Un programa pot crear un temporal, obrir-lo, esborrar-lo immediatament i continuar fent-lo servir pel descriptor. El fitxer no té nom, així que ningú més no el pot tocar, i el sistema l'allibera tot sol quan el procés mor, fins i tot si mor d'un kill -9. Ho veurem amb mkstemp a 04-04.

Errors Habituals i Consells

Creure que rm esborra el fitxer. Esborra un nom. Si hi ha un altre enllaç dur, el contingut continua; si un procés el té obert, l'espai continua ocupat. rm és unlink, i el nom de la crida és literal.

Buscar espai lliure amb du quan df diu que està ple. du recorre noms, així que no veu els fitxers esborrats que continuen oberts. Si df i du discrepen en gigabytes, executa lsof +L1.

Fer servir camins relatius en enllaços simbòlics sense pensar-hi. ln -s dades.dat /altre/lloc/enllac crea un enllaç que busca dades.dat a /altre/lloc/. Fes servir camins absoluts llevat que sàpigues exactament el que fas.

Confondre el comptador d'enllaços d'un directori amb «quants fitxers té». És 2 + nombre de subdirectoris; els fitxers regulars no compten.

Ficar cent mil fitxers en un directori. Encara que dir_index salvi la cerca, el llistat, l'ordenació, les còpies de seguretat i els comodins de l'intèrpret continuen sent O(n) o pitjor. I un directori no encongeix en esborrar: un que va arribar a 2,5 MB continuarà trigant el mateix a llistar-se encara que quedi buit, i l'única cura és recrear-lo. Reparteix en subdirectoris des del principi: migrar-ho després és dolorós.

Deixar el cwd d'un dimoni dins d'un volum que vols desmuntar. És una causa habitual de «target is busy» (04-03). Els serveis haurien de fer chdir("/").

Consell: fes servir ls -f o find -maxdepth 1 en directoris grans, que eviten l'ordenació en memòria, i stat -c '%i %h %n' per veure inode, comptador d'enllaços i nom d'un cop d'ull.

Consell: quan dubtis de si dos camins són el mateix fitxer, compara st_dev i st_ino, amb stat -c '%d %i' o directament find / -samefile cami. Comparar continguts és lent i comparar noms no diu res.

Exercicis

Exercici 1: demostrar que el nom no és a l'inode

Crea un fitxer, fes-li un enllaç dur i un de simbòlic, i dissenya una seqüència d'ordres que demostri les cinc afirmacions següents, mostrant la sortida que ho prova en cada cas: (a) l'enllaç dur i l'original comparteixen inode i el simbòlic no; (b) crear l'enllaç dur no consumeix espai de dades; (c) esborrar l'original no destrueix el contingut si hi ha enllaç dur; (d) esborrar l'original deixa trencat l'enllaç simbòlic; (e) el comptador d'enllaços reflecteix exactament el nombre de noms. Explica a cada punt quin camp de l'inode ho justifica.

Exercici 2: el cost de la resolució de camins

Calcula el nombre d'accessos a disc necessari per obrir /var/lib/meteora/arxiu/2026/08/2026-08-15.dat amb les memòries cau buides, detallant els passos. Després, suposant que /var/lib/meteora/arxiu/2026/08/ ja és a la memòria cau de dentries, recalcula-ho. Amb una latència de 80 µs per accés en NVMe i de 8 ms en HDD, dona els quatre temps. Finalment, explica què canvia si arxiu és un enllaç simbòlic a /mnt/historic/meteora i quants accessos hi afegeix.

Exercici 3: el fitxer esborrat que no allibera espai

Reprodueix l'incident complet: escriu un programa (en intèrpret d'ordres o en C) que obri un fitxer, hi escrigui contínuament i no el tanqui; en un altre terminal, esborra el fitxer i comprova que df no baixa. Diagnostica-ho amb lsof i du enfront de df, i resol-ho sense matar el procés. Després explica per què existeix logrotate amb copytruncate, i què hauria passat si en lloc de rm s'hagués fet servir truncate -s 0.

Solucions

Solució 1

cd /tmp && mkdir demo && cd demo
dd if=/dev/urandom of=original.dat bs=1M count=10 status=none
df --output=avail /tmp | tail -1        # espai lliure ABANS: p. ex. 8123456
ln    original.dat dur.dat
ln -s original.dat tou.dat
df --output=avail /tmp | tail -1        # espai lliure DESPRÉS: idèntic
ls -li

Sortida esperada:

264531 -rw-r--r-- 2 joan joan 10485760 set  1 13:02 dur.dat
264531 -rw-r--r-- 2 joan joan 10485760 set  1 13:02 original.dat
264532 lrwxrwxrwx 1 joan joan       12 set  1 13:02 tou.dat -> original.dat

(a) original.dat i dur.dat comparteixen l'inode 264531; tou.dat té el 264532, propi. L'enllaç dur és una entrada de directori més que apunta al mateix inode; el simbòlic és un fitxer diferent el contingut del qual són els 12 caràcters original.dat.

(b) L'espai lliure no ha canviat després d'ln: un enllaç dur afegeix 24 bytes a un bloc del directori ja reservat, sense tocar la zona de dades. El camp de l'inode que ho justifica és que els punters a blocs no s'han duplicat: continuen sent els mateixos.

(c) i (e):

rm original.dat
ls -li dur.dat         # → 264531 -rw-r--r-- 1 ... dur.dat
md5sum dur.dat         # el contingut íntegre continua allà

El comptador d'enllaços va passar de 2 a 1. unlink va decrementar el comptador; com que no va arribar a 0, els blocs no es van alliberar. El camp és i_nlink.

(d):

ls -l tou.dat          # l'enllaç continua existint, amb la seva mida 12
cat tou.dat            # cat: tou.dat: No existeix el fitxer o el directori
find . -xtype l        # ./tou.dat

L'enllaç simbòlic guarda el nom original.dat, no l'inode. En desaparèixer aquest nom del directori, la resolució falla amb ENOENT a l'últim component. Curiosament, ln -s dur.dat tou2.dat funcionaria, perquè el contingut continua accessible per aquell altre nom: prova que el simbòlic depèn del nom i el dur de l'inode.

Solució 2

El camí té 6 components: var, lib, meteora, arxiu, 2026, 08, 2026-08-15.dat. En realitat en són 7.

Memòries cau buides. Un accés per a l'inode de l'arrel, i per cada component un per llegir les dades del directori i un altre per llegir l'inode trobat:

1 (inode arrel) + 7 × 2 = 15 accessos.

Amb /var/lib/meteora/arxiu/2026/08/ a la dcache, la resolució arrenca directament des de l'inode de 08/ (que la memòria cau té), i només queda buscar l'últim component: 1 (dades de 08/) + 1 (inode final) = 2 accessos. I si l'inode final també fos a la memòria cau, 0.

Escenari Accessos NVMe (80 µs) HDD (8 ms)
Memòries cau buides 15 1,2 ms 120 ms
Dentries a la memòria cau 2 0,16 ms 16 ms

Els 120 ms de l'HDD fred són la raó que la primera arrencada d'un servei amb molts fitxers sigui tan lenta i la segona gairebé instantània, i una justificació numèrica de la dcache.

Amb arxiu com a enllaç simbòlic a /mnt/historic/meteora: en arribar a arxiu, el nucli llegeix el seu inode, detecta el tipus l, i —si el camí ocupa menys de 60 bytes, com aquí (24)— el llegeix del mateix inode, sense accés extra. Després reinicia la resolució amb el nou camí absolut: arrel, mnt, historic, meteora, i després 2026, 08 i el fitxer.

Recompte en fred: 1 (arrel) + 2×3 (var, lib, meteora) + 2 (arxiu, l'inode del qual ja porta la destinació) + 1 (arrel una altra vegada, segurament a la memòria cau) + 2×3 (mnt, historic, meteora) + 2×3 (2026, 08, fitxer) = ~22 accessos, uns 7 més. Si l'enllaç fes 60 bytes o més, caldria llegir a més el seu bloc de dades: un accés addicional per cada enllaç travessat. És poc, però explica per què les cadenes llargues d'enllaços simbòlics en camins crítics es noten.

Solució 3

Reproducció:

# Terminal 1
( while :; do dd if=/dev/zero bs=1M count=10 status=none; sleep 1; done ) > /tmp/gros.log &
echo $!        # anota el PID, p. ex. 5512

# Terminal 2
sleep 30
df -h /tmp | tail -1     # l'ús puja
rm /tmp/gros.log
ls -l /tmp/gros.log      # No existeix
df -h /tmp | tail -1     # l'ús NO baixa, i continua pujant!
du -sh /tmp              # du mostra molt menys que df: la discrepància

Diagnòstic:

sudo lsof +L1 /tmp
# COMMAND  PID USER  FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
# dd      5513 joan   1w  REG   0,25 943718400     0  312 /tmp/gros.log (deleted)

NLINK 0 i (deleted): l'inode 312 no té cap nom però continua viu perquè el descriptor 1 del procés 5513 el manté obert. unlink va completar els passos 1-3 i es va aturar al 5.

Resolució sense matar el procés:

sudo truncate -s 0 /proc/5513/fd/1
df -h /tmp | tail -1     # l'espai torna immediatament

/proc/<pid>/fd/1 dona accés a l'inode orfe a través del descriptor obert. Truncar-lo a 0 allibera tots els seus blocs a l'instant. El procés continua escrivint —en un fitxer que ara comença de zero—, sense haver-se'n assabentat.

Per què existeix copytruncate. Quan logrotate reanomena un registre i en crea un de nou, un servei que mantingui el descriptor obert continuarà escrivint al fitxer reanomenat, perquè el descriptor apunta a l'inode, no al nom (aquesta és tota la lliçó). Hi ha dues solucions: enviar SIGHUP al servei perquè reobri el fitxer pel seu nom —l'elegant, i el que fa Meteora des de 03-03—, o fer servir copytruncate, que copia el contingut i trunca l'original a zero sense canviar l'inode, de manera que el descriptor del servei continua sent vàlid. És l'opció per a programes que no saben reobrir el seu registre, i té el risc de perdre les línies escrites entre la còpia i el truncament.

Amb truncate -s 0 en lloc de rm no hi hauria hagut incident en cap moment: el nom continua existint, l'inode continua sent el mateix, el descriptor del procés continua sent vàlid, i els blocs s'alliberen immediatament. L'única peculiaritat és que un procés amb O_APPEND continua al final (que ara és 0) mentre que un sense O_APPEND manté el seu desplaçament antic i crea un fitxer dispers (04-04). Per això la regla és: per buidar un registre en ús, truncate -s 0 o : > fitxer, mai rm.

Conclusió

Un directori és un fitxer el contingut del qual és una taula de parells (nom, número d'inode). L'hem obert amb debugfs i hem vist l'estructura real d'ext4: 8 bytes de capçalera amb inode, rec_len, name_len i file_type, més el nom. D'aquest format surten dos comportaments que es veuen cada dia: els directoris no encongeixen en esborrar entrades, i un mateix inode pot aparèixer en diverses entrades, que és la definició d'enllaç dur.

L'organització en graf acíclic d'UNIX és el quart intent històric, i cadascun dels anteriors va caure per un motiu concret: el nivell únic per les col·lisions de noms, els dos nivells per la seva rigidesa, l'arbre pur per no permetre compartir. El graf general es descarta per dos problemes greus —recorreguts infinits i escombraries inabastables que el comptador d'enllaços no detecta—, i per això UNIX prohibeix els enllaços durs a directoris: una sola restricció que garanteix l'aciclicitat i fa del comptador d'enllaços un recol·lector d'escombraries suficient.

La resolució de camins és un bucle component a component que arrenca a l'arrel o al cwd del procés, comprova tipus i permís a cada pas, i expandeix els enllaços simbòlics amb un topall de 40 per evitar ELOOP. Amb memòries cau buides costa 11 accessos a disc per a /var/lib/meteora/lectures/2026-08-31.dat —0,9 ms en NVMe, 88 ms en HDD—, i amb la memòria cau de dentries calenta baixa a un o a cap. Aquells 78 MiB de dentries a meteo-01 són la raó que el sistema de fitxers sembli instantani.

Els dos tipus d'enllaç es distingeixen per una sola cosa, de la qual se'n dedueix tota la resta: el dur apunta a un inode i el simbòlic a un nom. Per això el dur no creua sistemes de fitxers ni enllaça directoris, sobreviu a l'esborrat i al reanomenament, i no té «original»; i per això el simbòlic creua el que vulgui, es trenca amb facilitat i costa una resolució extra —encara que a ext4, si fa menys de 60 caràcters, ni tan sols consumeix un bloc—.

El cost dels directoris enormes és un problema clàssic d'estructures de dades: la llista lineal fa la cerca O(n) i la creació de n fitxers O(n²), mentre que l'htree d'ext4 (dir_index) resol en tres accessos el que costava tres-cents. Però fins i tot així, cent mil fitxers en un directori continuen sent mala idea, i la resposta correcta és repartir en subdirectoris, com fa l'arxiu històric de Meteora a arxiu/2026/08/.

L'FHS explica per què cada camí de Meteora és on és, i la seva lògica es resumeix en una frase que convé memoritzar: la ubicació d'un fitxer declara les seves garanties de persistència. /var/lib per a les dades que han de durar, /etc per a la configuració d'aquesta màquina, /var/log per als registres, i /run i /dev/shm —que són tmpfs— per al que ha de desaparèixer en reiniciar.

I unlink tanca el cercle de l'inode sense nom: treu una entrada de directori, decrementa el comptador d'enllaços, i allibera els blocs només si el comptador arriba a 0 i ningú no el té obert. D'aquí l'incident dels 17 GB que no s'alliberen, que es diagnostica amb lsof +L1 i es cura amb truncate -s 0 /proc/<pid>/fd/N sense reiniciar res.

Ens falta l'última baula. Hem dit «el sistema de fitxers de /var/lib/meteora», «el de /run», «el de /proc», com si l'arbre únic que recorrem amb els camins estigués fet de peces diferents. I ho està: / és un ext4 sobre una partició, /var/lib/meteora és un altre ext4 sobre el RAID /dev/md0, /run és tmpfs a la RAM, /proc no té dispositiu al darrere. Com s'enganxen totes aquestes peces en un arbre continu pel qual la resolució de camins camina sense assabentar-se del canvi? Què passa exactament en executar mount? I com pot el mateix open() funcionar sobre un SSD, sobre la RAM i sobre un servidor a l'altra banda de la xarxa?

És el que veurem a Particions, Muntatge i Sistema de Fitxers Virtual.

Fonaments de Sistemes Operatius

Mòdul 1: Introducció als Sistemes Operatius

Mòdul 2: Gestió de Recursos

Mòdul 3: Concurrència

Mòdul 4: Estructures de Fitxers

Mòdul 5: Protecció i Seguretat del Sistema

Mòdul 6: Virtualització i Contenidors

Mòdul 7: Administració i Diagnòstic a la Pràctica

© Copyright 2026. Tots els drets reservats