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

  1. El problema: memòria compartida sense invasions
  2. Adreces lògiques i adreces físiques
  3. Les tres fases de vinculació d'adreces
  4. Reubicació amb registre base i límit
  5. La MMU: el traductor al camí crític
  6. Assignació contigua: particions fixes i variables
  7. Estratègies d'assignació: primer, millor i pitjor ajust
  8. Fragmentació interna i externa, amb números
  9. Compactació i per què gairebé mai no es fa servir
  10. Segmentació
  11. Intercanvi (swapping) clàssic
  12. La idea de la paginació
  13. 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:

$ nm -C /opt/meteora/bin/ingestor | grep ' T main'
0000000000401b40 T main

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 la que veus a /proc/pid/maps 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 (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  →  SIGSEGV

Exemple 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:

  1. 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.
  2. No permet permisos diferenciats. Tot l'espai té el mateix tractament: no pots marcar el codi com a no escrivible.
  3. 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, cada push a 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-api necessita 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'agregador necessita 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:

H1: 200 MB    H2: 500 MB    H3: 300 MB    H4: 600 MB    H5: 250 MB

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
Total assignat:       2.048 MB
Total usat:           1.030 MB
Fragmentació interna: 1.018 MB = 49,7 % malbaratat

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:

[Nucli 400][ lliure 600 ][backup 500][ lliure 500 ][api 300][ lliure 260 ]
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:

adreça lògica = <número de segment, desplaçament>

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):

0x1000 = 4.096 < 16.777.216  →  vàlid
física = 0x0C200000 + 0x1000 = 0x0C201000

Traducció de <1, 0x3000> (desplaçament 12.288 al segment de dades de 8 KB):

12.288 > 8.192  →  fora de límits
→ excepció → SIGSEGV

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:

  1. Divideix la memòria física en trossos iguals i petits anomenats marcs (frames), típicament de 4 KB.
  2. Divideix l'espai lògic de cada procés en trossos de la mateixa mida anomenats pàgines.
  3. 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: r lectura, w escriptura, x execució, i p privada (copy-on-write) o s compartida.
  • Desplaçament: en quin punt del fitxer comença aquest mapatge.
  • Dispositiu i inode: quin fitxer s'ha mapejat (00:00 i 0 si 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ó:

  1. 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.

  2. El binari ocupa quatre regions, no una. El carregador ELF separa les seccions segons els seus permisos, exactament pel que hem dit.

  3. libc apareix amb la mateixa estructura de quatre regions, i les seves parts r-xp estan 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ó.

  4. [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 com gettimeofday() o clock_gettime() es resolguin sense creuar al mode nucli. Recorda el cost del syscall que vam calcular a 01-06: entre 50 i 500 ns. El vDSO el redueix a uns pocs nanosegons, i per això existeix.

  5. La pila és a 0x7ffd... i el codi a 0x0040..., 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 el VSZ de ps.
  • RSS: quant d'això és realment a la RAM. Fixa't en libc.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:

H1: 150 MB   H2: 400 MB   H3: 250 MB   H4: 320 MB   H5: 180 MB

Arriben aquestes peticions, en ordre: A = 230 MB, B = 140 MB, C = 310 MB, D = 190 MB.

  1. Resol l'assignació amb primer ajust, millor ajust i pitjor ajust.
  2. Per a cada estratègia, indica quantes peticions se satisfan i quina és la fragmentació externa resultant.
  3. 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.

  1. Calcula la fragmentació interna total amb particions fixes de 512 MB.
  2. Calcula la fragmentació interna total amb paginació de 4 KB, suposant que cada procés té 15 regions de memòria.
  3. Compara tots dos resultats en valor absolut i percentatge.
  4. 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
  1. Quina regió domina el consum i què representa probablement?
  2. La regió de libssl.so.3 reserva 1.024 KB però només té 12 KB a la RAM. És un problema? Per què passa?
  3. El mode de /dev/shm/meteora-cache és rw-s-. Què significa la s i què implica?
  4. 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.
  5. Amb aquestes dades, diries que meteo-api té 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
Fragmentació interna = 1.018 / 2.048 = 49,7 % de l'espai assignat

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
Fragmentació interna = 120 KB / 1.030 MB = 0,0114 %

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:

  1. Que les peticions tinguin mides diferents.
  2. 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.

0000000001a40000  118784  118784  118784 rw---   [ anon ]

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

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 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 12 KB
/dev/shm/... rw-s 512 512 Bruta (i a la RAM, tmpfs) 0
[stack] 36 36 No, anònima i bruta 0
Total expulsable sense escriptura = 680 + 12 = 692 KB

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
  done

La 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

Mòdul 2: Gestió de Recursos

Mòdul 3: Concurrència

Mòdul 4: Estructures de Fitxers

Mòdul 5: Protecció i Seguretat del Sistema

Mòdul 6: Virtualització i Contenidors

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

© Copyright 2026. Tots els drets reservats