Vam acabar la lliçó anterior amb una pregunta molt concreta: si ingestor i agregador tenen tots dos codi a l'adreça 0x400000, com és possible que no es trepitgin? La resposta a aquesta pregunta és la segona gran feina del sistema operatiu, i és més profunda del que sembla: implica el compilador, l'enllaçador, el carregador i un circuit específic dins de la CPU.
En aquesta lliçó entendràs com diversos processos comparteixen una memòria física limitada sense envair-se, quina diferència hi ha entre una adreça lògica i una de física, i per què la solució va evolucionar des d'un simple parell de registres fins als esquemes moderns. Veuràs també els dos tipus de fragmentació amb càlculs concrets, i acabaràs llegint el mapa de memòria real d'un procés de Meteora regió per regió. És la lliçó que prepara el terreny per a la següent, que és la que de debò explica com funciona un sistema modern.
Contingut
- El problema: memòria compartida sense invasions
- Adreces lògiques i adreces físiques
- Les tres fases de vinculació d'adreces
- Reubicació amb registre base i límit
- La MMU: el traductor al camí crític
- Assignació contigua: particions fixes i variables
- Estratègies d'assignació: primer, millor i pitjor ajust
- Fragmentació interna i externa, amb números
- Compactació i per què gairebé mai no es fa servir
- Segmentació
- Intercanvi (swapping) clàssic
- La idea de la paginació
- El mapa de memòria real d'un procés de Meteora
El problema: memòria compartida sense invasions
meteo-01 té 8 GB de RAM i executa uns 180 processos. El sistema operatiu ha de resoldre simultàniament cinc problemes que estiren en direccions diferents:
| Problema | Pregunta | Conseqüència si falla |
|---|---|---|
| Reubicació | On carrego el programa si no sé per endavant quina zona estarà lliure? | El programa no es pot executar |
| Protecció | Com impedeixo que meteo-api llegeixi la memòria d'ingestor? |
Fuita de dades, corrupció |
| Compartició | Com deixo que dos processos comparteixin el codi de libc? | Es malbaraten centenars de MB |
| Organització lògica | Com dono permisos diferents a codi i dades? | Vulnerabilitats d'execució de codi |
| Capacitat | Què faig si els processos demanen més RAM de la que hi ha? | El sistema es queda sense memòria |
Fixa't en un detall que condiciona tota la resta: la protecció s'ha de comprovar a cada accés a memòria. No una vegada en arrencar el procés, sinó a cada mov que la CPU executa, milers de milions de vegades per segon. Això descarta de ple qualsevol solució basada en programari: si el sistema operatiu hagués de validar cada accés, un programa aniria mil vegades més lent.
Com ja vam veure a 01-06 amb les instruccions privilegiades, l'única solució possible és que ho faci el maquinari. I aquest maquinari és la MMU.
Adreces lògiques i adreces físiques
Quan compiles l'ingestor i mires on és la seva funció principal:
La CPU executarà instruccions que fan referència a l'adreça 0x401b40. Però aquesta no és la posició real als xips de RAM. És una adreça lògica (o virtual): un número que només té sentit dins de l'espai d'adreces d'aquest procés.
| Adreça lògica (virtual) | Adreça física | |
|---|---|---|
| Qui la genera | La CPU en executar el programa | La MMU després de traduir |
| Què veu | El programa, el compilador, el depurador | El bus de memòria, els xips de RAM |
| Rang | 0 a 2^48 a x86-64 (256 TB) | 0 a la RAM instal·lada (8 GB) |
| Única al sistema | No: cada procés té la seva | Sí |
És la que veus a /proc/pid/maps |
Sí | No |
Això explica la paradoxa del principi: ingestor i agregador poden tenir tots dos codi a 0x401b40 perquè cadascun viu al seu propi espai d'adreces. La MMU tradueix aquesta mateixa adreça lògica a marcs físics diferents segons quin procés s'estigui executant.
Ho pots comprovar:
$ sudo grep -m1 'r-xp' /proc/1842/maps 00401000-00489000 r-xp 00001000 fd:01 1573241 /opt/meteora/bin/ingestor $ sudo grep -m1 'r-xp' /proc/1877/maps 00401000-004a3000 r-xp 00001000 fd:01 1573242 /opt/meteora/bin/agregador
Els dos processos tenen el seu codi començant exactament a 0x401000. Cap dels dos no sap —ni necessita saber— on és realment a la RAM.
Les tres fases de vinculació d'adreces
La traducció de "la variable total" a "el byte físic número 3.221.225.472" es pot fixar en tres moments diferents, i l'elecció té conseqüències molt diferents:
| Fase | Quan es decideix | Flexibilitat | Necessita maquinari | Exemple |
|---|---|---|---|---|
| Compilació | En generar el codi | Nul·la: adreça absoluta fixa | No | .COM d'MS-DOS, microprogramari encastat |
| Càrrega | En carregar el programa a memòria | Mitjana: es tria el lloc una vegada | No | Sistemes antics amb reubicació estàtica |
| Execució | A cada accés a memòria | Total: el procés es pot moure | Sí (MMU) | Tots els SO moderns |
Vinculació en compilació. El compilador genera adreces absolutes: "carrega el que hi ha a la posició 2000". Si el programa no es carrega exactament al lloc previst, no funciona. És el que fan encara avui els microcontroladors de les estacions meteorològiques de Meteora: el microprogramari sap que la memòria comença a 0x20000000 perquè és una placa concreta i mai no canviarà.
Vinculació en càrrega. El compilador genera codi reubicable amb adreces relatives a l'inici del programa. El carregador hi suma l'adreça base real. És més flexible, però un cop carregat el procés no es pot moure, perquè les seves adreces ja han estat reescrites.
Vinculació en execució. El codi conserva adreces lògiques i la traducció es produeix a cada accés, en maquinari. Això permet moure un procés a la RAM mentre s'executa, i és la base de tot el que és modern: memòria virtual, copy-on-write, biblioteques compartides i ASLR.
De fet, ASLR (Address Space Layout Randomization) només és possible amb vinculació en execució. Comprova-ho:
$ cat /proc/self/maps | tail -3 7ffd2a1c3000-7ffd2a1e4000 rw-p 00000000 00:00 0 [stack] 7ffd2a1f7000-7ffd2a1fb000 r--p 00000000 00:00 0 [vvar] 7ffd2a1fb000-7ffd2a1fd000 r-xp 00000000 00:00 0 [vdso] $ cat /proc/self/maps | tail -3 7ffc8b442000-7ffc8b463000 rw-p 00000000 00:00 0 [stack] 7ffc8b47a000-7ffc8b47e000 r--p 00000000 00:00 0 [vvar] 7ffc8b47e000-7ffc8b480000 r-xp 00000000 00:00 0 [vdso]
Dues execucions del mateix cat amb la pila en adreces completament diferents. El sistema aleatoritza la disposició a cada arrencada perquè un atacant no pugui predir on hi haurà res. Hi tornarem al mòdul 5.
Reubicació amb registre base i límit
L'esquema de maquinari més simple que resol reubicació i protecció alhora fa servir dos registres:
- Registre base (o de reubicació): l'adreça física on comença el procés.
- Registre límit: la mida de l'espai d'adreces del procés.
A cada accés a memòria, el maquinari fa dues operacions:
si (adreça_lògica < límit)
adreça_física = base + adreça_lògica
si no
generar excepció de fallada d'adreçament → SIGSEGVExemple numèric amb l'ingestor:
Registre base: 0x0C000000 (201.326.592) Registre límit: 0x00800000 (8.388.608 = 8 MB) Accés a l'adreça lògica 0x401b40 (4.201.280): 4.201.280 < 8.388.608 → vàlid física = 201.326.592 + 4.201.280 = 205.527.872 = 0x0C401B40 Accés a l'adreça lògica 0x900000 (9.437.184): 9.437.184 > 8.388.608 → fora de límits → excepció → el nucli envia SIGSEGV → "Violació de segment"
Aquí hi ha el mecanisme complet de la protecció de memòria, en dues línies de lògica. I observa el detall crucial que connecta amb 01-06: els registres base i límit només es poden modificar amb instruccions privilegiades. Si un procés pogués canviar el seu propi registre límit, la protecció no valdria res. Per això el nucli els carrega a cada canvi de context i el procés no els pot tocar.
Aquest esquema és elegant i rapidíssim (una comparació i una suma, uns pocs cicles), però té tres limitacions fatals:
- Tot el procés ha de ser a la RAM i en un bloc contigu. Si necessita 8 MB, cal un forat de 8 MB seguits.
- No permet permisos diferenciats. Tot l'espai té el mateix tractament: no pots marcar el codi com a no escrivible.
- No permet compartir. Dos processos que executen el mateix binari necessiten dues còpies completes.
La MMU: el traductor al camí crític
La MMU (Memory Management Unit) és el circuit que fa aquesta traducció. Als processadors moderns està integrada al nucli mateix de la CPU, al costat de les memòries cau, i no és un detall d'implementació: és una de les peces més crítiques del rendiment del sistema.
flowchart LR
CPU["CPU<br/>adreça lògica<br/>0x401b40"] --> MMU
MMU{"MMU<br/>és vàlida?<br/>permisos?"}
MMU -->|sí| RAM["RAM<br/>adreça física<br/>0x0C401B40"]
MMU -->|no| TRAP["Excepció<br/>→ nucli → SIGSEGV"]
SO["Sistema operatiu"] -.->|carrega base i límit<br/>a cada canvi de context| MMU
Tres característiques de la MMU que convé fixar des d'ara:
- Actua a cada accés. Cada
mov, cada instrucció llegida, cadapusha la pila. Si la traducció costés 10 ns, el sistema seria inutilitzable. Per això les MMU incorporen una memòria cau de traduccions (la TLB) que veurem a la lliçó vinent. - És maquinari i no es pot eludir. Un programa en mode usuari no té cap manera d'accedir a una adreça física directament. Cap.
- La configura el sistema operatiu. El nucli decideix quines traduccions són vàlides; la MMU les aplica. És el mateix repartiment de papers que vam veure a 01-06: la política la posa el programari, el mecanisme l'imposa el maquinari.
Assignació contigua: particions fixes i variables
Amb base i límit, cada procés ocupa un bloc contigu. Com es reparteix la memòria entre ells?
Particions fixes
Es divideix la memòria en un nombre fix de particions de mida predeterminada en arrencar el sistema.
┌──────────────────┐ 0 MB │ Nucli │ ├──────────────────┤ 512 MB │ Partició 1 │ 1 GB ├──────────────────┤ 1,5 GB │ Partició 2 │ 1 GB ├──────────────────┤ 2,5 GB │ Partició 3 │ 2 GB ├──────────────────┤ 4,5 GB │ Partició 4 │ 3,5 GB └──────────────────┘ 8 GB
Simple d'implementar (n'hi ha prou amb una taula de 4 entrades), però rígid:
- Si
meteo-apinecessita 300 MB i li dones la partició 1 d'1 GB, es malbaraten 700 MB dins de la partició. Ningú més no els pot fer servir. - Si l'
agregadornecessita 4 GB, no cap en cap, encara que sumant els forats lliures sobri espai. - El grau de multiprogramació està limitat al nombre de particions: quatre processos com a màxim.
És l'esquema de l'IBM OS/MFT dels anys 60, que vam veure en parlar de multiprogramació a 01-02.
Particions variables
El sistema manté una llista de forats lliures i assigna a cada procés exactament el que demana. En acabar, el seu espai torna a la llista i es fusiona amb els forats adjacents.
Simulem una seqüència real en una memòria de 2.560 MB amb el nucli ocupant 400 MB:
Estat inicial: [Nucli 400][═══════════ lliure 2160 ═══════════] Arriba ingestor (600 MB): [Nucli 400][ingestor 600][═════ lliure 1560 ═════] Arriba agregador (1000 MB): [Nucli 400][ingestor 600][agregador 1000][ lliure 560 ] Arriba meteo-api (300 MB): [Nucli 400][ingestor 600][agregador 1000][api 300][lliure 260] Acaba l'agregador: [Nucli 400][ingestor 600][═ lliure 1000 ═][api 300][lliure 260] Arriba backup (500 MB): [Nucli 400][ingestor 600][backup 500][lliure 500][api 300][lliure 260] Acaba l'ingestor: [Nucli 400][═ lliure 600 ═][backup 500][lliure 500][api 300][lliure 260]
Situació final: hi ha 1.360 MB lliures en total, però repartits en tres forats de 600, 500 i 260 MB. Si ara arriba un procés que necessita 900 MB, no hi cap, encara que n'hi hagi de sobres. Aquest és el problema al qual arribarem a l'apartat de fragmentació.
Estratègies d'assignació: primer, millor i pitjor ajust
Quan hi ha diversos forats on cap un procés, cal triar. Tres estratègies clàssiques:
| Estratègia | Regla | Avantatge | Inconvenient |
|---|---|---|---|
| Primer ajust | El primer forat on hi càpiga | La més ràpida | Fragmenta el començament de la memòria |
| Millor ajust | El forat més petit on hi càpiga | Aprofita bé l'espai | Recorre tota la llista; deixa engrunes inútils |
| Pitjor ajust | El forat més gran | Deixa restes utilitzables | Destrueix els forats grans |
Comparem-les amb el mateix cas. Forats lliures, en aquest ordre:
Peticions successives: P1 = 212 MB, P2 = 417 MB, P3 = 112 MB, P4 = 426 MB.
Primer ajust:
| Petició | Forat escollit | Per què | Resta |
|---|---|---|---|
| P1 = 212 | H2 (500) | H1 (200) és massa petit; H2 és el primer que serveix | H2 → 288 |
| P2 = 417 | H4 (600) | H2 (288) i H3 (300) no hi arriben | H4 → 183 |
| P3 = 112 | H1 (200) | Primer forat on hi cap | H1 → 88 |
| P4 = 426 | cap | Queden 88, 288, 300, 183, 250 | falla |
Millor ajust:
| Petició | Forat escollit | Per què | Resta |
|---|---|---|---|
| P1 = 212 | H3 (300) | És el més petit on hi cap | H3 → 88 |
| P2 = 417 | H2 (500) | El més petit dels que serveixen (500 < 600) | H2 → 83 |
| P3 = 112 | H5 (250) | El més petit on hi cap | H5 → 138 |
| P4 = 426 | H4 (600) | Hi cap | H4 → 174 |
Les quatre peticions se satisfan.
Pitjor ajust:
| Petició | Forat escollit | Per què | Resta |
|---|---|---|---|
| P1 = 212 | H4 (600) | El més gran | H4 → 388 |
| P2 = 417 | H2 (500) | El més gran disponible | H2 → 83 |
| P3 = 112 | H4 (388) | El més gran disponible | H4 → 276 |
| P4 = 426 | cap | Queden 200, 83, 300, 276, 250 | falla |
Resum:
| Estratègia | Peticions satisfetes | Memòria lliure final | Forat més gran final |
|---|---|---|---|
| Primer ajust | 3 de 4 | 909 MB | 300 MB |
| Millor ajust | 4 de 4 | 483 MB | 174 MB |
| Pitjor ajust | 3 de 4 | 909 MB | 300 MB |
Compte a generalitzar a partir d'un sol cas. Aquí guanya el millor ajust, però els estudis de simulació clàssics conclouen que:
- Primer ajust i millor ajust són equivalents en aprofitament, i el primer ajust és notablement més ràpid perquè no recorre tota la llista.
- El pitjor ajust és pitjor que tots dos en gairebé tots els escenaris: destrueix sistemàticament els forats grans, que són els més valuosos.
- El millor ajust té un defecte acumulatiu: deixa restes diminutes (83 MB, 88 MB) que mai no serviran per a res, i la llista de forats creix indefinidament. És fragmentació disfressada d'eficiència.
Per això el consens pràctic és el primer ajust, o la seva variant ajust següent (començar a buscar on va acabar la cerca anterior, en lloc de sempre des del principi), que reparteix millor el desgast.
Fragmentació interna i externa, amb números
La fragmentació és memòria que existeix físicament però no es pot fer servir. N'hi ha de dos tipus, i confondre'ls és un dels errors més freqüents:
| Fragmentació interna | Fragmentació externa | |
|---|---|---|
| On és el malbaratament | Dins del bloc assignat | Entre blocs assignats |
| Causa | El bloc assignat és més gran que el demanat | Els forats lliures estan dispersos |
| A qui pertany | Al procés (però no la fa servir) | A ningú |
| Apareix a | Particions fixes, paginació | Particions variables, segmentació |
| Es resol amb | Blocs més petits | Compactació o paginació |
Càlcul de fragmentació interna. Suposa particions fixes de 512 MB:
| Procés | Necessita | Partició | Malbaratament intern |
|---|---|---|---|
ingestor |
180 MB | 512 MB | 332 MB |
agregador |
490 MB | 512 MB | 22 MB |
meteo-api |
300 MB | 512 MB | 212 MB |
backup |
60 MB | 512 MB | 452 MB |
Gairebé la meitat de la memòria assignada no serveix per a res. I és un malbaratament invisible: el sistema informa de 2.048 MB en ús i és tècnicament cert.
Càlcul de fragmentació externa. Tornem a l'estat final de la simulació de particions variables:
Memòria lliure total: 600 + 500 + 260 = 1.360 MB Bloc contigu més gran: 600 MB Petició de 900 MB: FALLA tot i haver-hi 1.360 MB lliures Fragmentació externa: 1.360 − 600 = 760 MB inutilitzables
La regla del 50 % quantifica com de dolent arriba a ser això: en un sistema amb particions variables i primer ajust, per cada N blocs assignats hi ha estadísticament 0,5·N blocs lliures perduts per fragmentació, cosa que significa que fins a un terç de la memòria pot quedar inutilitzable.
Un matís important per a més endavant: la paginació elimina totalment la fragmentació externa (perquè tots els blocs fan el mateix, així que qualsevol forat serveix per a qualsevol pàgina) però introdueix fragmentació interna acotada. Amb pàgines de 4 KB:
Fragmentació interna mitjana per regió: 4.096 / 2 = 2.048 bytes Amb 180 processos i ~20 regions cadascun: 180 × 20 × 2.048 = 7,4 MB de 8 GB = 0,09 %
Canviar un terç de la memòria per un 0,09 % és un negoci excel·lent, i és la raó de fons per la qual tots els sistemes moderns paginen.
Compactació i per què gairebé mai no es fa servir
La solució òbvia a la fragmentació externa: moure els processos per ajuntar tots els forats en un de sol.
Abans: [Nucli 400][ lliure 600 ][backup 500][ lliure 500 ][api 300][ lliure 260 ] Després de compactar: [Nucli 400][backup 500][api 300][═══════ lliure 1360 ═══════]
Ara el procés de 900 MB sí que hi cap. La compactació només és possible amb vinculació en temps d'execució: si les adreces estiguessin fixades a la càrrega, moure un procés el trencaria. Amb base i límit n'hi ha prou de copiar els bytes i actualitzar el registre base.
Per què, doncs, no es fa servir? Pel cost:
Ample de banda de memòria típic: 20 GB/s Moure 800 MB (backup + api): 800 MB / 20 GB/s = 40 ms Durant aquests 40 ms: - Els processos moguts no es poden executar - L'ample de banda de memòria està saturat, així que TOT va més lent - 40 ms = 10 quantums sencers de 4 ms
I això s'hauria de repetir cada vegada que la fragmentació es torni a acumular, que en un sistema amb processos entrant i sortint és constantment. En un servidor amb 64 GB, compactar podria costar segons.
Conclusió: la compactació funciona, però el remei és pitjor que la malaltia. La solució real va ser canviar el plantejament: si el problema és exigir que un procés ocupi un bloc contigu, eliminem aquest requisit. Això és la paginació.
Segmentació
Abans d'arribar a la paginació, hi va haver un intent intermedi amb una motivació diferent: que la memòria reflecteixi com el programador veu el seu programa.
Un programa no és un bloc uniforme de bytes. És un conjunt de peces lògiques: el codi, les dades globals, la pila, el monticle, cada biblioteca. La segmentació dona a cadascuna el seu propi espai, amb la seva pròpia mida i els seus propis permisos.
Una adreça segmentada té dues parts:
I la traducció fa servir una taula de segments per procés, amb una base i un límit per cada segment:
| Segment | Nom | Base física | Límit | Permisos |
|---|---|---|---|---|
| 0 | Codi | 0x0C000000 | 552.960 (540 KB) | r-x |
| 1 | Dades globals | 0x0C100000 | 8.192 (8 KB) | rw- |
| 2 | Monticle | 0x0C200000 | 16.777.216 (16 MB) | rw- |
| 3 | Pila | 0x0D000000 | 8.388.608 (8 MB) | rw- |
| 4 | libc (compartit) | 0x08000000 | 2.097.152 (2 MB) | r-x |
Traducció de <2, 0x1000> (segment 2, desplaçament 4096):
Traducció de <1, 0x3000> (desplaçament 12.288 al segment de dades de 8 KB):
Els avantatges de la segmentació sobre base i límit simples són reals:
- Protecció per segment. El segment de codi és
r-x: un intent d'escriure-hi falla. Això és exactament el que impedeix que un desbordament de memòria intermèdia sobreescrigui instruccions. - Compartició natural. El segment 4 (libc) pot apuntar a la mateixa base física en 180 processos. Una sola còpia de la biblioteca a la RAM.
- Creixement independent. El monticle pot créixer sense haver de moure la pila.
- Coincideix amb l'estructura del programa, cosa que facilita la feina a l'enllaçador i al depurador.
Però conserva el pecat original: cada segment continua sent contigu en memòria física, així que la fragmentació externa persisteix. Només es redueix, perquè els segments són més petits que un procés sencer.
A x86-64 la segmentació existeix però està essencialment desactivada: els segments de codi i dades abasten tot l'espai d'adreces (model flat), i només sobreviuen els registres FS i GS, que es fan servir per a l'emmagatzematge local del fil i per a dades per CPU al nucli. La història va triar la paginació.
Intercanvi (swapping) clàssic
Última peça de l'esquema clàssic: què fer si els processos actius no hi caben tots a la RAM?
L'intercanvi (swapping) consisteix a treure un procés complet a disc i tornar-lo a portar quan li toqui executar-se. És la funció del planificador de mitjà termini que vam esmentar a 02-02.
sequenceDiagram
participant P as agregador (a la RAM)
participant K as Nucli
participant D as Disc (àrea d'intercanvi)
K->>K: falta memòria per a un procés nou
K->>K: tria una víctima: agregador (bloquejat, prioritat baixa)
K->>D: escriu els 1000 MB de l'agregador
Note over K,D: swap out
K->>K: la RAM queda lliure per al procés nou
Note over P,D: passa el temps
K->>K: l'agregador torna a ser executable
D->>K: llegeix els 1000 MB
Note over K,D: swap in
K->>P: l'agregador continua on era
El cost és brutal. Amb un disc mecànic a 100 MB/s:
Treure 1.000 MB: 1.000 / 100 = 10 segons Tornar-lo a portar: 10 segons Total per intercanvi complet: 20 segons
Vint segons durant els quals l'agregador no existeix. Amb un SSD NVMe a 3.000 MB/s baixaria a 0,67 segons, millor però encara desmesurat comparat amb els microsegons d'un canvi de context.
Per això l'intercanvi de processos complets és pràcticament mort. El que fan els sistemes moderns és intercanviar pàgines individuals de 4 KB, no processos sencers: si l'agregador té 1 GB però només fa servir activament 30 MB, s'expulsen les pàgines inactives i es queda la resta. Això ja és memòria virtual, i és el tema central de la lliçó vinent.
Tot i així, l'intercanvi clàssic continua viu en un cas concret: la hibernació. Quan suspens un portàtil a disc, el sistema escriu tota la RAM a l'àrea de swap. És exactament el mateix mecanisme, aplicat a la màquina sencera.
La idea de la paginació
Recapitulem el problema central. La fragmentació externa existeix perquè exigim que l'espai d'un procés sigui contigu en memòria física. La compactació intenta arreglar el símptoma. La paginació ataca la causa:
I si un procés no hagués d'estar contigu en memòria física?
La idea, en tres passos:
- Divideix la memòria física en trossos iguals i petits anomenats marcs (frames), típicament de 4 KB.
- Divideix l'espai lògic de cada procés en trossos de la mateixa mida anomenats pàgines.
- Col·loca cada pàgina en qualsevol marc lliure, sense importar l'ordre. Una taula de pàgines per procés registra quina pàgina és a quin marc.
Espai lògic de l'ingestor Memòria física ┌──────────┐ pàgina 0 ┌──────────┐ marc 0 ← pàgina 2 │ │ ├──────────┤ marc 1 ← (un altre procés) ├──────────┤ pàgina 1 ├──────────┤ marc 2 ← pàgina 0 │ │ ├──────────┤ marc 3 ← (lliure) ├──────────┤ pàgina 2 ├──────────┤ marc 4 ← pàgina 3 │ │ ├──────────┤ marc 5 ← pàgina 1 ├──────────┤ pàgina 3 ├──────────┤ marc 6 ← (lliure) └──────────┘ └──────────┘
Les conseqüències són enormes:
| Problema | Com el resol la paginació |
|---|---|
| Fragmentació externa | Desapareix: tots els forats fan el mateix, qualsevol serveix |
| Fragmentació interna | Apareix, però acotada a mitja pàgina (2 KB) per regió |
| Compactació | Innecessària: mai no cal moure res |
| Compartició | Trivial: dues taules de pàgines apunten al mateix marc |
| Processos més grans que la RAM | Possible: n'hi ha prou de no tenir totes les pàgines carregades |
| Cost | Una taula de pàgines per procés i una traducció per accés |
Aquest últim punt és el que cal resoldre bé, i no és trivial: si cada accés a memòria requereix consultar la taula de pàgines, que també és a memòria, cada accés costaria el doble. La solució és la TLB, i tot el mecanisme —taules multinivell, bits d'estat, fallades de pàgina, algorismes de reemplaçament— és el que desenvoluparem en detall a Memòria Virtual i Paginació.
De moment queda't amb la idea central: la paginació funciona perquè uniformitza la mida dels blocs. Quan totes les peces són iguals, el problema d'encaixar-les desapareix.
El mapa de memòria real d'un procés de Meteora
Tot l'anterior deixa de ser teoria tan bon punt llegeixes /proc/<pid>/maps. Vegem el de l'ingestor:
$ sudo cat /proc/1842/maps 00400000-00401000 r--p 00000000 fd:01 1573241 /opt/meteora/bin/ingestor 00401000-00489000 r-xp 00001000 fd:01 1573241 /opt/meteora/bin/ingestor 00489000-004a2000 r--p 00089000 fd:01 1573241 /opt/meteora/bin/ingestor 004a2000-004a4000 rw-p 000a1000 fd:01 1573241 /opt/meteora/bin/ingestor 004a4000-004c8000 rw-p 00000000 00:00 0 [heap] 7f2a1c000000-7f2a1c021000 rw-p 00000000 00:00 0 7f2a24a1e000-7f2a24a46000 r--p 00000000 fd:01 2229817 /usr/lib/x86_64-linux-gnu/libc.so.6 7f2a24a46000-7f2a24bce000 r-xp 00028000 fd:01 2229817 /usr/lib/x86_64-linux-gnu/libc.so.6 7f2a24bce000-7f2a24c23000 r--p 001b0000 fd:01 2229817 /usr/lib/x86_64-linux-gnu/libc.so.6 7f2a24c23000-7f2a24c27000 rw-p 00204000 fd:01 2229817 /usr/lib/x86_64-linux-gnu/libc.so.6 7f2a24c27000-7f2a24c34000 rw-p 00000000 00:00 0 7ffd8b3a1000-7ffd8b3c2000 rw-p 00000000 00:00 0 [stack] 7ffd8b3f5000-7ffd8b3f9000 r--p 00000000 00:00 0 [vvar] 7ffd8b3f9000-7ffd8b3fb000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
El format de cada línia, camp a camp:
00401000-00489000 r-xp 00001000 fd:01 1573241 /opt/meteora/bin/ingestor └──── rang ─────┘ └perm┘ └desplaç.┘ └disp.┘ └ inode ┘ └────── fitxer ───────┘
- Rang: adreces lògiques d'inici i final (el final no s'hi inclou).
- Permisos:
rlectura,wescriptura,xexecució, ipprivada (copy-on-write) oscompartida. - Desplaçament: en quin punt del fitxer comença aquest mapatge.
- Dispositiu i inode: quin fitxer s'ha mapejat (
00:00i0si no és un fitxer).
Ara la interpretació regió per regió, que és on hi ha tot l'aprenentatge:
| Rang | Mida | Permisos | Què és | Per què aquests permisos |
|---|---|---|---|---|
00400000-00401000 |
4 KB | r--p |
Capçaleres ELF | Només lectura: són metadades |
00401000-00489000 |
544 KB | r-xp |
.text, el codi |
Executable però no escrivible |
00489000-004a2000 |
100 KB | r--p |
.rodata, constants i cadenes |
Només lectura: no canvien mai |
004a2000-004a4000 |
8 KB | rw-p |
.data + .bss |
Escrivible però no executable |
004a4000-004c8000 |
144 KB | rw-p |
[heap], el monticle |
Creix amb malloc |
7f2a1c000000-... |
132 KB | rw-p |
Arena de malloc via mmap |
Blocs grans o d'un altre fil |
7f2a24a1e000-... |
4 regions | diversos | libc, amb la mateixa divisió | Compartida entre tots els processos |
7ffd8b3a1000-... |
132 KB | rw-p |
[stack], la pila |
Creix cap avall |
[vvar] / [vdso] |
24 KB | r--p/r-xp |
Codi del nucli en usuari | Accelera gettimeofday() sense syscall |
Cinc observacions que mereixen atenció:
-
Cap regió no és alhora escrivible i executable. Aquesta separació estricta s'anomena W^X (write xor execute) i és una defensa fonamental: encara que un atacant aconsegueixi injectar codi al monticle o a la pila, no el podrà executar perquè aquestes regions no tenen el bit
x. És una aplicació directa de la protecció per regió que vam veure a segmentació, implementada aquí sobre pàgines. Tornarà al mòdul 5. -
El binari ocupa quatre regions, no una. El carregador ELF separa les seccions segons els seus permisos, exactament pel que hem dit.
-
libc apareix amb la mateixa estructura de quatre regions, i les seves parts
r-xpestan compartides físicament amb els altres 179 processos del sistema. Una sola còpia del codi de libc a la RAM serveix per a tots. Aquí hi ha la compartició que la segmentació prometia, aconseguida amb paginació. -
[vdso]és un truc brillant. El nucli mapeja un petit tros del seu propi codi a l'espai de cada procés, perquè crides molt freqüents comgettimeofday()oclock_gettime()es resolguin sense creuar al mode nucli. Recorda el cost delsyscallque vam calcular a 01-06: entre 50 i 500 ns. El vDSO el redueix a uns pocs nanosegons, i per això existeix. -
La pila és a
0x7ffd...i el codi a0x0040..., amb un abisme entre tots dos. Aquest forat enorme no costa res: són adreces lògiques sense traducció assignada, i no consumeixen ni un byte de RAM. Reservar espai d'adreces és de franc; només costa la memòria física efectivament suportada.
pmap presenta el mateix amb les mides ja calculades, cosa que és més còmoda en el dia a dia:
$ sudo pmap -x 1842 1842: /opt/meteora/bin/ingestor --puerto 9010 Address Kbytes RSS Dirty Mode Mapping 0000000000400000 4 4 0 r---- ingestor 0000000000401000 544 412 0 r-x-- ingestor 0000000000489000 100 64 0 r---- ingestor 00000000004a2000 8 8 8 rw--- ingestor 00000000004a4000 144 144 144 rw--- [ anon ] 00007f2a24a1e000 160 160 0 r---- libc.so.6 00007f2a24a46000 1568 692 0 r-x-- libc.so.6 00007ffd8b3a1000 132 24 24 rw--- [ stack ] ---------------- ------ ------ ------ total kB 18204 13108 892
Les tres columnes numèriques diuen coses molt diferents i confondre-les porta a diagnòstics equivocats:
Kbytes: espai d'adreces reservat. És la suma que dona elVSZdeps.RSS: quant d'això és realment a la RAM. Fixa't enlibc.so.6: reserva 1.568 KB de codi però només 692 KB estan carregats. La resta són funcions de libc que aquest procés no ha cridat mai, i que per tant mai no s'han portat del disc.Dirty: pàgines modificades, que no es poden descartar sense escriure-les abans en algun lloc. El codi mai no està brut (mai no es modifica), així que es pot expulsar sense cost: si torna a caldre, es torna a llegir del binari.
Aquesta distinció entre pàgina neta i bruta és la que governa què s'expulsa primer quan falta memòria, i és una de les claus de la lliçó vinent.
Errors Habituals i Consells
Confondre fragmentació interna amb externa. Regla mnemotècnica: la interna és dins del que t'han donat (et sobra lloc al teu bloc); l'externa és fora, entre blocs aliens (hi ha lloc però no en un sol tros). Particions fixes i paginació produeixen interna; particions variables i segmentació, externa.
Creure que VSZ és la memòria que fa servir un procés. No ho és. Un procés pot reservar 100 GB d'espai d'adreces en una màquina de 8 GB sense problema, perquè les adreces són de franc. La memòria real és RSS, i encara així amb matisos: les pàgines compartides de libc es compten al RSS de tots els processos que la fan servir, així que sumar els RSS dona un total molt superior a la RAM instal·lada.
Pensar que el millor ajust és el millor. El nom enganya. Deixa engrunes inservibles i obliga a recórrer tota la llista de forats. El primer ajust és igual de bo en aprofitament i força més ràpid.
Assumir que segmentació i paginació són alternatives excloents. Històricament es van combinar: x86 de 32 bits feia segmentació i paginació (l'adreça passava per la taula de segments i el resultat per la de pàgines). A x86-64 la segmentació va quedar reduïda a un model pla, però no va desaparèixer del tot: FS i GS continuen fent-se servir.
Interpretar malament una "violació de segment". El missatge és històric i confon: avui gairebé sempre significa que s'ha accedit a una adreça sense traducció vàlida a la taula de pàgines, no a un segment fora de límits. Per diagnosticar-la, compara l'adreça que ha fallat amb les regions de /proc/<pid>/maps: si cau en un forat, és un punter corrupte; si cau en una regió r--p i era una escriptura, és un intent d'escriure en memòria de només lectura (típicament, modificar una cadena literal).
Consell pràctic: quan un procés "consumeix molta memòria", l'ordre correcte d'investigació és pmap -x <pid> per veure quina regió creix. Si creix [heap], és malloc sense free. Si creixen regions [anon] soltes, són blocs grans mapejats amb mmap. Si creix [stack], hi ha recursió descontrolada. Cada cas té una causa i un arranjament diferents, i el mapa t'ho diu sense tocar el codi.
Exercicis
Exercici 1: simular estratègies d'assignació
meteo-01 té els forats lliures següents, en aquest ordre de memòria:
Arriben aquestes peticions, en ordre: A = 230 MB, B = 140 MB, C = 310 MB, D = 190 MB.
- Resol l'assignació amb primer ajust, millor ajust i pitjor ajust.
- Per a cada estratègia, indica quantes peticions se satisfan i quina és la fragmentació externa resultant.
- Quina hauria funcionat millor? Pots concloure d'aquí quina és superior en general?
Exercici 2: calcular la fragmentació en els dos esquemes
Els quatre processos de Meteora necessiten: ingestor 180 MB, agregador 490 MB, meteo-api 300 MB, backup 60 MB.
- Calcula la fragmentació interna total amb particions fixes de 512 MB.
- Calcula la fragmentació interna total amb paginació de 4 KB, suposant que cada procés té 15 regions de memòria.
- Compara tots dos resultats en valor absolut i percentatge.
- Explica per què la paginació no produeix fragmentació externa.
Exercici 3: interpretar un mapa de memòria
Aquest és el mapa resumit de meteo-api després de 22 hores en marxa:
$ sudo pmap -x 1901 | tail -12 Address Kbytes RSS Dirty Mode Mapping 0000000000400000 820 680 0 r-x-- meteo-api 00000000006c9000 16 16 16 rw--- meteo-api 0000000001a40000 118784 118784 118784 rw--- [ anon ] 00007f8c14000000 1024 12 0 r-x-- libssl.so.3 00007f8c18a00000 8192 512 512 rw-s- /dev/shm/meteora-cache 00007ffe3c21a000 132 36 36 rw--- [ stack ] ---------------- ------ ------ ------ total kB 148320 135140 119348
- Quina regió domina el consum i què representa probablement?
- La regió de
libssl.so.3reserva 1.024 KB però només té 12 KB a la RAM. És un problema? Per què passa? - El mode de
/dev/shm/meteora-cacheésrw-s-. Què significa lasi què implica? - Si el sistema necessités alliberar memòria urgentment, quines pàgines d'aquest procés podria expulsar sense escriure res al disc? Calcula quants KB són.
- Amb aquestes dades, diries que
meteo-apité una fuita de memòria? Justifica quina comprovació faries.
Solucions
Solució 1
Primer ajust (el primer forat on hi càpiga, recorrent des d'H1):
| Petició | Forats disponibles | Escollit | Resta |
|---|---|---|---|
| A = 230 | 150, 400, 250, 320, 180 | H2 (400) | H2 → 170 |
| B = 140 | 150, 170, 250, 320, 180 | H1 (150) | H1 → 10 |
| C = 310 | 10, 170, 250, 320, 180 | H4 (320) | H4 → 10 |
| D = 190 | 10, 170, 250, 10, 180 | H3 (250) | H3 → 60 |
4 de 4 satisfetes. Forats finals: 10, 170, 10, 60, 180 = 430 MB lliures, bloc més gran 180 MB.
Millor ajust (el forat més petit on hi càpiga):
| Petició | Forats disponibles | Escollit | Per què | Resta |
|---|---|---|---|---|
| A = 230 | 150, 400, 250, 320, 180 | H3 (250) | El més petit dels que serveixen (250 < 320 < 400) | H3 → 20 |
| B = 140 | 150, 400, 20, 320, 180 | H1 (150) | 150 és el menor on hi cap | H1 → 10 |
| C = 310 | 10, 400, 20, 320, 180 | H4 (320) | 320 < 400 | H4 → 10 |
| D = 190 | 10, 400, 20, 10, 180 | H2 (400) | L'únic on hi cap | H2 → 210 |
4 de 4 satisfetes. Forats finals: 10, 210, 20, 10, 180 = 430 MB lliures, bloc més gran 210 MB.
Pitjor ajust (el forat més gran):
| Petició | Forats disponibles | Escollit | Resta |
|---|---|---|---|
| A = 230 | 150, 400, 250, 320, 180 | H2 (400) | H2 → 170 |
| B = 140 | 150, 170, 250, 320, 180 | H4 (320) | H4 → 180 |
| C = 310 | 150, 170, 250, 180, 180 | cap | falla |
| D = 190 | 150, 170, 250, 180, 180 | H3 (250) | H3 → 60 |
3 de 4 satisfetes. C queda sense servir tot i haver-hi 930 MB lliures abans d'intentar-ho.
Resum:
| Estratègia | Satisfetes | Lliure total | Bloc més gran | Fragmentació externa |
|---|---|---|---|---|
| Primer ajust | 4/4 | 430 MB | 180 MB | 250 MB |
| Millor ajust | 4/4 | 430 MB | 210 MB | 220 MB |
| Pitjor ajust | 3/4 | 620 MB | 250 MB | 370 MB |
3. Anàlisi. Primer i millor ajust empaten en peticions servides; el millor ajust deixa el bloc contigu més gran una mica més gran (210 davant de 180), cosa que li dona un avantatge marginal per a una petició futura.
El pitjor ajust falla, i la seva fallada és instructiva: en destinar el forat de 400 MB a una petició de 230 MB, va destruir l'únic forat on després hi hauria cabut C (310 MB). Aquesta és la crítica de fons al pitjor ajust: consumeix sistemàticament el recurs més escàs, que són els forats grans.
Es pot concloure quina és superior en general? No. Aquest exercici mostra una seqüència concreta. Canviant l'ordre de les peticions el resultat s'inverteix: si C arribés primer, el pitjor ajust la serviria sense problema. Les conclusions vàlides vénen de simulacions amb milers de seqüències aleatòries, i aquestes diuen que primer i millor ajust són estadísticament equivalents en aprofitament, que el primer ajust és més ràpid —no recorre tota la llista— i que el pitjor ajust és sistemàticament inferior. El primer ajust guanya per velocitat, no per aprofitament.
Solució 2
1. Particions fixes de 512 MB.
| Procés | Necessita | Particions | Assignat | Malbaratament intern |
|---|---|---|---|---|
ingestor |
180 MB | 1 | 512 MB | 332 MB |
agregador |
490 MB | 1 | 512 MB | 22 MB |
meteo-api |
300 MB | 1 | 512 MB | 212 MB |
backup |
60 MB | 1 | 512 MB | 452 MB |
| Total | 1.030 MB | 4 | 2.048 MB | 1.018 MB |
Gairebé la meitat. I observa com de desigual és el repartiment: l'agregador (490 MB) malbarata només 22 MB, mentre que backup (60 MB) en malbarata 452. La fragmentació interna amb particions fixes castiga desproporcionadament els processos petits, que són majoria en qualsevol sistema real.
2. Paginació de 4 KB amb 15 regions per procés.
La clau del càlcul: dins d'una regió, totes les pàgines són plenes tret de l'última, que de mitjana s'omple fins a la meitat.
Malbaratament mitjà per regió = 4.096 / 2 = 2.048 bytes Regions totals = 4 processos × 15 regions = 60 Fragmentació interna = 60 × 2.048 bytes = 122.880 bytes = 120 KB
3. Comparació.
| Esquema | Fragmentació interna | Percentatge | Factor |
|---|---|---|---|
| Particions fixes 512 MB | 1.018 MB | 49,7 % | — |
| Paginació 4 KB | 0,12 MB | 0,011 % | 8.483 vegades menys |
La diferència no és de grau, és de naturalesa. I la raó és purament aritmètica: el malbaratament per fragmentació interna és proporcional a la mida del bloc d'assignació. Amb blocs de 512 MB malbarates fins a 512 MB per procés; amb blocs de 4 KB malbarates com a molt 4 KB per regió. Reduir el bloc en un factor de 131.072 redueix el malbaratament en la mateixa proporció.
Això també explica per què les pàgines grans (2 MB) no són de franc: milloren la TLB però multipliquen per 512 la fragmentació interna. Ho veurem a la lliçó vinent.
4. Per què la paginació no produeix fragmentació externa.
La fragmentació externa apareix quan hi ha prou memòria lliure però no en un bloc contigu de la mida necessària. Requereix dues condicions simultànies:
- Que les peticions tinguin mides diferents.
- Que l'assignació hagi de ser contigua.
La paginació elimina totes dues:
- Totes les unitats fan exactament el mateix (4 KB), així que qualsevol marc lliure serveix per a qualsevol pàgina. No existeix el concepte de "no hi cap": si hi ha un marc lliure, la pàgina hi cap.
- No s'exigeix contigüitat: les pàgines 0, 1, 2 i 3 d'un procés poden ser als marcs 847, 12, 5.301 i 92. La taula de pàgines s'encarrega que el procés vegi un espai continu.
Formulat de manera exacta: amb paginació, si hi ha N marcs lliures, es pot satisfer qualsevol petició de fins a N pàgines, sense importar on siguin aquests marcs. Aquesta garantia és impossible amb assignació contigua, i és la raó per la qual també desapareix la necessitat de compactar.
Solució 3
1. La regió dominant.
118.784 KB = 116 MB, el 88 % del RSS total del procés (118.784 de 135.140). És memòria anònima —no suportada per cap fitxer—, escrivible, privada, i amb RSS = Kbytes = Dirty: és sencera a la RAM i sencera modificada.
Que comenci a 0x1a40000, just per damunt del binari, indica que és el monticle expandit amb brk (o una arena gran de malloc). Per la seva mida i pel fet de tractar-se de meteo-api, el més probable és una memòria cau de respostes o un conjunt de lectures carregades en memòria per servir consultes sense tocar /var/lib/meteora/lectures/.
Un contrast útil: 116 MB a 24 bytes per Lectura són uns 5,07 milions de lectures, prop de set dies de dades a 800 lectures/segon.
2. La regió de libssl.so.3 amb 12 KB d'1.024 KB.
No és cap problema: és exactament el que ha de passar.
Passa per la paginació per demanda: quan es mapeja una biblioteca, el nucli no llegeix el seu codi del disc. Només crea les entrades a la taula de pàgines marcades com a no presents. Una pàgina es porta del disc únicament quan el procés intenta executar-la i es produeix una fallada de pàgina.
meteo-api ha fet servir 12 KB (tres pàgines) d'OpenSSL. Tota la resta —algorismes de xifratge que no fa servir, funcions de gestió de certificats, codi de compatibilitat— no s'ha tocat mai i per tant mai no ha ocupat RAM.
L'estalvi agregat és enorme: si 20 processos mapegen libssl i cadascun fa servir unes poques pàgines diferents, el consum real és una fracció mínima dels 20 × 1.024 KB nominals. I com que el codi és de només lectura, les pàgines que sí que es carreguen es comparteixen entre tots.
3. El mode rw-s-.
La s significa compartida (shared), davant de la p de privada que porten totes les altres. La diferència és fonamental:
p (privada) |
s (compartida) |
|
|---|---|---|
| En escriure | Copy-on-write: es copia la pàgina | S'escriu a la pàgina comuna |
| Qui veu els canvis | Només aquest procés | Tots els que la mapegen |
| Es propaga al fitxer | No | Sí |
Com que és sobre /dev/shm/ (un sistema de fitxers a la RAM), es tracta de memòria compartida entre processos: probablement els quatre treballadors de meteo-api que vèiem al pstree de 02-01 comparteixen una única memòria cau de 8 MB en lloc de tenir-ne quatre còpies.
Implicació important: escriure-hi des de diversos processos alhora exigeix sincronització. Sense ella, dos treballadors actualitzant la mateixa entrada la corromprien. Aquest és precisament el terreny del mòdul 3.
4. Pàgines expulsables sense escriure al disc.
Són les pàgines netes (Dirty = 0) suportades per un fitxer: si tornen a caldre, es tornen a llegir del binari original, així que descartar-les és de franc.
| Regió | RSS | Dirty | És neta i suportada? | Expulsable sense cost |
|---|---|---|---|---|
meteo-api r-x |
680 | 0 | Sí | 680 KB |
meteo-api rw |
16 | 16 | No, bruta | 0 |
[anon] |
118.784 | 118.784 | No, anònima i bruta | 0 |
libssl.so.3 r-x |
12 | 0 | Sí | 12 KB |
/dev/shm/... rw-s |
512 | 512 | Bruta (i a la RAM, tmpfs) | 0 |
[stack] |
36 | 36 | No, anònima i bruta | 0 |
Només 692 KB de 135 MB. I aquí hi ha la lliçó incòmoda: el 99,5 % de la memòria d'aquest procés és anònima i bruta, així que alliberar-la exigeix escriure-la a l'àrea de swap, amb el cost de disc que això implica. Si meteo-01 no tingués swap configurat, aquesta memòria seria directament irrecuperable i el sistema hauria de recórrer a l'OOM killer.
Nota addicional sobre els 512 KB de /dev/shm: com que resideixen en tmpfs, no tenen suport al disc; només poden anar a swap, mai no es poden descartar.
5. Hi ha fuita de memòria?
Amb aquestes dades no es pot afirmar. Una foto no distingeix una memòria cau legítima d'una fuita: totes dues es veuen igual, com una regió anònima gran i bruta.
La comprovació correcta és mesurar l'evolució en el temps:
$ for i in $(seq 1 12); do
printf "%s RSS=%s kB\n" "$(date +%H:%M)" "$(awk '/VmRSS/{print $2}' /proc/1901/status)"
sleep 300
doneLa interpretació de la sèrie resultant:
| Patró observat | Diagnòstic |
|---|---|
| Puja i s'estabilitza en un sostre | Memòria cau amb límit. Correcte |
| Puja a graons i baixa en alliberar | Ús normal del monticle |
| Puja linealment sense parar | Fuita confirmada |
| Puja només sota càrrega i no baixa després | Fuita proporcional a les peticions |
Un càlcul que orienta des del primer moment: 116 MB després de 22 hores són uns 5,3 MB/hora. Si aquest ritme fos constant, en una setmana serien 890 MB i en un mes 3,8 GB, cosa que en una màquina de 8 GB acabaria a l'OOM killer. Que la resposta depengui de si el ritme es manté o s'aplana és justament el motiu pel qual cal mesurar dues vegades.
Comprovacions complementàries: pmap -x repetit per veure quina regió creix; sudo cat /proc/1901/status | grep VmPeak per conèixer el màxim històric; i al codi, revisar si la memòria cau té una política d'expulsió o creix sense límit, que és la causa més freqüent d'aquest perfil.
Conclusió
Gestionar la memòria significa resoldre alhora reubicació, protecció, compartició, organització lògica i capacitat, i amb una restricció duríssima: la protecció s'ha de comprovar a cada accés, cosa que obliga a resoldre-la en maquinari.
La peça clau és la separació entre adreces lògiques i físiques. Vincular-les en temps d'execució —i no en compilació ni en càrrega— és el que permet que la MMU tradueixi a cada accés, que un procés es pugui moure, que existeixi ASLR i que dos processos facin servir la mateixa adreça 0x401000 sense trepitjar-se. L'esquema mínim que ho aconsegueix són els registres base i límit: una comparació i una suma, protecció completa, i impossible d'eludir perquè només es poden carregar des del mode nucli.
L'assignació contigua —particions fixes o variables, amb primer, millor o pitjor ajust— funciona però xoca contra la fragmentació: interna (fins al 49,7 % de l'espai assignat en el nostre càlcul amb particions de 512 MB) o externa (fins a un terç de la memòria, per la regla del 50 %). La compactació la resol, però costaria 40 ms cada vegada, així que no és una solució. La segmentació va aportar el millor de l'assignació contigua —permisos per regió i compartició— sense curar la fragmentació externa, i l'intercanvi de processos complets va morir tan bon punt es van fer els números: 20 segons per un agregador d'1 GB.
La sortida no va ser millorar l'assignació contigua, sinó abandonar el requisit de contigüitat. Si totes les peces fan el mateix, encaixar-les deixa de ser un problema: això és la paginació. I tot això deixa de ser teoria en llegir /proc/<pid>/maps i pmap -x, on veus codi compartit i no escrivible, libc amb només el 44 % del seu codi realment carregat, W^X protegint pila i monticle, i la diferència entre Kbytes, RSS i Dirty que decideix què es pot expulsar i què no.
Queda la part gran: com funciona realment la paginació. Com es tradueix una adreça amb número de pàgina i desplaçament, per què una taula plana de 64 bits seria impossible i com ho arreglen les taules multinivell, què és la TLB i quant rendiment en depèn, què passa exactament quan una pàgina no és a la RAM, quina pàgina expulsar quan no queda forat i per què un sistema pot entrar en hiperpaginació i deixar d'avançar. Tot això, i mmap() mapejant 2026-08-31.dat en memòria, és a Memòria Virtual i Paginació.
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
