A la lliçó anterior vam arribar a la idea de la paginació i la vam deixar plantejada: si totes les peces fan la mateixa mida, encaixar-les deixa de ser un problema. Ara toca complir la promesa. Aquesta és la lliçó més densa del mòdul i probablement la més rendible de tot el curs, perquè la memòria virtual és el mecanisme que explica una quantitat enorme de coses que veuràs en producció: per què un procés reserva 400 MB i només n'usa 31, per què la primera petició a meteo-api després d'un desplegament és lenta i les següents no, per què un servidor que comença a paginar deixa de respondre de cop, i per què l'OOM killer tria el que tria.
Veuràs la traducció d'adreces amb números concrets, calcularàs la mida impossible d'una taula plana de 64 bits, mesuraràs quant rendiment depèn de la TLB, seguiràs una fallada de pàgina pas a pas, resoldràs els algorismes de reemplaçament sobre una mateixa cadena de referències i mapejaràs el fitxer 2026-08-31.dat en memòria amb mmap(). Al final sabràs interpretar free -h, vmstat i el rastre de l'OOM killer amb criteri.
Contingut
- Pàgines i marcs
- Traducció d'adreces, amb un exemple numèric complet
- La taula de pàgines i els seus bits de control
- Per què una taula plana és impossible en 64 bits
- Taules multinivell
- La TLB i el temps efectiu d'accés
- Pàgines grans (huge pages)
- Paginació per demanda i la fallada de pàgina
- Algorismes de reemplaçament de pàgines
- Assignació de marcs, hiperpaginació i conjunt de treball
- Fitxers mapejats i memòria compartida amb
mmap() - Copy-on-write, revisitat
- Swap,
swappinessi l'OOM killer - Mesura:
VSZenfront deRSS,/proc/meminfo,free -h,vmstat
Pàgines i marcs
Els dos termes són la base de tot el vocabulari i convé fixar-los sense ambigüitat:
| Pàgina | Marc (frame) | |
|---|---|---|
| On viu | Espai lògic del procés | Memòria física |
| Mida | 4 KB (típica) | 4 KB (la mateixa, obligatòriament) |
| Quantes n'hi ha | Fins a 2^36 a x86-64 | RAM / 4 KB |
| Es numeren des de | 0, per procés | 0, globalment |
Comprova la mida de pàgina del teu sistema:
I els números de meteo-01:
Espai lògic per procés: 2^47 bytes = 128 TB → 2^35 pàgines RAM instal·lada: 8 GB = 8.589.934.592 bytes → 2.097.152 marcs
Hi ha trenta-quatre mil milions de vegades més pàgines possibles que marcs disponibles. Aquesta desproporció és exactament el que fa possible la memòria virtual: la immensa majoria de les pàgines no existirà mai, i les que existeixin no han d'estar totes a la RAM alhora.
Traducció d'adreces, amb un exemple numèric complet
Una adreça lògica es parteix en dos camps:
┌────────────────────────┬──────────────────────┐ │ Número de pàgina (p) │ Desplaçament (d) │ └────────────────────────┴──────────────────────┘
- El desplaçament ocupa tants bits com calgui per adreçar dins d'una pàgina. Amb pàgines de 4 KB = 2^12 bytes, són 12 bits.
- El número de pàgina ocupa la resta.
La traducció té tres passos:
- Extreure
pidde l'adreça lògica. - Buscar a la taula de pàgines l'entrada
p, que conté el número de marcf. - L'adreça física és
f × mida_pàgina + d.
El desplaçament no es tradueix mai. Pàgina i marc fan la mateixa mida, així que la posició dins de la pàgina és idèntica a la posició dins del marc. Només canvia quin bloc, no on dins del bloc.
Exemple complet
Treballem amb un espai lògic petit per poder veure-ho tot: adreces de 16 bits i pàgines de 4 KB.
Adreça lògica: 16 bits Pàgina: 4 KB = 2^12 → desplaçament de 12 bits Número de pàgina: 16 − 12 = 4 bits → 16 pàgines (0 a 15)
Taula de pàgines de l'ingestor:
| Pàgina | Marc | Present |
|---|---|---|
| 0 | 5 | Sí |
| 1 | 9 | Sí |
| 2 | 2 | Sí |
| 3 | — | No |
| 4 | 7 | Sí |
Traduir l'adreça lògica 0x2A5C.
Pas 1, descompondre:
0x2A5C en binari: 0010 1010 0101 1100
└──┘ └────────────┘
p d
p = 0010₂ = 2
d = 1010 0101 1100₂ = 0xA5C = 2652Comprovació aritmètica, que sol ser més ràpida:
Pas 2, consultar la taula: pàgina 2 → marc 2, present.
Pas 3, compondre l'adreça física:
Ha coincidit amb la lògica per casualitat (la pàgina 2 és al marc 2). Provem-ne una altra.
Traduir 0x105C:
p = 0x105C / 4096 = 4188 / 4096 = 1 d = 4188 − 4096 = 92 Taula: pàgina 1 → marc 9 física = 9 × 4096 + 92 = 36864 + 92 = 36956 = 0x905C
Fixa't en un detall revelador: 0x105C → 0x905C. Els tres dígits hexadecimals de la dreta no canvien (05C), perquè són el desplaçament. Només canvia el dígit de l'esquerra: 1 → 9, de pàgina a marc. En hexadecimal la traducció és visualment evident quan la mida de pàgina és potència de 16.
Traduir 0x3200:
Aquí no hi ha traducció possible. La MMU genera una excepció i el nucli pren el control. Què fa aleshores és l'apartat 8.
La taula de pàgines i els seus bits de control
Cada entrada de la taula de pàgines (PTE, Page Table Entry) ocupa 8 bytes a x86-64 i conté molt més que un número de marc:
| Bit | Nom | Què significa | Qui el posa |
|---|---|---|---|
| 0 | P (Present) | La pàgina és a la RAM | El nucli |
| 1 | R/W | 0 = només lectura, 1 = lectura/escriptura | El nucli |
| 2 | U/S | 0 = només mode nucli, 1 = accessible en usuari | El nucli |
| 3 | PWT | Política de memòria cau write-through | El nucli |
| 4 | PCD | Memòria cau deshabilitada (per a E/S mapejada) | El nucli |
| 5 | A (Accessed) | S'ha accedit a la pàgina | El maquinari |
| 6 | D (Dirty) | S'ha escrit a la pàgina | El maquinari |
| 7 | PS (Page Size) | És una pàgina gran (2 MB o 1 GB) | El nucli |
| 8 | G (Global) | No s'invalida en canviar de procés | El nucli |
| 12-51 | Número de marc | Els 40 bits del marc físic | El nucli |
| 63 | NX (No eXecute) | La pàgina no es pot executar | El nucli |
Els que importen de debò i per què:
Bit P (present). És l'interruptor de tota la memòria virtual. Si val 0, qualsevol accés provoca una fallada de pàgina. El nucli el fa servir per a tres situacions diferents, i les distingeix amb els bits restants de l'entrada, que queden lliures quan P=0: pàgina mai carregada, pàgina expulsada a swap, o adreça simplement no vàlida.
Bits R/W i NX. Aquí és on s'implementa la protecció per regió que vèiem a /proc/<pid>/maps. La regió r-xp del codi té R/W=0 (no escrivible); les regions rw-p de pila i monticle tenen NX=1 (no executable). Aquest parell de bits és la política W^X.
Bits A i D. Són especials perquè els escriu el maquinari, no el sistema operatiu. Cada vegada que la CPU accedeix a una pàgina hi posa A=1; cada vegada que hi escriu hi posa D=1. El nucli els llegeix i els neteja periòdicament. Sense ells seria impossible implementar els algorismes de reemplaçament: el nucli no té manera d'observar cada accés a memòria, així que necessita que el maquinari li deixi aquesta pista.
Bit D (brut). Determina el cost d'expulsar la pàgina. Si D=0, la pàgina no ha canviat des que es va carregar, així que es pot descartar sense més (si torna a caldre es rellegeix del fitxer). Si D=1, cal escriure-la a swap abans de reutilitzar el marc. És exactament la distinció de la columna Dirty de pmap que vam veure a 02-03, i la diferència de cost és de zero enfront de mil·lisegons.
Per què una taula plana és impossible en 64 bits
Fem el càlcul que justifica tota la complexitat que ve després.
A x86-64 s'usen actualment 48 bits d'adreça virtual (els 16 superiors són extensió de signe). Amb pàgines de 4 KB:
Bits per al desplaçament: 12 Bits per al número de pàgina: 48 − 12 = 36 Nombre de pàgines possibles: 2^36 = 68.719.476.736 Mida d'una taula plana: 68.719.476.736 entrades × 8 bytes = 549.755.813.888 bytes = 512 GB
512 GB de taula de pàgines. Per procés. En una màquina de 8 GB amb 180 processos, caldrien 92 TB només per a les taules.
És absurd, i ho és per una raó molt concreta: la taula plana reserva una entrada per a cada pàgina possible, incloses les que no existiran mai. Recorda el mapa de memòria de l'ingestor: el codi és a 0x400000 i la pila a 0x7ffd8b3a1000. Entre tots dos hi ha un abisme de 140 TB d'adreces que no es faran servir mai, i una taula plana necessitaria una entrada per a cadascuna.
El procés real fa servir unes 4.500 pàgines dels 68.000 milions possibles: el 0,0000065 %. L'estructura ha d'aprofitar aquesta dispersió extrema.
Taules multinivell
La solució és fer la taula jeràrquica i dispersa: dividir el número de pàgina en diversos camps, cadascun indexant un nivell, i crear només les taules de nivell inferior que realment calguin.
x86-64 fa servir quatre nivells (cinc a les CPU més recents). El número de pàgina de 36 bits es parteix en quatre camps de 9 bits:
┌────────┬────────┬────────┬────────┬──────────────┐ │ PML4 │ PDPT │ PD │ PT │ Desplaç. 12 │ │ 9 bits │ 9 bits │ 9 bits │ 9 bits │ │ └────────┴────────┴────────┴────────┴──────────────┘
Cada nivell té 2^9 = 512 entrades de 8 bytes = exactament 4.096 bytes, una pàgina. Aquest encaix no és casual: cada taula ocupa just una pàgina, cosa que en simplifica enormement la gestió.
flowchart LR
CR3["Registre CR3<br/>(per procés)"] --> PML4
PML4["PML4<br/>512 entrades"] -->|índex 9 bits| PDPT
PDPT["PDPT<br/>512 entrades"] -->|índex 9 bits| PD
PD["Directori<br/>512 entrades"] -->|índex 9 bits| PT
PT["Taula de pàgines<br/>512 entrades"] -->|índex 9 bits| MARC["Marc físic<br/>+ desplaçament"]
L'estalvi és espectacular. Per a un procés que fa servir codi a la part baixa i pila a l'alta:
1 taula PML4: 4 KB
2 taules PDPT (una per zona): 8 KB
2 taules PD: 8 KB
~10 taules PT (per a ~5.000 pàgines): 40 KB
───────
Total: 60 KB60 KB enfront de 512 GB. Un factor de vuit milions.
El preu: una traducció requereix quatre accessos a memòria (un per nivell) més l'accés a les dades. Cinc accessos on abans n'hi havia un. A ~100 ns per accés a RAM, això serien 500 ns per cada lectura de memòria: el sistema seria 5 vegades més lent que sense paginació. Inacceptable.
Aquesta és la raó exacta per la qual existeix la TLB.
La TLB i el temps efectiu d'accés
La TLB (Translation Lookaside Buffer) és una memòria cau associativa, dins de la MMU, que guarda les traduccions pàgina→marc usades recentment. És petita (entre 64 i 1.536 entrades a les CPU modernes) i rapidíssima (menys d'1 ns).
El flux:
- La CPU genera una adreça lògica.
- La MMU busca el número de pàgina a la TLB.
- Encert de TLB: s'obté el marc directament. Cost ≈ 0.
- Fallada de TLB: cal recórrer els quatre nivells de taules en memòria i després inserir el resultat a la TLB.
El càlcul del temps efectiu d'accés (TEA) és la fórmula que cal saber fer:
on h és la taxa d'encerts, t_TLB el temps de consulta de la TLB, t_mem l'accés a memòria i n el nombre de nivells.
Amb valors realistes (t_TLB = 1 ns, t_mem = 100 ns, n = 4):
| Taxa d'encerts | Càlcul | TEA | Degradació |
|---|---|---|---|
| 100 % | 1 + 100 | 101 ns | 1,00× |
| 99 % | 0,99×101 + 0,01×501 | 105 ns | 1,04× |
| 95 % | 0,95×101 + 0,05×501 | 121 ns | 1,20× |
| 90 % | 0,90×101 + 0,10×501 | 141 ns | 1,40× |
| 70 % | 0,70×101 + 0,30×501 | 221 ns | 2,19× |
| 50 % | 0,50×101 + 0,50×501 | 301 ns | 2,98× |
Detall del cas del 99 %:
Encert (99 %): 1 ns (TLB) + 100 ns (dada) = 101 ns Fallada (1 %): 1 ns + 4×100 ns (taules) + 100 ns (dada) = 501 ns TEA = 0,99 × 101 + 0,01 × 501 = 99,99 + 5,01 = 105,0 ns
La conclusió pràctica és contundent: amb un 99 % d'encerts només perds un 4 %; amb un 70 % perds més del doble de rendiment. La TLB no és una optimització menor, és el que fa viable la paginació.
I per això importa tant el cost del canvi de context que vam calcular a 02-01: en canviar cr3, les entrades de TLB del procés anterior deixen de valer i s'invaliden. El procés entrant arrenca amb la TLB buida i una ràfega de fallades. Els PCID (identificadors de context de procés) ho mitiguen etiquetant cada entrada amb el procés a què pertany, de manera que no calgui buidar-la sencera.
Pots veure les fallades de TLB reals:
$ sudo perf stat -e dTLB-load-misses,dTLB-loads -p 1877 sleep 10
Performance counter stats for process id '1877':
12.847.331 dTLB-load-misses # 0,84% of all dTLB cache accesses
1.529.204.882 dTLB-loads
10,003 seconds time elapsedUn 0,84 % de fallades, és a dir un 99,16 % d'encerts. Perfectament sa. Si hi veiessis un 15 % de fallades, tindries un procés amb un patró d'accés molt dispers, i aquí és on entren les pàgines grans.
Pàgines grans (huge pages)
El problema apareix quan un procés treballa amb conjunts de dades enormes. L'agregador processant un dia sencer de lectures:
Dades d'un dia: 17 MB Pàgines de 4 KB necessàries: 17.000.000 / 4.096 ≈ 4.150 pàgines Entrades de TLB disponibles (típic L2 TLB): 1.536
No hi caben. Recórrer els 17 MB provoca fallades de TLB contínues, perquè les traduccions s'expulsen entre elles. I amb 30 dies d'històric serien 124.000 pàgines: la TLB seria inútil.
Les pàgines grans resolen això fent servir una mida de pàgina més gran: 2 MB o 1 GB a x86-64. S'aconsegueix aturant la jerarquia un nivell abans (una entrada del directori de pàgines apunta directament a un bloc de 2 MB en lloc d'a una taula).
| 4 KB | 2 MB | 1 GB | |
|---|---|---|---|
| Pàgines per a 17 MB | 4.150 | 9 | 1 |
| Entrades de TLB usades | 4.150 | 9 | 1 |
| Nivells de traducció | 4 | 3 | 2 |
| Fragmentació interna mitjana | 2 KB | 1 MB | 512 MB |
| Granularitat d'expulsió a swap | Fina | Grossa | Inviable |
Els 17 MB de l'agregador passen de 4.150 entrades de TLB a 9. Amb això hi caben de sobres i les fallades de TLB pràcticament desapareixen.
A Linux hi ha dues maneres de fer-les servir:
$ cat /sys/kernel/mm/transparent_hugepage/enabled [always] madvise never $ grep -i huge /proc/meminfo AnonHugePages: 215040 kB HugePages_Total: 0 HugePages_Free: 0 Hugepagesize: 2048 kB
- THP (Transparent Huge Pages): el nucli promou automàticament regions grans a pàgines de 2 MB. Els 215 MB d'
AnonHugePagesindiquen que ja està passant. És còmode, però té un cost conegut: el dimonikhugepagedcompacta memòria en segon pla i pot introduir pauses. Per això moltes bases de dades recomanen posar-lo amadviseonever. - Huge pages explícites: es reserven en arrencar i l'aplicació les demana expressament. Més control, menys comoditat.
Regla de decisió: les pàgines grans ajuden quan el conjunt de treball és gran i es recorre de manera dispersa; fan nosa quan la memòria és escassa i cal paginar, perquè expulsar 2 MB de cop és molt car.
Paginació per demanda i la fallada de pàgina
Aquí arriba la peça que converteix la paginació en memòria virtual: no cal que totes les pàgines d'un procés siguin a la RAM.
La paginació per demanda consisteix a no carregar res fins que es necessiti. En arrencar meteo-api, el nucli no llegeix els seus 820 KB de codi: crea les entrades amb P=0 i deixa que les fallades de pàgina portin el que calgui. Això explica les dades que vam veure a 02-03: libssl amb 12 KB d'1.024 KB carregats.
Quan la CPU accedeix a una pàgina amb P=0:
sequenceDiagram
participant P as Procés
participant M as MMU
participant K as Nucli
participant D as Disc
P->>M: accés a l'adreça 0x3200
M->>M: consulta la taula: bit P = 0
M->>K: excepció de fallada de pàgina (#PF)<br/>adreça a CR2
K->>K: és una adreça vàlida del procés?
alt Adreça no vàlida
K->>P: SIGSEGV → violació de segment
else Adreça vàlida
K->>K: busca un marc lliure
alt No hi ha marcs lliures
K->>K: tria una víctima (algorisme de reemplaçament)
K->>D: si és bruta, l'escriu a swap
end
K->>D: llegeix la pàgina del fitxer o del swap
D-->>K: dades (0,1 - 10 ms)
K->>K: actualitza la PTE: marc i P = 1
K->>P: reintenta la instrucció que ha fallat
end
Un detall essencial del final: la instrucció que ha fallat es torna a executar des de zero. El procés no se n'assabenta de res. Per a ell, aquell accés a memòria simplement ha trigat molt.
El cost real d'una fallada de pàgina
Aquí és on els números fan mal. Hi ha dos tipus de fallada molt diferents:
| Tipus | Què passa | Cost | Exemple |
|---|---|---|---|
| Fallada menor (minor) | La pàgina és a la RAM però no mapejada en aquest procés | 1-3 µs | Memòria cau de pàgines, COW, biblioteca ja carregada |
| Fallada major (major) | Cal llegir-la del disc | 0,1-10 ms | Primera lectura d'un fitxer, pàgina a swap |
La diferència és de tres a quatre ordres de magnitud:
Fallada menor amb SSD NVMe: ~2 µs Fallada major amb SSD NVMe: ~100 µs (50 vegades més) Fallada major amb disc dur: ~8 ms (4.000 vegades més)
I ara el càlcul que explica per què la taxa de fallades majors ha de ser minúscula. Amb accés normal a memòria de 100 ns i fallada major de 8 ms:
Taxa de fallades p |
TEA | Degradació |
|---|---|---|
| 0 | 100 ns | 1× |
| 1 de cada 1.000.000 | 108 ns | 1,08× |
| 1 de cada 100.000 | 180 ns | 1,8× |
| 1 de cada 10.000 | 900 ns | 9× |
| 1 de cada 1.000 | 8.100 ns | 81× |
Amb una fallada major per cada mil accessos, el sistema va 81 vegades més lent. Per mantenir la degradació per sota del 10 % cal menys d'una fallada per cada milió d'accessos.
Això no és una curiositat acadèmica: és exactament el que li passa a un servidor que comença a paginar. No es degrada suaument, cau per un precipici. I és la raó de la hiperpaginació que veurem a l'apartat 10.
Pots veure les fallades dels teus processos:
$ ps -eo pid,min_flt,maj_flt,comm -u meteora
PID MINFL MAJFL COMMAND
1842 84213 3 ingestor
1877 291045 112 agregador
1901 138922 8 meteo-apiLes fallades menors són normals i abundants: formen part del funcionament habitual. Les majors són les que importen: 112 a l'agregador després de 22 hores és perfectament sa. Si en veiessis 400.000, el procés estaria llegint constantment de swap i aquest seria el problema a resoldre.
Algorismes de reemplaçament de pàgines
Quan es produeix una fallada de pàgina i no queden marcs lliures, cal expulsar algú. L'elecció determina quantes fallades hi haurà després.
Resoldrem tots els algorismes sobre la mateixa cadena de referències, que és l'única manera honesta de comparar-los:
Òptim (OPT)
Expulsa la pàgina que trigarà més a tornar-se a usar. És impossible d'implementar (requereix conèixer el futur), però serveix com a fita inferior amb què comparar.
| Ref | Marcs | Fallada | Víctima i per què |
|---|---|---|---|
| 7 | [7,-,-] | ● | — |
| 0 | [7,0,-] | ● | — |
| 1 | [7,0,1] | ● | — |
| 2 | [2,0,1] | ● | 7 (no torna fins al final) |
| 0 | [2,0,1] | ja hi és | |
| 3 | [2,0,3] | ● | 1 (torna a la posició 14) |
| 0 | [2,0,3] | ja hi és | |
| 4 | [2,4,3] | ● | 0 (torna més tard que 2 i 3) |
| 2 | [2,4,3] | ||
| 3 | [2,4,3] | ||
| 0 | [2,0,3] | ● | 4 (no torna mai) |
| 3 | [2,0,3] | ||
| 2 | [2,0,3] | ||
| 1 | [2,0,1] | ● | 3 (no torna) |
| 2 | [2,0,1] | ||
| 0 | [2,0,1] | ||
| 1 | [2,0,1] | ||
| 7 | [7,0,1] | ● | 2 (no torna) |
| 0 | [7,0,1] | ||
| 1 | [7,0,1] |
9 fallades.
FIFO
Expulsa la pàgina que fa més temps que està carregada, sense mirar-ne l'ús.
| Ref | Marcs (ordre d'arribada) | Fallada |
|---|---|---|
| 7 | [7] | ● |
| 0 | [7,0] | ● |
| 1 | [7,0,1] | ● |
| 2 | [0,1,2] | ● surt 7 |
| 0 | [0,1,2] | |
| 3 | [1,2,3] | ● surt 0 |
| 0 | [2,3,0] | ● surt 1 |
| 4 | [3,0,4] | ● surt 2 |
| 2 | [0,4,2] | ● surt 3 |
| 3 | [4,2,3] | ● surt 0 |
| 0 | [2,3,0] | ● surt 4 |
| 3 | [2,3,0] | |
| 2 | [2,3,0] | |
| 1 | [3,0,1] | ● surt 2 |
| 2 | [0,1,2] | ● surt 3 |
| 0 | [0,1,2] | |
| 1 | [0,1,2] | |
| 7 | [1,2,7] | ● surt 0 |
| 0 | [2,7,0] | ● surt 1 |
| 1 | [7,0,1] | ● surt 2 |
15 fallades. Un 67 % més que l'òptim.
L'anomalia de Belady
FIFO té un defecte que desafia la intuïció: donar-li més memòria pot augmentar les fallades. Amb aquesta cadena:
Amb 3 marcs:
| Ref | 1 | 2 | 3 | 4 | 1 | 2 | 5 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Fallada | ● | ● | ● | ● | ● | ● | ● | ● | ● |
9 fallades.
Amb 4 marcs:
| Ref | 1 | 2 | 3 | 4 | 1 | 2 | 5 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Fallada | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● |
10 fallades. Més memòria, més fallades.
La causa: FIFO no té la propietat de pila (que el conjunt de pàgines amb n marcs estigui contingut en el de n+1 marcs). En afegir un marc, l'ordre d'expulsió canvia del tot i pot expulsar justament el que anava a caldre. LRU i OPT sí que tenen aquesta propietat i per això estan lliures de l'anomalia.
És la raó principal per la qual FIFO pur no es fa servir en cap sistema real: un algorisme del qual no pots afirmar «amb més RAM anirà millor» és inacceptable.
LRU (menys usada recentment)
Expulsa la pàgina que fa més temps que no es fa servir. Es basa en el principi de localitat temporal: allò que s'ha usat fa poc probablement es tornarà a fer servir.
| Ref | Marcs (més recent a la dreta) | Fallada |
|---|---|---|
| 7 | [7] | ● |
| 0 | [7,0] | ● |
| 1 | [7,0,1] | ● |
| 2 | [0,1,2] | ● surt 7 |
| 0 | [1,2,0] | |
| 3 | [2,0,3] | ● surt 1 |
| 0 | [2,3,0] | |
| 4 | [3,0,4] | ● surt 2 |
| 2 | [0,4,2] | ● surt 3 |
| 3 | [4,2,3] | ● surt 0 |
| 0 | [2,3,0] | ● surt 4 |
| 3 | [2,0,3] | |
| 2 | [0,3,2] | |
| 1 | [3,2,1] | ● surt 0 |
| 2 | [3,1,2] | |
| 0 | [1,2,0] | ● surt 3 |
| 1 | [2,0,1] | |
| 7 | [0,1,7] | ● surt 2 |
| 0 | [1,7,0] | |
| 1 | [7,0,1] |
12 fallades. Entre l'òptim (9) i FIFO (15).
El problema de LRU és el seu cost d'implementació. Requereix ordenar les pàgines per últim accés, i això vol dir actualitzar una estructura a cada accés a memòria. Caldria maquinari que, a cada lectura, mogués una entrada al principi d'una llista de milers d'elements. Cap CPU no ho fa, perquè seria caríssim.
Aproximacions a LRU: segona oportunitat i algorisme del rellotge
Com que LRU exacte és inviable, s'aproxima fent servir el bit A (accedit) que el maquinari sí que manté de franc.
L'algorisme del rellotge organitza els marcs en un cercle amb un punter:
- El punter apunta a un marc candidat.
- Si el seu bit A = 0, s'expulsa i el punter avança.
- Si el seu bit A = 1, se li dona una segona oportunitat: es posa A = 0 i el punter avança al següent, sense expulsar res.
- Es repeteix fins a trobar un marc amb A = 0.
┌───────┐
┌───→│ P3 A=1│───┐
│ └───────┘ ↓
┌───────┐ ┌───────┐
│ P0 A=0│ │ P4 A=1│
└───────┘ └───────┘
↑ ┌───────┐ │
└────│ P2 A=0│←──┘
└───────┘
↑ punterLa intuïció és exacta: una pàgina amb A=1 s'ha fet servir des de l'última volta del punter, així que probablement se seguirà usant. Una amb A=0 no s'ha tocat en tota una volta: és bona candidata.
Una variant millor fa servir dos bits, A i D, que combinen ús recent i cost d'expulsió:
| A | D | Interpretació | Prioritat d'expulsió |
|---|---|---|---|
| 0 | 0 | Ni usada ni modificada | 1a: la millor víctima, es descarta de franc |
| 0 | 1 | No usada però modificada | 2a: cal escriure-la, però no la volen |
| 1 | 0 | Usada, no modificada | 3a: es descarta de franc però s'està usant |
| 1 | 1 | Usada i modificada | 4a: la pitjor víctima |
Linux fa servir una variant refinada: dues llistes LRU (activa i inactiva) per zona de memòria, amb promoció entre elles segons els accessos, més el bit A per a l'envelliment. És LRU aproximat, amb el cost amortitzat a gairebé zero.
Comparació final
| Algorisme | Fallades | Enfront de l'òptim | Implementable | Anomalia de Belady |
|---|---|---|---|---|
| Òptim | 9 | — | No | No |
| LRU | 12 | +33 % | Només aproximat | No |
| Rellotge (2a oportunitat) | ~13 | +44 % | Sí, barat | No |
| FIFO | 15 | +67 % | Sí, trivial | Sí |
I aquí hi ha la conclusió pràctica que se sol passar per alt: la diferència entre el millor algorisme possible i un de decent és un 33 %; la diferència entre tenir prou RAM i no tenir-ne és un factor de 81. Optimitzar l'algorisme de reemplaçament importa molt menys que dimensionar bé la memòria.
Assignació de marcs, hiperpaginació i conjunt de treball
Amb 180 processos i 2 milions de marcs, quants marcs li toquen a cada procés?
Assignació equitativa: marcs / processos. Simple i injusta: meteo-api amb 116 MB de dades rebria el mateix que un sshd de 9 MB.
Assignació proporcional: repartir segons la mida de cada procés.
I una distinció important:
- Reemplaçament local: un procés que falla només es pot robar marcs a si mateix. El seu rendiment és predictible però no aprofita la memòria ociosa dels altres.
- Reemplaçament global: pot robar a qualsevol. Millor ús global, però el rendiment d'un procés depèn del comportament dels altres. Linux fa servir reemplaçament global.
Hiperpaginació (thrashing)
Aquí hi ha el fenomen més important d'aquesta secció. Si un procés no té prou marcs per al seu conjunt de treball, entra en un cicle destructiu:
flowchart TD
A["Procés amb pocs marcs"] --> B["Fallada de pàgina"]
B --> C["Expulsa una pàgina<br/>que necessitarà de seguida"]
C --> D["Es bloqueja esperant el disc"]
D --> E["La CPU queda ociosa"]
E --> F["El sistema creu que pot<br/>admetre més processos"]
F --> G["Menys marcs per procés"]
G --> B
El bucle es realimenta: com menys CPU es fa servir, més processos s'admeten, i menys memòria queda per a cadascun.
Els símptomes són inconfusibles i val la pena memoritzar-los:
| Mètrica | Valor en hiperpaginació | Per què |
|---|---|---|
| Ús de CPU | Molt baix (5-15 %) | Tots els processos esperen disc |
%iowait |
Molt alt (60-90 %) | Només hi ha activitat de disc |
| Fallades majors | Milers per segon | Cada accés falla |
Columna si/so de vmstat |
Centenars de MB/s | Swap constant en tots dos sentits |
| Càrrega mitjana | Altíssima | Molts processos en estat D |
| Sensació | El sistema «no respon» | Ni tan sols accepta un ssh |
La combinació CPU al 10 % amb càrrega mitjana de 40 és el diagnòstic. Si veiessis això a meteo-01:
$ vmstat 1 3 procs -----------memory---------- ---swap-- -----io---- --system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 1 24 4194300 22140 1024 81920 48932 51204 62104 51988 8421 21044 4 9 2 85 0 0 27 4194300 19008 1024 79872 52108 49872 64220 50104 9102 23811 3 11 1 85 0
Les dades parlen soles: 85 % de %wa (espera d'E/S), 27 processos bloquejats a la columna b, i si/so al voltant de 50.000 KB/s en totes dues direccions. El sistema està portant i emportant-se les mateixes pàgines contínuament. No hi ha cap ajust de configuració que salvi això: falta memòria.
El model del conjunt de treball
La solució conceptual la va formular Peter Denning el 1968. El conjunt de treball W(t, Δ) és el conjunt de pàgines referenciades per un procés en les últimes Δ referències.
Δ = 10.000 referències
Referències: ...2 6 1 5 7 7 7 5 1 6 2 3 4 1 2 3 4 4 4 3 4 4 4 1 3 2 3 4 4 4 4 3...
└──────── finestra Δ ───────┘
W = {1, 2, 5, 6, 7} W = {3, 4}La regla és simple i potent:
I quan això passa, l'única solució correcta és suspendre processos —reduir el grau de multiprogramació— perquè els que quedin tinguin el seu conjunt de treball complet. És contraintuïtiu però cert: executar menys processos fa que el sistema avanci més.
Linux no implementa el model de Denning literalment, però la seva pressió de memòria i l'OOM killer persegueixen el mateix objectiu: quan el sistema no pot sostenir-los tots, n'elimina algun en lloc de deixar que ningú avanci.
Fitxers mapejats i memòria compartida amb mmap()
mmap() és una de les crides al sistema més potents d'UNIX: fa que un fitxer aparegui com a memòria.
En lloc d'open + read + copiar a una memòria intermèdia, es mapeja el fitxer a l'espai d'adreces i s'hi accedeix com si fos un array.
/* llegir_lectures.c — compilar: gcc -Wall -o llegir_lectures llegir_lectures.c */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdint.h>
struct Lectura {
uint32_t estacio_id;
uint32_t timestamp;
float temperatura;
float humitat;
float pressio;
uint32_t _reservat; /* completa els 24 bytes */
};
int main(void) {
const char *ruta = "/var/lib/meteora/lectures/2026-08-31.dat";
int fd = open(ruta, O_RDONLY);
if (fd == -1) { perror("open"); return 1; }
struct stat st;
if (fstat(fd, &st) == -1) { perror("fstat"); return 1; }
size_t n = st.st_size / sizeof(struct Lectura);
printf("Fitxer de %ld bytes = %zu lectures\n", (long)st.st_size, n);
/* Mapejar el fitxer sencer en memòria */
struct Lectura *lectures = mmap(NULL, st.st_size,
PROT_READ, MAP_PRIVATE, fd, 0);
if (lectures == MAP_FAILED) { perror("mmap"); return 1; }
close(fd); /* el mapatge sobreviu al tancament del descriptor */
/* Recórrer-lo com si fos un array normal */
double suma = 0.0;
float maxima = -100.0f;
for (size_t i = 0; i < n; i++) {
suma += lectures[i].temperatura;
if (lectures[i].temperatura > maxima)
maxima = lectures[i].temperatura;
}
printf("Temperatura mitjana: %.2f °C\n", suma / n);
printf("Temperatura màxima: %.2f °C\n", maxima);
munmap(lectures, st.st_size);
return 0;
}$ ./llegir_lectures Fitxer de 17280000 bytes = 720000 lectures Temperatura mitjana: 18.43 °C Temperatura màxima: 34.70 °C
Què fa cada part i per què importa:
mmap(NULL, mida, PROT_READ, MAP_PRIVATE, fd, 0)demana al nucli que associï el fitxer a una regió de l'espai d'adreces.NULLdeixa que el nucli triï l'adreça;PROT_READla fa de només lectura;MAP_PRIVATEsignifica que les escriptures (si n'hi hagués) serien copy-on-write i no arribarien al fitxer.- No es llegeix res del disc en aquest moment. El nucli només crea entrades de taula de pàgines amb P=0. Els 17 MB arriben per demanda, quan el bucle els toqui: cada accés a una pàgina nova provoca una fallada de pàgina que la porta.
close(fd)no invalida el mapatge. El mapatge manté la seva pròpia referència al fitxer. És un detall que sorprèn i que permet tancar descriptors sense perdre l'accés.lectures[i].temperaturaés aritmètica de punters normal. El compilador genera un accés a memòria; la MMU i el nucli fan la resta. No hi ha cap crida al sistema dins del bucle.
Aquest últim punt és la raó de fons per fer servir mmap. Compara-ho amb l'alternativa de read(), aplicant el que vam calcular a 01-06:
read() en blocs de 4 KB |
mmap() |
|
|---|---|---|
| Crides al sistema | 17.280.000 / 4.096 = 4.219 | 1 |
| Cost de syscalls a 1 µs | 4.219 µs = 4,2 ms | 1 µs |
| Còpies de les dades | 2 (disc→memòria cau→memòria intermèdia d'usuari) | 1 (disc→memòria cau) |
| Memòria addicional | La memòria intermèdia del procés | Cap |
| Si dos processos llegeixen el mateix fitxer | Dues còpies a la RAM | Comparteixen les mateixes pàgines |
| Accés aleatori | lseek + read |
Indexació directa |
L'última fila és especialment valuosa per a Meteora: si agregador i meteo-api mapegen tots dos 2026-08-31.dat, les pàgines de la memòria cau del nucli es comparteixen físicament. Un sol joc de 17 MB a la RAM serveix els dos processos.
Quan mmap no convé: per a lectures seqüencials d'una sola passada de fitxers molt grans, read() amb una memòria intermèdia gran pot ser igual o millor, perquè mmap provoca una fallada de pàgina per cada 4 KB (milers d'excepcions), i perquè un fitxer més gran que la RAM mapejat sencer pot provocar pressió de memòria.
mmap per a memòria compartida
Amb MAP_SHARED en lloc de MAP_PRIVATE, les escriptures sí que es propaguen: al fitxer i a tots els processos que el mapegin.
int fd = open("/dev/shm/meteora-cache", O_RDWR | O_CREAT, 0640);
ftruncate(fd, 8 * 1024 * 1024);
void *cache = mmap(NULL, 8 * 1024 * 1024,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);Això és exactament la regió rw-s- que vam veure al pmap de meteo-api a 02-03: els quatre treballadors comparteixen una única memòria cau de 8 MB. Els mecanismes de comunicació entre processos i la sincronització que això exigeix corresponen a Comunicació entre Processos i Sincronització.
Copy-on-write, revisitat
Ara que coneixes els bits de la taula de pàgines, el COW de fork() que vam veure a 02-01 es pot explicar exactament:
fork()copia les taules de pàgines del pare al fill.- A totes dues còpies, totes les pàgines escrivibles es marquen amb R/W = 0 (només lectura), i el nucli anota internament que són COW.
- Tots dos processos apunten als mateixos marcs físics. Zero còpies de dades.
- Quan qualsevol dels dos escriu, la MMU detecta R/W=0 i genera una fallada de pàgina de protecció.
- El nucli la distingeix d'un error real (comprova que la regió és COW i no de només lectura genuïna), copia aquella única pàgina de 4 KB, l'assigna en exclusiva al que ha escrit i li posa R/W=1.
- Si el comptador de referències del marc original baixa a 1, l'altre procés també recupera R/W=1: ja no hi ha res a protegir.
Els números per a meteo-api amb els seus 148 MB:
Còpia ingènua: 148 MB / 20 GB/s = 7,4 ms
COW: ~37.000 entrades de taula de pàgines ≈ 300 KB
0,3 MB / 20 GB/s + gestió ≈ 0,5 ms
Factor de millora: ~15×
Si el fill fa execve() immediatament: es copien ~0 pàginesI ara entens també per què fork() pot semblar barat i després costar: si el fill escriu per tota la memòria heretada, acabaràs pagant la còpia pàgina a pàgina, amb una fallada de pàgina per cada 4 KB. Aquesta és la queixa clàssica contra fork() en processos enormes, i la raó que existeixin alternatives com posix_spawn() i vfork().
Swap, swappiness i l'OOM killer
L'àrea d'intercanvi (swap) és l'espai en disc on es guarden les pàgines anònimes expulsades.
$ swapon --show
NAME TYPE SIZE USED PRIO
/dev/sda3 partition 4G 512M -2
$ free -h
total used free shared buff/cache available
Mem: 7,8Gi 3,1Gi 412Mi 528Mi 4,3Gi 3,9Gi
Swap: 4,0Gi 512Mi 3,5GiInterpretació de free -h, que és on gairebé tothom s'equivoca:
| Columna | Què és | Parany habitual |
|---|---|---|
total |
RAM instal·lada | — |
used |
Usada pels processos | — |
free |
Completament sense usar | 412 MB no vol dir que falti memòria |
buff/cache |
Memòria cau de pàgines i memòries intermèdies | S'allibera a l'instant si cal |
available |
El que un procés nou pot obtenir | Aquesta és la columna que importa |
Els 412 MB de free alarmen molta gent sense motiu. Els 4,3 GB de buff/cache són pàgines de fitxers que el nucli manté per si tornen a caldre, i són llencables a l'instant. La memòria realment disponible són els 3,9 GB d'available.
RAM lliure no és RAM aprofitada. Un sistema amb memòria lliure està desaprofitant l'oportunitat de fer memòria cau. El correcte és que
freesigui baix iavailablesigui alt.
swappiness
Controla l'agressivitat amb què el nucli expulsa pàgines anònimes enfront de descartar memòria cau de fitxers:
| Valor | Comportament | Adequat per a |
|---|---|---|
| 0 | Swap només per evitar l'OOM killer | Bases de dades amb prou RAM |
| 1-10 | Molt reticent a fer swap | Servidors sensibles a la latència |
| 60 | Per defecte, equilibrat | Escriptori, ús general |
| 100 | Tracta igual anònimes i de fitxer | Càrregues amb molta lectura de fitxers |
Per a meteo-01 un valor de 10 seria raonable: és preferible descartar memòria cau de pàgines (recuperable amb una lectura seqüencial ràpida) abans que enviar a swap la memòria cau de respostes de meteo-api (que provocaria fallades majors al camí crític de les peticions).
$ sudo sysctl -w vm.swappiness=10 $ echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.d/99-meteora.conf
Un avís important: swappiness=0 no desactiva el swap, i desactivar el swap del tot tampoc no és bona idea. Sense swap, les pàgines anònimes inactives —que en qualsevol sistema són moltes— es queden ocupant RAM eternament, i davant de pressió de memòria el nucli passa directament a l'OOM killer sense tenir alternatives intermèdies.
L'OOM killer
Quan no hi ha memòria ni marcs per expulsar, el nucli activa l'Out Of Memory killer: tria un procés i el mata.
L'elecció es basa en una puntuació:
oom_score és proporcional a la memòria consumida, amb ajustos: penalitza els processos grans i d'usuaris normals, i protegeix els de root i el PID 1. oom_score_adj va de −1000 (no matar-lo mai) a +1000 (matar-lo primer) i és el que pots ajustar tu:
# Protegir l'ingestor: perdre lectures és irreversible $ echo -900 | sudo tee /proc/1842/oom_score_adj # Sacrificar primer la còpia de seguretat $ echo 800 | sudo tee /proc/3901/oom_score_adj
El rastre que deixa al registre:
$ sudo dmesg -T | grep -A4 'Out of memory' [Sun Aug 31 04:12:33 2026] agregador invoked oom-killer: gfp_mask=0x100cca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0 [Sun Aug 31 04:12:33 2026] Mem-Info: [Sun Aug 31 04:12:33 2026] active_anon:1842103 inactive_anon:204118 isolated_anon:0 [Sun Aug 31 04:12:33 2026] Tasks state (memory values in pages): [Sun Aug 31 04:12:33 2026] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name [Sun Aug 31 04:12:33 2026] [ 1877] 998 1877 1204832 1180221 9539584 0 0 agregador [Sun Aug 31 04:12:33 2026] [ 1901] 998 1901 37080 33785 274432 0 0 meteo-api [Sun Aug 31 04:12:33 2026] Out of memory: Killed process 1877 (agregador) total-vm:4819328kB, anon-rss:4720884kB, file-rss:0kB, shmem-rss:0kB, UID:998 pgtables:9316kB oom_score_adj:0
Com llegir-ho línia a línia, que és el que cal saber fer en una guàrdia:
agregador invoked oom-killer: qui va provocar l'OOM va ser l'agregadoren demanar memòria. No implica necessàriament que en sigui el culpable, encara que aquí ho és.- La taula de tasques llista tots els processos amb el seu
rssen pàgines. L'agregadorté 1.180.221 pàgines × 4 KB = 4,5 GB.meteo-apien té 33.785 × 4 KB = 132 MB. Killed process 1877 (agregador)ambanon-rss:4720884kB: es va triar el que més memòria anònima consumia, que és el criteri habitual.anon-rssenfront defile-rss: els 4,7 GB són anònims, és a dir, memòria dinàmica sense suport en fitxer. Matar el procés els allibera immediatament. Si fosfile-rss, matar-lo tot just alliberaria res.
Aquesta xifra de 4,5 GB en un agregador que en condicions normals fa servir 31 MB és el diagnòstic complet: processar un dia sencer carregant-ho tot a memòria en lloc de fer-ho per blocs. La correcció és de disseny —mmap o lectura per blocs—, no de configuració.
Recorda a més el que vam veure a 02-01: quan l'OOM killer mata un procés, aquest mor per SIGKILL (senyal 9), cosa que el teu supervisor detectaria amb WIFSIGNALED(estat) i WTERMSIG(estat) == 9.
Mesura: VSZ enfront de RSS, /proc/meminfo, free -h, vmstat
Tanquem amb les eines i com interpretar-les sense equivocar-se.
$ ps -eo pid,vsz,rss,comm -u meteora
PID VSZ RSS COMMAND
1842 408212 18204 ingestor
1877 412308 31456 agregador
1901 486300 148320 meteo-api| Mètrica | Què mesura | Quan la fas servir | Parany |
|---|---|---|---|
| VSZ | Espai d'adreces reservat | Gairebé mai | Inclou el mai tocat; reservar és de franc |
| RSS | Pàgines realment a la RAM | Estimació ràpida | Compta les compartides a cada procés |
| PSS | RSS amb les compartides dividides | Sumar consum de diversos processos | Només a smaps |
| USS | Memòria exclusiva del procés | Quant s'allibera en matar-lo | Només a smaps |
El problema del RSS amb un exemple concret: si meteo-api té 4 treballadors i cadascun reporta 148 MB de RSS, sumar dona 592 MB, però el consum real pot ser 200 MB perquè comparteixen codi, libc i la memòria cau de /dev/shm. PSS ho resol dividint cada pàgina compartida entre els qui la fan servir:
94 MB reals enfront de 148 MB de RSS. Per comptabilitzar memòria en un servidor amb processos que comparteixen molt, PSS és la mètrica correcta.
/proc/meminfo dona la foto global:
$ grep -E 'MemTotal|MemAvailable|Cached|Dirty|Writeback|AnonPages|Mapped|Slab|SwapTotal|SwapFree' /proc/meminfo MemTotal: 8122448 kB MemAvailable: 4089216 kB Cached: 4198400 kB Dirty: 28160 kB Writeback: 0 kB AnonPages: 3021312 kB Mapped: 412160 kB Slab: 298432 kB SwapTotal: 4194300 kB SwapFree: 3670012 kB
Les línies que informen de debò:
MemAvailable: 3,9 GB. L'única que respon a «quanta memòria puc fer servir?».AnonPages2,9 GB: memòria anònima dels processos. Només pot anar a swap.Cached4 GB: pàgines de fitxer. Llencables a l'instant.Dirty28 MB: modificades però encara no escrites a disc. Si aquesta xifra puja molt, hi ha un coll d'ampolla d'escriptura. El seu buidatge forçat és el que fafsync().Slab298 MB: estructures del mateix nucli (inodes, dentries,task_struct). No és memòria de processos.
I vmstat per veure la dinàmica:
$ vmstat 2 3 procs -----------memory---------- ---swap-- -----io---- --system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 2 0 524288 421904 1024 4198400 0 0 142 288 4211 8877 12 4 83 1 0 1 0 524288 419872 1024 4199424 0 0 88 412 4402 9104 14 5 80 1 0 3 1 524288 418112 1024 4200448 0 16 204 1128 5108 10944 16 6 76 2 0
Les columnes crítiques per a memòria:
si/so(swap in / swap out, en KB/s): la mètrica més important. Unswpdde 512 MB ambsi/soa zero és inofensiu: són pàgines velles expulsades fa temps que ningú no reclama. El greu éssi/sosostinguts: aquí sí que hi ha hiperpaginació.b: processos bloquejats en E/S ininterrompible (l'estatDde 02-01).wa: percentatge de CPU esperant E/S.
La regla de diagnòstic que resumeix la lliçó: mira si/so, no swpd. Tenir swap ocupat és normal; tenir swap en moviment constant significa que falta RAM.
Errors Habituals i Consells
Alarmar-se perquè free és baix. La columna correcta és available. Un sistema sa té poca memòria lliure i molta memòria cau, perquè la RAM ociosa és RAM malbaratada.
Confondre swpd amb estar paginant. Que hi hagi 512 MB a swap no diu res per si sol: poden portar dies allà sense que ningú els reclami. El que indica un problema són si/so sostinguts a vmstat.
Desactivar el swap «perquè el sistema no s'alenteixi». Sense swap, el nucli perd la seva eina intermèdia i davant de pressió de memòria passa directament a l'OOM killer. La configuració raonable és tenir swap i abaixar swappiness, no eliminar-lo.
Sumar els RSS de diversos processos. Dona un total inflat perquè les pàgines compartides es compten diverses vegades. Per sumar, fes servir PSS.
Creure que l'OOM killer mata el culpable. Mata el que té la puntuació més alta, que sol ser el més gran. Si l'agregador provoca l'escassetat i meteo-api és el que té més memòria, mor meteo-api. Protegeix el que és crític amb oom_score_adj negatiu.
Activar THP a tot arreu. Les pàgines transparents de 2 MB ajuden càrregues amb conjunts de treball grans, però khugepaged compactant memòria pot introduir pauses de desenes de mil·lisegons. Les bases de dades solen recomanar madvise o never justament per això.
Interpretar un SIGSEGV com un error del sistema. És la MMU fent la seva feina: el procés ha accedit a una adreça sense traducció vàlida o sense els permisos adequats. Compara l'adreça amb /proc/<pid>/maps per saber si va ser un punter corrupte (cau en un forat) o una escriptura en zona de només lectura.
Consell de diagnòstic: davant d'una sospita de problema de memòria, aquest és l'ordre que funciona: free -h (mirar available), vmstat 1 (mirar si/so i wa), ps -eo pid,rss,maj_flt --sort=-rss | head (qui consumeix i qui falla), pmap -x <pid> (quina regió creix) i dmesg -T | grep -i oom (si ja ha mort algú). Cinc ordres i tens el quadre complet.
Exercicis
Exercici 1: traducció d'adreces i mida de taules
Un sistema té adreces lògiques de 32 bits i pàgines de 4 KB.
- Quants bits ocupen el número de pàgina i el desplaçament? Quantes pàgines hi ha com a màxim?
- Amb aquesta taula de pàgines, tradueix les adreces lògiques
0x00003ABC,0x00001234i0x00006000:
| Pàgina | Marc | Present |
|---|---|---|
| 0 | 0x0A | Sí |
| 1 | 0x1F | Sí |
| 2 | 0x03 | Sí |
| 3 | 0x2C | Sí |
| 4 | — | No |
- Calcula la mida d'una taula plana amb entrades de 4 bytes. Compara-la amb el cas de 64 bits de l'apartat 4 de la lliçó.
- Amb una TLB del 96 % d'encerts, accés a memòria de 80 ns, TLB de 2 ns i 2 nivells de taula, calcula el temps efectiu d'accés.
Exercici 2: comparar algorismes de reemplaçament
L'agregador genera aquesta cadena de referències a pàgines:
- Calcula les fallades de pàgina amb 3 marcs per a: òptim, FIFO i LRU. Mostra l'estat dels marcs a cada pas.
- Repeteix-ho amb 4 marcs.
- Quin algorisme presenta l'anomalia de Belady? Demostra-ho amb els teus números.
- Si cada fallada major costa 8 ms, calcula el temps total perdut en cada cas amb 3 marcs.
Exercici 3: diagnosticar un servidor amb problemes de memòria
meteo-01 respon amb lentitud extrema. Reculls aquestes dades:
$ free -h
total used free shared buff/cache available
Mem: 7,8Gi 7,4Gi 102Mi 12Mi 298Mi 118Mi
Swap: 4,0Gi 3,8Gi 204Mi
$ vmstat 2 3
procs -----------memory---------- ---swap-- -----io---- --system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 31 3985408 104448 512 305152 42104 39882 51204 40118 9821 24102 3 8 1 88 0
1 29 3985408 102112 512 303104 44210 41004 53108 41220 10104 25811 2 9 1 88 0
0 33 3985408 101888 512 301056 43108 40112 52004 40988 9902 24998 3 8 1 88 0
$ ps -eo pid,rss,maj_flt,comm --sort=-rss | head -5
PID RSS MAJFL COMMAND
1877 4720884 892104 agregador
1901 148320 41022 meteo-api
1842 18204 8104 ingestor- Diagnostica què li passa al sistema. Anomena el fenomen i justifica'l amb almenys quatre dades.
- Per què l'ús de CPU és del 3 % si el sistema va lentíssim?
- Identifica la causa arrel. Quanta memòria hauria de fer servir l'
agregador, sabent que processa un dia de lectures? - Proposa tres solucions: una d'immediata, una de configuració i una de disseny. Indica quina és la correcta.
- Escriu el fragment de codi que resoldria el problema d'arrel.
Solucions
Solució 1
1. Descomposició de l'adreça.
Pàgina de 4 KB = 2^12 bytes → desplaçament = 12 bits Número de pàgina = 32 − 12 = 20 bits Pàgines màximes = 2^20 = 1.048.576
2. Traduccions.
0x00003ABC:
En binari: 0000 0000 0000 0000 0011 | 1010 1011 1100
p = 0x00003 = 3 | d = 0xABC = 2748
Aritmèticament: 0x3ABC = 15036; 15036 / 4096 = 3; 15036 % 4096 = 2748
Taula: pàgina 3 → marc 0x2C = 44, present
física = 44 × 4096 + 2748 = 180.224 + 2.748 = 182.972 = 0x0002CABCDrecera hexadecimal: els tres dígits de la dreta (ABC) es conserven i els de l'esquerra passen de 00003 a 0002C.
0x00001234:
p = 0x00001 = 1, d = 0x234 = 564 Taula: pàgina 1 → marc 0x1F = 31 física = 31 × 4096 + 564 = 126.976 + 564 = 127.540 = 0x0001F234
0x00006000:
p = 0x00006 = 6, d = 0 Taula: la pàgina 6 no existeix (només hi ha entrades 0-4) → FALLADA DE PÀGINA per adreça no vàlida → El nucli comprova /proc/pid/maps: no pertany a cap regió → SIGSEGV: violació de segment
És important distingir aquest cas del de la pàgina 4, que sí que existeix però té Present = No: allà la fallada seria recuperable (el nucli la portaria de swap o del fitxer i reintentaria la instrucció), mentre que aquí és un error genuí del programa.
3. Mida de la taula plana en 32 bits.
Comparació amb 64 bits:
| 32 bits | 64 bits (48 usats) | |
|---|---|---|
| Bits de pàgina | 20 | 36 |
| Entrades | 1.048.576 | 68.719.476.736 |
| Bytes per entrada | 4 | 8 |
| Taula plana | 4 MB | 512 GB |
| Amb 180 processos | 720 MB | 92 TB |
I aquí hi ha l'observació clau de l'exercici: 4 MB per procés ja és massa. Amb 180 processos són 720 MB només de taules, gairebé el 9 % dels 8 GB de meteo-01, i la immensa majoria d'aquestes entrades estarien buides. Per això fins i tot els sistemes de 32 bits feien servir taules multinivell (x86 de 32 bits en tenia dos nivells). La taula plana no va ser mai viable; en 64 bits passa d'inviable a directament absurda.
4. Temps efectiu d'accés.
Encert (96 %): 2 ns (TLB) + 80 ns (dada) = 82 ns
Fallada (4 %): 2 ns + 2 × 80 ns (dos nivells) + 80 ns (dada) = 242 ns
TEA = 0,96 × 82 + 0,04 × 242
= 78,72 + 9,68
= 88,4 nsDegradació enfront de l'accés pur (80 ns): 88,4 / 80 = 1,105, un 10,5 %.
Val la pena observar quant pesa aquest 4 % de fallades: aporta 9,68 ns dels 88,4 totals, un 11 % del temps, sent només el 4 % dels accessos. És l'aritmètica típica de les memòries cau, i explica per què millorar del 96 % al 99 % d'encerts val la pena:
Solució 2
1. Amb 3 marcs.
Òptim (expulsa la que trigarà més a reaparèixer):
| Ref | 1 | 2 | 3 | 4 | 1 | 2 | 5 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| M1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 3 | 3 | 3 |
| M2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 4 | 4 | |
| M3 | 3 | 4 | 4 | 4 | 5 | 5 | 5 | 5 | 5 | 5 | ||
| Fallada | ● | ● | ● | ● | ● | ● | ● |
Les decisions d'expulsió: a la posició 4 surt el 3 (reapareix a la 10, més tard que 1 i 2); a la 7 surt el 4 (reapareix a la 11); a la 10 surt l'1 (no torna mai); a la 11 surt el 2 (tampoc no torna).
7 fallades.
FIFO:
| Ref | 1 | 2 | 3 | 4 | 1 | 2 | 5 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| M1 | 1 | 1 | 1 | 4 | 4 | 4 | 5 | 5 | 5 | 5 | 5 | 5 |
| M2 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 3 | 3 | 3 | |
| M3 | 3 | 3 | 3 | 2 | 2 | 2 | 2 | 2 | 4 | 4 | ||
| Fallada | ● | ● | ● | ● | ● | ● | ● | ● | ● |
9 fallades.
LRU:
| Ref | 1 | 2 | 3 | 4 | 1 | 2 | 5 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| M1 | 1 | 1 | 1 | 4 | 4 | 4 | 5 | 5 | 5 | 3 | 3 | 3 |
| M2 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 4 | 4 | |
| M3 | 3 | 3 | 3 | 2 | 2 | 2 | 2 | 2 | 2 | 5 | ||
| Fallada | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● |
10 fallades.
2. Amb 4 marcs.
Òptim:
| Ref | 1 | 2 | 3 | 4 | 1 | 2 | 5 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| M1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 4 | 4 |
| M2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | |
| M3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | ||
| M4 | 4 | 4 | 4 | 5 | 5 | 5 | 5 | 5 | 5 | |||
| Fallada | ● | ● | ● | ● | ● | ● |
Amb 4 marcs hi caben l'1, el 2, el 3 i el 4 des del principi. En arribar el 5 s'expulsa el 4, perquè reapareix a la posició 11, més tard que 1, 2 i 3. A la posició 10 el 3 ja és resident, així que no hi ha fallada. A l'11 cal portar el 4 i s'expulsa l'1, que no es torna a fer servir.
6 fallades.
FIFO:
| Ref | 1 | 2 | 3 | 4 | 1 | 2 | 5 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| M1 | 1 | 1 | 1 | 1 | 1 | 1 | 5 | 5 | 5 | 5 | 4 | 4 |
| M2 | 2 | 2 | 2 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 5 | |
| M3 | 3 | 3 | 3 | 3 | 3 | 3 | 2 | 2 | 2 | 2 | ||
| M4 | 4 | 4 | 4 | 4 | 4 | 4 | 3 | 3 | 3 | |||
| Fallada | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● |
10 fallades.
LRU:
| Ref | 1 | 2 | 3 | 4 | 1 | 2 | 5 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| M1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 5 |
| M2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | |
| M3 | 3 | 3 | 3 | 3 | 5 | 5 | 5 | 5 | 4 | 4 | ||
| M4 | 4 | 4 | 4 | 4 | 4 | 4 | 3 | 3 | 3 | |||
| Fallada | ● | ● | ● | ● | ● | ● | ● | ● |
Traça de les expulsions: en arribar el 5 (posició 7), l'ordre d'ús és 3, 4, 1, 2, així que surt el 3. A la posició 10 cal el 3 i el menys usat recentment és el 4. A l'11 cal el 4 i surt el 5. A la 12 cal el 5 i surt l'1. Fixa't en el patró: les tres últimes referències fallen perquè LRU acaba d'expulsar justament el que es demana tot seguit.
8 fallades.
3. L'anomalia de Belady.
| Algorisme | 3 marcs | 4 marcs | Millora amb més memòria? |
|---|---|---|---|
| Òptim | 7 | 6 | Sí (−1) |
| FIFO | 9 | 10 | NO: empitjora (+1) |
| LRU | 10 | 8 | Sí (−2) |
FIFO presenta l'anomalia: passar de 3 a 4 marcs augmenta les fallades de 9 a 10.
L'explicació estructural: FIFO no compleix la propietat de pila. Formalment, un algorisme la compleix si el conjunt de pàgines residents amb n marcs és sempre un subconjunt del conjunt amb n+1 marcs. LRU i OPT la compleixen perquè la seva decisió depèn del patró de referències, que no canvia en afegir marcs. FIFO decideix per ordre d'arribada, i aquest ordre s'altera completament en canviar el nombre de marcs: amb 4 marcs, les pàgines 1 i 2 sobreviuen més temps i acaben sent expulsades just abans de tornar-se a fer servir.
És un resultat important perquè trenca una intuïció que semblava segura, i és la raó pràctica per la qual cap sistema real no fa servir FIFO pur: no pots prometre que ampliar la RAM millorarà el rendiment.
4. Temps perdut amb 3 marcs.
| Algorisme | Fallades | Temps perdut | Enfront de l'òptim |
|---|---|---|---|
| Òptim | 7 | 7 × 8 ms = 56 ms | — |
| FIFO | 9 | 9 × 8 ms = 72 ms | +28,6 % |
| LRU | 10 | 10 × 8 ms = 80 ms | +42,9 % |
Una observació honesta sobre aquests números: aquí LRU surt pitjor que FIFO, cosa que contradiu la intuïció general. És un artefacte d'aquesta cadena concreta, dissenyada precisament per exhibir l'anomalia de Belady. Amb cadenes reals, que presenten localitat temporal forta, LRU supera clarament FIFO. És un bon recordatori que una cadena de dotze referències no demostra res sobre el comportament general d'un algorisme; per a això calen traces reals de milions de referències.
El que sí que demostra la comparació és l'ordre de magnitud del problema: 12 accessos a memòria que haurien de costar 1,2 microsegons han costat entre 56 i 80 mil·lisegons. Un factor de 50.000. Quan falten marcs, l'algorisme de reemplaçament és el de menys.
Solució 3
1. Diagnòstic: hiperpaginació (thrashing).
Les dades que ho confirmen, una a una:
| Evidència | Valor | Què significa |
|---|---|---|
available |
118 Mi de 7,8 Gi | Memòria pràcticament exhaurida |
| Swap usat | 3,8 Gi de 4,0 Gi | El swap també és ple |
si/so |
~42.000 / ~40.000 KB/s | 40 MB/s en totes dues direccions simultàniament |
wa |
88 % | La CPU no fa res més que esperar disc |
b |
29-33 processos | Gairebé tot el sistema en estat D |
MAJFL de l'agregador |
892.104 | Gairebé un milió de fallades majors |
La dada definitiva és si i so alts alhora. Si només hi hagués so, el sistema estaria alliberant memòria de manera ordenada. Que entri i surti alhora a 40 MB/s significa que les mateixes pàgines s'expulsen i es tornen a portar sense parar: el conjunt de treball no cap a la RAM i cada pàgina expulsada es necessita immediatament després. És la definició exacta del bucle de realimentació del diagrama de la lliçó.
2. Per què la CPU està al 3 %.
Perquè no hi ha res a executar. Gairebé tots els processos són en estat D, bloquejats esperant que el disc porti una pàgina. La columna r (executables) marca 0 o 1, mentre que b (bloquejats) marca 31.
Aquest és el patró més enganyós de tots: CPU gairebé ociosa amb el sistema completament aturat. Qui miri només l'ús de CPU conclourà que el servidor està bé i buscarà el problema en un altre lloc. La combinació que cal reconèixer a l'instant és us+sy baixos + wa altíssim + b alt.
Un càlcul que ho dimensiona: 892.104 fallades majors de l'agregador, a uns 100 µs cadascuna amb SSD, són 89 segons d'espera pura. Amb disc mecànic a 8 ms serien ~2 hores de disc.
3. Causa arrel.
L'agregador té 4.720.884 KB = 4,5 GB de RSS en una màquina de 7,8 GB. Ell sol és el 60 % de la RAM total. Ni meteo-api (148 MB) ni ingestor (18 MB) són rellevants en comparació.
Quant n'hauria de fer servir:
Un dia de lectures: 17 MB (dada del glossari del curs) Estructures d'agregació: Estacions × hores × 5 camps ≈ 50 × 24 × 5 × 8 bytes = 48 KB Memòries intermèdies de treball, libc, codi: ~15 MB Consum raonable: 30-50 MB Consum real: 4.500 MB Factor d'excés: ~100×
I amb 24 bytes per lectura, aquests 4,5 GB equivalen a uns 196 milions de lectures: més de 270 dies de dades. El diagnòstic és immediat: l'agregador està carregant a memòria tot l'històric en lloc del dia que necessita processar, molt probablement per un readdir sobre /var/lib/meteora/lectures/ sense filtre de data, acumulant-ho tot en una estructura en memòria.
4. Tres solucions.
Immediata (recuperar el servidor ara):
Allibera 4,5 GB a l'instant. El sistema deixa de paginar en segons. És un pedaç, no una cura: tornarà a passar a la propera execució.
De configuració (contenir el dany):
# Limitar la memòria del servei amb systemd $ sudo systemctl edit meteora-agregador [Service] MemoryMax=512M MemoryHigh=384M # Protegir els processos crítics de l'OOM killer $ echo -900 | sudo tee /proc/1842/oom_score_adj # ingestor $ echo -500 | sudo tee /proc/1901/oom_score_adj # meteo-api # Reduir la tendència a fer swap $ sudo sysctl -w vm.swappiness=10
Amb MemoryMax=512M (un límit de cgroup, tema de 06-02), l'agregador que intenti passar de 512 MB mor ell sol sense arrossegar la resta del sistema. Això converteix una caiguda global en una fallada aïllada, que és exactament el que es vol. No arregla l'error, però el conté.
De disseny (la correcta):
Processar per flux, no carregant-ho tot. Dues variants vàlides: llegir per blocs amb read(), o mapejar el fitxer del dia amb mmap() i deixar que la paginació per demanda gestioni la memòria.
5. Codi que ho resol.
Versió amb mmap(), que aplica directament el que hem vist a la lliçó:
/* agregar_dia.c — processa UN dia sense carregar-lo sencer a memòria pròpia */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdint.h>
struct Lectura {
uint32_t estacio_id;
uint32_t timestamp;
float temperatura;
float humitat;
float pressio;
uint32_t _reservat;
};
#define MAX_ESTACIONS 64
#define HORES_DIA 24
struct Acumulador {
double suma_temp;
double suma_hum;
double suma_pres;
uint32_t n;
};
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "ús: %s AAAA-MM-DD\n", argv[0]);
return 1;
}
char ruta[256];
snprintf(ruta, sizeof ruta,
"/var/lib/meteora/lectures/%s.dat", argv[1]);
int fd = open(ruta, O_RDONLY);
if (fd == -1) { perror("open"); return 1; }
struct stat st;
if (fstat(fd, &st) == -1) { perror("fstat"); close(fd); return 1; }
struct Lectura *lec = mmap(NULL, st.st_size,
PROT_READ, MAP_PRIVATE, fd, 0);
if (lec == MAP_FAILED) { perror("mmap"); close(fd); return 1; }
close(fd);
/* Avís al nucli: el recorrerem en ordre i no el rellegirem */
madvise(lec, st.st_size, MADV_SEQUENTIAL);
/* Acumuladors: 64 × 24 × 32 bytes = 48 KB, mida FIXA */
static struct Acumulador acc[MAX_ESTACIONS][HORES_DIA];
memset(acc, 0, sizeof acc);
size_t n = st.st_size / sizeof(struct Lectura);
for (size_t i = 0; i < n; i++) {
uint32_t est = lec[i].estacio_id % MAX_ESTACIONS;
uint32_t hora = (lec[i].timestamp / 3600) % HORES_DIA;
struct Acumulador *a = &acc[est][hora];
a->suma_temp += lec[i].temperatura;
a->suma_hum += lec[i].humitat;
a->suma_pres += lec[i].pressio;
a->n++;
}
munmap(lec, st.st_size);
for (int e = 0; e < MAX_ESTACIONS; e++)
for (int h = 0; h < HORES_DIA; h++)
if (acc[e][h].n > 0)
printf("%s %02d:00 est=%d n=%u T=%.2f H=%.1f P=%.1f\n",
argv[1], h, e, acc[e][h].n,
acc[e][h].suma_temp / acc[e][h].n,
acc[e][h].suma_hum / acc[e][h].n,
acc[e][h].suma_pres / acc[e][h].n);
return 0;
}Per què això resol el problema d'arrel:
- Un sol dia per execució. La ruta es construeix amb la data rebuda: és impossible carregar l'històric complet per accident.
- Els acumuladors tenen mida fixa: 64 × 24 × 32 bytes = 48 KB, independent del volum de dades. Aquesta és la clau de l'arranjament: la memòria del procés ja no creix amb l'entrada.
mmapno consumeix RSS propi. Les pàgines mapejades pertanyen a la memòria cau de pàgines del nucli, que és llencable sota pressió de memòria (són netes i tenen suport en fitxer, com vam veure a 02-03). Si falta RAM, el nucli les descarta sense escriure res i les rellegeix després. Enfront dels 4,5 GB anònims de la versió anterior —que només podien anar a swap—, això canvia completament el comportament del sistema sota pressió.madvise(MADV_SEQUENTIAL)diu al nucli que l'accés serà seqüencial. El nucli activa lectura anticipada agressiva i descarta abans les pàgines ja recorregudes. És una optimització petita d'escriure i molt efectiva en recorreguts complets.
Consum resultant:
RSS del procés: ~15 MB (codi + libc + acumuladors) Memòria cau de pàgines: fins a 17 MB, alliberables a l'instant Total efectiu: ~32 MB enfront de 4.500 MB
Un factor de 140 de reducció, i cap pàgina anònima que pugui arrossegar el sistema a la hiperpaginació.
Conclusió
La paginació divideix l'espai lògic en pàgines i la memòria física en marcs de la mateixa mida, i tradueix cada adreça separant número de pàgina i desplaçament —que no es tradueix mai—. Cada entrada de la taula porta el marc i uns bits de control que ho governen tot: P habilita la memòria virtual, R/W i NX implementen W^X, i A i D, que escriu el maquinari, són l'única pista que el nucli té per decidir qui expulsar i a quin cost.
Una taula plana en 64 bits ocuparia 512 GB per procés, així que les taules són multinivell i disperses: quatre nivells de 9 bits, cada taula ocupant exactament una pàgina, i 60 KB reals en lloc de 512 GB. El preu són quatre accessos a memòria per traducció, i per això existeix la TLB: amb un 99 % d'encerts perds un 4 % de rendiment, amb un 70 % perds més del doble. Les pàgines grans redueixen 4.150 entrades de TLB a 9 per als 17 MB de l'agregador, a canvi de fragmentació interna i d'una granularitat d'expulsió grossera.
La paginació per demanda fa que res no es carregui fins que es toca, cosa que explica libc amb només el 44 % del seu codi a la RAM. Una fallada menor costa microsegons i és normal; una fallada major costa mil·lisegons, i amb una per cada mil accessos el sistema va 81 vegades més lent. Els algorismes de reemplaçament —òptim com a fita, FIFO amb la seva anomalia de Belady, LRU inviable en exacte i el rellotge com a aproximació barata fent servir el bit A— es mouen en un marge del 33 %, molt menys del que importa tenir prou RAM. Quan el conjunt de treball no hi cap, apareix la hiperpaginació: CPU al 3 %, wa al 88 % i el sistema aturat.
I tot això es toca amb les mans: mmap() converteix 4.219 crides al sistema en una i permet que dos processos comparteixin físicament el mateix 2026-08-31.dat; el copy-on-write de fork() és simplement R/W=0 més una fallada de protecció; swappiness decideix què se sacrifica abans; i l'OOM killer deixa a dmesg un rastre que es llegeix procés a procés. La regla que resumeix la part operativa: mira available i no free, mira si/so i no swpd, i suma PSS i no RSS.
Amb això tanquem la memòria. Ens queda l'altre gran recurs que el sistema operatiu administra i que ha aparegut a cada fallada major d'aquesta lliçó: l'emmagatzematge. Quant costa realment aquest accés a disc que hem estat comptant en mil·lisegons, per què un SSD canvia totes les regles, com s'ordenen les peticions per minimitzar el moviment del capçal i quina configuració de RAID mereix /var/lib/meteora. És el que veurem a Gestió d'Emmagatzematge.
Fonaments de Sistemes Operatius
Mòdul 1: Introducció als Sistemes Operatius
- Conceptes Bàsics de Sistemes Operatius
- Història i Evolució dels Sistemes Operatius
- Tipus de Sistemes Operatius
- Funcions Principals d'un Sistema Operatiu
- Arquitectura del Nucli: Monolític, Microkernel i Híbrid
- Mode Usuari, Mode Nucli i Crides al Sistema
Mòdul 2: Gestió de Recursos
- Gestió de Processos
- Planificació de la CPU
- Gestió de Memòria
- Memòria Virtual i Paginació
- Gestió d'Emmagatzematge
- Gestió de Dispositius
- Controladors, Interrupcions i Operacions d'E/S
Mòdul 3: Concurrència
- Conceptes de Concurrència
- Fils i Processos
- Comunicació entre Processos (IPC)
- Sincronització i Exclusió Mútua
- Problemes Clàssics de Concurrència
- Interbloquejos: Prevenció, Detecció i Recuperació
Mòdul 4: Estructures de Fitxers
- Sistemes de Fitxers
- Estructures de Directoris
- Particions, Muntatge i Sistema de Fitxers Virtual
- Gestió de Fitxers
- Assignació d'Espai, Journaling i Integritat
- Seguretat i Permisos de Fitxers
Mòdul 5: Protecció i Seguretat del Sistema
- Principis de Protecció i Control d'Accés
- Usuaris, Autenticació i Escalada de Privilegis
- Amenaces Habituals i Enfortiment del Sistema
- Auditoria, Registres i Resposta a Incidents
Mòdul 6: Virtualització i Contenidors
- Virtualització: Hipervisors i Màquines Virtuals
- Contenidors: Namespaces i cgroups
- El Sistema Operatiu al Núvol
- Sistemes Operatius Mòbils i de Temps Real
