Gestión de memoria — teoría con tu portátil repartiendo la RAM entre gcc, spotify, el campus y onedrive (y los 4 KiB que yo creía que eran 4000 bytes)

En el simulacro de IC2 me tocó una pregunta de paginación que parecía regalada: páginas de 4 KiB, la dirección lógica 6700 y una tabla en la que la página 1 estaba en el marco 9. La hice de cabeza: «4 KiB son 4000 bytes, así que 6700 es la página 1 con desplazamiento 2700», y escribí con toda la seguridad del mundo 9 · 4000 + 2700 = 38700. Cero puntos. Un KiB son 1024 bytes, así que una página de 4 KiB tiene 4096: 6700 = 1 · 4096 + 2604, y la física era 9 · 4096 + 2604 = 39468. Tenía bien la página y el marco, y aun así fallé por no saber de qué tamaño es una página.

Hoy vamos a entender cómo reparte el sistema operativo la RAM entre los procesos sin que se pisen: la memoria contigua (con sus registros base y límite y sus huecos), la segmentación y la paginación, la TLB que hace que todo esto no sea lentísimo y el problema de lo que ocupa la tabla de páginas. El ejemplo de siempre: tu portátil una noche de estudio, con gcc compilando, spotify sonando, el campus abierto en el navegador y onedrive subiendo tus apuntes. Venimos de Concurrencia y sincronización, donde los procesos compartían variables a propósito; aquí el SO se encarga de que no compartan memoria por accidente. Cuando acabes, practica con los ejercicios tipo examen de gestión de memoria.

Por qué hay que gestionar la memoria

La memoria principal (RAM) es un conjunto de celdas que se referencian por una dirección lineal: la primera es la 0, la siguiente la 1, y así hasta la última. Para que un programa se ejecute, su código y sus datos tienen que estar cargados en la RAM, al menos en parte. Y el núcleo del SO también vive ahí.

Con un solo programa sería fácil. Pero tu portátil es un sistema multitarea: la memoria hay que repartirla entre todos los procesos, y cada uno tiene su propio espacio de memoria (instrucciones, datos y pila).

La RAM repartida entre procesosLa RAM tiene el núcleo, gcc, spotify y el campus; onedrive no cabe. Dos problemas: falta de sitio y accesos indebidos.La RAM del portátil, repartida entre procesos0x0000núcleo del SOgccspotifycampus (navegador)libreFFFF…memoria física (RAM)onedrive quiere entrarcódigodatospilasu memoria lógicano cabe① ¿Y si no hay sitio para todos?② ¿Y si gcc escribe en la zona de spotify?
Cada proceso necesita su trozo de RAM, y el núcleo también: de ahí salen los dos problemas del tema.

Fíjate en el dibujo: gcc, spotify y el campus ya están dentro, y onedrive quiere entrar. De ahí salen los dos problemas de la gestión de memoria:

  1. Capacidad: puede que la RAM no tenga sitio para todos los procesos que quieren ejecutarse.
  2. Protección: un proceso podría acceder, por error o a propósito, a la zona de otro proceso o a la del SO.

Recuerda el cambio de contexto: cuando el SO pasa de gcc a spotify guarda el estado de gcc en su PCB y carga el de spotify. La memoria de gcc no se mueve: se queda en la RAM esperando su turno. Por eso los dos tienen que caber a la vez.

Requisitos, objetivos y conceptos clave

El SO tiene tres requisitos que cumplir:

RequisitoQué significa
Controlar el solapamientoQue los procesos no choquen en memoria, pero que puedan compartir zonas cuando quieran
ProtecciónQue un proceso no lea la memoria privada de otro ni los datos del SO
ReubicaciónConvertir las direcciones lógicas del proceso en direcciones físicas

Y dos bloques de objetivos, que en el examen se preguntan por separado:

  • Rendimiento: aprovechar al máximo la memoria principal, tener varios procesos cargados sin que el sistema se venga abajo y permitir que un programa se ejecute sin estar entero en la RAM.
  • Protección y compartición: proteger al SO de accesos indebidos, proteger el espacio de cada proceso y permitir que varios procesos compartan zonas de memoria (para comunicarse o para no repetir código, como una biblioteca).

Por último, tres conceptos clave que van a salir en todo el tema:

  • Reubicación: que un programa pueda residir en cualquier zona de la memoria física.
  • Fragmentación: los huecos libres que van quedando al asignar y liberar memoria, y que a veces no sirven para nada.
  • Tiempo de acceso: los mecanismos de gestión de memoria no pueden hacer mucho más lento cada acceso.

En las preguntas de objetivos, poner «aprovechar al máximo la memoria secundaria» o «compartir memoria secundaria». Los objetivos hablan siempre de la memoria principal. Es un distractor clásico.

De practica.c a la memoria: la vinculación de direcciones

Cuando escribes int cont; en practica.c, cont es una dirección simbólica: un nombre. En algún momento ese nombre tiene que convertirse en un número de celda de la RAM. ¿Cuándo? Depende de en qué etapa del ciclo de vida del programa se haga.

Ciclo de vida de un programapractica.c pasa por compilador, enlazador y cargador. La reubicación puede no existir (compilación), ser estática (carga) o dinámica (ejecución).De practica.c a la memoria: ¿cuándo se fijan las direcciones?practica.cfuentepractica.oobjetopracticaejecutableen la RAMprocesocompiladorenlazadorcargador+ otros .o y bibliotecas+ libc.so, libm.soCompilacióndirecciones absolutas → no reubicableCargalas fija el cargador → reubicación estáticaEjecuciónlas traduce la MMU en cada acceso → reubicación dinámica
Cuanto más tarde se fija la dirección definitiva, más libertad tiene el SO para mover el proceso.

El compilador produce el módulo objeto, el enlazador lo junta con otros objetos y bibliotecas en un ejecutable, y el cargador lo pone en memoria. En cualquiera de esas etapas se pueden fijar las direcciones:

Cuándo se fijanTipoConsecuencia
Al compilarDirecciones absolutasEl programa solo funciona en una zona fija de la RAM: no es reubicable
Al cargarReubicación estáticaEl cargador ajusta las direcciones una vez; después el proceso ya no se puede mover
Al ejecutarReubicación dinámicaEl hardware traduce en cada acceso; el proceso se puede mover cuando haga falta

Lo que interesa es que el compilador no genere direcciones definitivas, sino reubicables, que se resuelven más tarde. Cuanto más tarde, más libertad.

Direcciones lógicas, físicas y la MMU

La reubicación dinámica necesita dos tipos de dirección y un traductor:

  • Dirección lógica (o virtual): la que genera la CPU al ejecutar el programa.
  • Dirección física: la que llega al chip de memoria.
  • MMU (unidad de manejo de memoria): el dispositivo hardware que traduce direcciones lógicas a físicas.

La MMU más sencilla es un registro base: se suma a cada dirección lógica. Imagina que gcc está cargado a partir de los 300 KiB de la RAM (300 · 1024 = 307200):

Reubicación dinámica con registro baseLa CPU emite la dirección lógica 1500; la MMU le suma la base 307200 y sale la física 308700.La MMU suma el registro baseCPUlógica1500MMUregistro base+ 307200física308700RAMgcc cree que su memoria empieza en 0; en realidad está en 300 KiB = 307200.307200 + 1500 = 308700
La CPU nunca ve la dirección física: la suma la hace el hardware en cada acceso.

gcc cree que su memoria empieza en 0. Cuando pide la dirección 1500, la MMU calcula 307200 + 1500 = 308700, y esa es la que llega a la RAM. Si mañana el SO mueve gcc a otro sitio, solo tiene que cambiar el registro base: gcc ni se entera.

Protección con base y límite

Con el registro base ya tenemos reubicación. Para la protección se añade un registro límite: el tamaño de la zona que el proceso puede usar. En modo usuario, un circuito comprueba cada dirección antes de que salga al bus:

Protección con registros base y límiteLa dirección lógica se compara con el límite; si es menor se le suma la base, si no, salta una excepción.Base y límite: primero se comprueba, después se sumadir. lógicadd < 215040¿límite?sí+ 307200dir. físicanoexcepciónd = 15001500 < 215040→ 308700d = 250000250000 ≥ 215040→ excepcióngcc: base 307200 (300 KiB), límite 215040 (210 KiB): solo puede tocar [307200, 522240).
El acceso ilegal nunca llega al bus: se corta antes, en la comparación con el límite.

Si gcc tiene una zona de 210 KiB (215040 bytes), la dirección lógica 1500 pasa la comprobación (1500 < 215040) y se convierte en 308700. La 250000 no la pasa: salta una excepción y el acceso nunca llega a la memoria. Ya viste estos registros en el tema del hardware; aquí entiendes por qué existen.

El orden importa. Primero se compara la dirección lógica con el límite y después se suma la base. Si te dan el límite como dirección final (por ejemplo, la zona va de 307200 a 522240), entonces lo que se compara es la física. Lee bien qué dato te dan.

Enlace dinámico e intercambio

Dos trucos más para ahorrar memoria antes de entrar en las técnicas de asignación.

Enlace dinámico (bibliotecas compartidas). La carga de un módulo de código se retrasa hasta que se usa por primera vez. En Windows son las DLL (dynamic link libraries) y en Unix y GNU/Linux, las bibliotecas compartidas (libc.so, libm.so). La biblioteca se carga cuando algún proceso llama a una de sus funciones, y las llamadas pasan por una tabla indirecta que gestiona el SO. Si diez procesos usan libc, solo hay una copia en memoria. Y si la RAM va muy justa, el SO puede quitar las que nadie referencia.

Intercambio (swapping). Si un proceso lleva mucho tiempo bloqueado, su memoria está ocupada sin servir para nada. Idea: volcar su imagen al disco (swap out) y recuperarla cuando haya que reanudarlo (swap in).

Intercambio entre RAM y discoonedrive lleva rato bloqueado: su imagen se vuelca al disco (swap out) y se recupera cuando vuelve a hacer falta (swap in).Intercambio (swapping): la RAM le presta su sitio a otronúcleo del SOgcconedrivebloqueado hace ratospotifyRAMdisco (área de swap)swap outswap in (al despertar)
El precio del swapping es el tiempo de copiar la imagen entera al disco y de vuelta.

Históricamente fue la forma de tener multiprogramación con muy poca RAM (en el caso extremo, un solo proceso en memoria y un intercambio en cada cambio de contexto). Su problema es el tiempo de copiar imágenes enteras. Para suavizarlo, mientras se intercambia un proceso los demás siguen ejecutándose, y se pueden usar varias áreas de intercambio en discos distintos para hacer varias copias a la vez.

Memoria contigua: MFT y MVT

La forma más simple de repartir la RAM: cada proceso ocupa un único bloque seguido. Hay dos variantes:

MFT frente a MVTTres memorias: MFT con particiones iguales, MFT con particiones de distinto tamaño y MVT con espacio libre repartido dinámicamente.Memoria contigua: particiones fijas (MFT) o variables (MVT)MFT, todas igualesSOpartición 1partición 2partición 3partición 4MFT, de varios tamañosSOpartición 1partición 2partición 3partición 4MVTSOespacio libre que sereparte a medidarígida pero muy eficientese fija al arrancar el SOlista de huecos: flexible y compleja
En MFT las particiones existen antes que los procesos; en MVT se crean al tamaño de cada petición.
  • MFT (particiones de tamaño fijo): las particiones se deciden al arrancar el SO y no cambian. Técnica muy rígida, pero muy eficiente.
  • MVT (particiones de tamaño variable): evolución de MFT. El espacio libre se reparte a medida, con una lista dinámica de huecos. Más flexible, pero más compleja.

Con MVT, el SO maneja dos estructuras:

  • Una tabla de zonas reservadas: por cada zona, dirección inicial, tamaño y proceso al que pertenece.
  • Una lista de huecos libres: por cada hueco, dirección inicial y tamaño.

Cuando un proceso pide memoria, pide un tamaño concreto. El SO busca un hueco suficientemente grande, se lo da y lo que sobra vuelve a la lista de huecos. La protección sigue siendo la misma pareja base y límite.

Primer, mejor y peor ajuste

Cuando hay varios huecos que sirven, ¿cuál se da? Hay tres políticas de asignación:

  • Primer ajuste (first-fit): se recorre la lista y se da el primer hueco mayor o igual que lo pedido.
  • Mejor ajuste (best-fit): se da el hueco que deja el menor resto libre. Riesgo: va dejando restos tan pequeños que no sirven.
  • Peor ajuste (worst-fit): se da siempre el hueco más grande.

En el portátil hay cinco huecos libres de 120, 480, 210, 300 y 560 KiB, y llegan, en este orden, gcc (210), spotify (430), campus (110) y onedrive (470). El simulador de Compruébalo con Python aplica las tres políticas a los mismos datos:

Primer, mejor y peor ajusteCinco huecos de 120, 480, 210, 300 y 560 KiB. Primer y peor ajuste no consiguen sitio para onedrive (470 KiB); mejor ajuste los coloca a todos.Mismos huecos, tres políticasPeticiones en orden: gcc 210 · spotify 430 · campus 110 · onedrive 470 (KiB)al principio120480210300560gscprimer ajusteonedrive no cabe · huecos: 10 270 210 300 130gscomejor ajustetodos caben · huecos: 10 50 300 90gscpeor ajusteonedrive no cabe · huecos: 120 50 210 300 240gccspotifycampusonedriveotros
Son los datos del simulador: con mejor ajuste el hueco de 560 sigue entero cuando llega onedrive.

Sigue la fila del primer ajuste: gcc entra en el primer hueco que le cabe, el de 480 (le sobran 270). spotify (430) no cabe ni en 270 ni en 210 ni en 300, y va al de 560. campus, al de 120. Cuando llega onedrive (470), el mayor hueco que queda es de 300: no cabe. El peor ajuste fracasa igual, porque gcc rompe el hueco de 560. Solo el mejor ajuste coloca a los cuatro: gcc entra justo en el de 210 y deja intacto el de 560 para onedrive.

PolíticaBuscaVentajaInconveniente
Primer ajusteEl primero que cabeRápida: no recorre toda la listaVa troceando los huecos del principio
Mejor ajusteEl que deja menos restoConserva los huecos grandesRecorre toda la lista; deja restos diminutos
Peor ajusteEl más grandeDeja restos grandes (en teoría útiles)Rompe los huecos grandes muy pronto

Lo que se sabe de la práctica es que primer y mejor ajuste rinden parecido, y mejor que el peor ajuste. Los sistemas reales usan algoritmos más elaborados: Linux, por ejemplo, el sistema buddy (de parejas). No saques conclusiones de un solo ejemplo: con otros datos, el primer ajuste podría ganar.

Fragmentación externa, interna y compactación

Mira cómo queda la memoria tras el primer ajuste:

Fragmentación externa y compactaciónTras el primer ajuste quedan huecos de 10, 270, 210, 300 y 130 KiB (920 en total) y onedrive (470) no cabe; compactando cabría.Fragmentación externa: 920 KiB libres y onedrive no cabeTras el primer ajuste, el espacio libre está roto en trozos:10270210300130= 920 KiBonedrive necesita 470 seguidosel mayor trozo: 300 → no cabeCompactar: mover los procesos para juntar los huecosquedan 450= 920 KiBonedrive 470Solo es posible con reubicación dinámica, y cuesta tiempo: hay que copiar bloques enteros.
Hay memoria de sobra, pero no seguida: eso es la fragmentación externa.

Hay 920 KiB libres y onedrive solo pide 470, pero están repartidos en trozos de 10, 270, 210, 300 y 130. Ninguno sirve. Eso es la fragmentación externa: memoria libre que existe pero no está seguida.

Una solución es la compactación: mover los procesos para juntar todos los huecos en uno. Tiene dos pegas:

  • Solo es posible con reubicación dinámica, porque los procesos cambian de dirección física (basta con cambiar su registro base).
  • Consume mucho tiempo: hay que copiar bloques enteros de memoria.

La otra solución es dejar de exigir que el proceso esté seguido: trocearlo. Eso es la memoria no contigua, con sus dos técnicas, segmentación y paginación.

Confundir los dos tipos de fragmentación. Externa: el espacio libre está fuera de las zonas asignadas, roto en huecos pequeños (memoria contigua y segmentación). Interna: el espacio desperdiciado está dentro de una zona asignada que el proceso no llena (particiones fijas de MFT y la última página en paginación).

Segmentación

Idea: descomponer el proceso en varios segmentos, que son sus trozos lógicos: código, datos, pila… Con el hardware adecuado, cada segmento puede ir a una zona distinta de la RAM.

Segmentos de gcc en la RAMgcc tiene tres segmentos (código, datos y pila) con su base y su límite en la tabla de segmentos; en la RAM quedan separados y en otro orden.Segmentación: cada trozo lógico, en su sitiogcc, visto por el compilador0 · códigotam. 32001 · datostam. 15002 · pilatam. 2000tabla de segmentossegbaselím.01400032001400015002210002000código14000datos4000pila21000RAM (otros procesos en gris)
Los segmentos no tienen por qué estar seguidos ni en orden: cada uno tiene su propia base.

En el dibujo, gcc tiene tres segmentos: código (3200 bytes), datos (1500) y pila (2000). En la RAM están separados y en otro orden, y la tabla de segmentos guarda dónde empieza cada uno (base) y cuánto mide (límite). El reparto del trabajo:

  • La CPU trabaja con direcciones de dos piezas: ⟨segmento, desplazamiento⟩.
  • El compilador identifica los segmentos del programa y genera direcciones segmentadas.
  • El cargador del SO coloca cada segmento en una zona de memoria.
  • La MMU traduce ⟨segmento, desplazamiento⟩ a direcciones lineales con la tabla de segmentos, que es una tabla de registros base y límite.

Traducir una dirección segmentada

Traducción con segmentación(1, 600): segmento de datos, base 4000, límite 1500; 600 es menor que 1500 y la física es 4600. (2, 2500): 2500 no es menor que 2000 y salta una excepción.Traducir ⟨segmento, desplazamiento⟩(1, 600)tabla[1] = datosbase 4000, lím. 1500600 < 150046004000 + 600(2, 2500)tabla[2] = pilabase 21000, lím. 20002500 ≥ 2000excepciónEl desplazamiento se compara con el límite de SU segmento, y después se suma SU base.
Es la pareja base y límite de la memoria contigua, pero una por segmento.

La traducción es la de base y límite, pero con una pareja por segmento:

  1. Con el número de segmento se busca su fila en la tabla.
  2. Se comprueba que el desplazamiento es menor que el límite. Si no, excepción.
  3. Física = base + desplazamiento.

⟨1, 600⟩ es el segmento de datos (base 4000, límite 1500): 600 < 1500, física 4000 + 600 = 4600. ⟨2, 2500⟩ es la pila (límite 2000): 2500 ≥ 2000, excepción.

Segmentación
VentajasAtenúa la fragmentación; permite compartir segmentos (una DLL puede ser un segmento en las tablas de varios procesos); permite proteger segmentos (solo lectura, solo ejecución en los de código)
InconvenientesEl compilador y el enlazador trabajan con un espacio de dos dimensiones; necesita hardware; cada acceso es más lento (hay que mirar la tabla); no elimina del todo la fragmentación; su eficacia depende de cómo se trocee el proceso

Decir que la segmentación soluciona la fragmentación externa. Solo la atenúa: los segmentos siguen siendo bloques contiguos de tamaño variable, y entre ellos pueden quedar huecos inservibles.

Paginación

La paginación es la técnica que erradica definitivamente la fragmentación externa. La idea:

  • El programa se trocea en páginas de tamaño fijo (por ejemplo, 4 KiB).
  • La RAM se divide en marcos de página (page frames) del mismo tamaño.
  • Cada página puede ir a cualquier marco libre, sin importar si están seguidos.
  • La MMU asocia cada página lógica con su marco físico usando la tabla de páginas del proceso.
Páginas y marcosLas cuatro páginas de gcc van a los marcos 5, 9, 2 y 12 según su tabla de páginas; los demás marcos son de otros procesos o están libres.Paginación: páginas de gcc en marcos sueltosmemoria lógicapágina 00x0000página 10x1000página 20x2000página 30x3000tabla de páginas0→ 51→ 92→ 23→ 12RAM: 16 marcos de 4 KiBotro proceso0otro proceso1pág. 22otro proceso3libre4pág. 05otro proceso6otro proceso7libre8pág. 19otro proceso10libre11pág. 312otro proceso13libre14libre15El último marco libre vale para cualquier página:ya no hay fragmentación externa.
Mismo tamaño para páginas y marcos: por eso cualquier marco libre sirve para cualquier página.

Las cuatro páginas de gcc están en los marcos 5, 9, 2 y 12: desordenados y separados por marcos de otros procesos. Da igual. Como páginas y marcos miden lo mismo, cualquier marco libre sirve para cualquier página: ya no puede haber un hueco libre demasiado pequeño.

Gestionar el espacio libre se vuelve muy sencillo: basta con saber qué marcos están libres. El SO lo apunta en una tabla de marcos de página, que se puede implementar con un mapa de bits (un bit por marco: libre u ocupado) o con una lista de marcos libres. Cuando llega un proceso de cuatro páginas, se cogen cuatro marcos de la lista y se apuntan en su tabla.

Partir la dirección en página y desplazamiento

Toda dirección lógica se descompone en número de página y desplazamiento dentro de la página. Si la página mide 2^n bytes, los n bits de abajo son el desplazamiento y el resto, la página. En el portátil usamos direcciones lógicas de 16 bits y páginas de 4 KiB = 2^12 bytes: 4 bits de página y 12 de desplazamiento.

Partir la dirección en bitsLa dirección lógica 0x1A2C = 6700 se parte en página 1 y desplazamiento 2604; la tabla da el marco 9 y la física es 0x9A2C = 39468.0x1A2C con páginas de 4 KiB: 4 bits de página y 12 de desplazamientológica00011010001011000x1A2Cpágina 1desplazamiento 2604tabla[1] = 9se copia tal cualfísica10011010001011000x9A2C6700 = 1·4096 + 2604física = 9·4096 + 2604 = 39468
Solo cambian los 4 bits de arriba: en hexadecimal, el primer dígito es la página y los otros tres, el desplazamiento.

Para traducir 0x1A2C:

  1. En binario, 0001 1010 0010 1100. Los 4 bits de arriba, 0001, son la página 1. Los 12 de abajo, 1010 0010 1100 = 2604, el desplazamiento.
  2. La tabla de páginas dice que la página 1 está en el marco 9 (1001).
  3. Se cambian los 4 bits de la página por los del marco, y el desplazamiento se copia tal cual: 1001 1010 0010 1100 = 0x9A2C.

En decimal es la misma cuenta: 0x1A2C = 6700 = 1 · 4096 + 2604, y la física es 9 · 4096 + 2604 = 39468. Las fórmulas son:

  • página = dirección lógica // tamaño de página
  • desplazamiento = dirección lógica % tamaño de página
  • física = marco · tamaño de página + desplazamiento

Con páginas de 4 KiB y la dirección en hexadecimal, cada dígito son 4 bits, así que los 12 bits del desplazamiento son exactamente los tres últimos dígitos. 0x1A2C: página 1, desplazamiento 0xA2C. Marco 9: física 0x9A2C. Ni una división. Y si te la dan en decimal, divide entre 4096, no entre 4000 (pregúntaselo a mi simulacro).

Fragmentación interna en la paginación

La paginación no es perfecta. Si gcc ocupa 13000 bytes, necesita 4 páginas de 4 KiB (16384 bytes), y la última está casi vacía:

Fragmentación interna según el tamaño de páginagcc ocupa 13000 bytes: con páginas de 4 KiB necesita 4 y pierde 3384 bytes; con páginas de 1 KiB necesita 13 y pierde 312, pero su tabla tiene 13 entradas.Fragmentación interna: gcc ocupa 13000 bytesPáginas de 4 KiB: 4 páginas4·4096 − 13000 = 3384 bytes perdidos en la última página · tabla de 4 entradasPáginas de 1 KiB: 13 páginas13·1024 − 13000 = 312 bytes perdidos en la última página · tabla de 13 entradasPágina pequeña: menos desperdicio, tabla más grande. Página grande: al revés.
El trozo coral es memoria asignada que nadie usa: está dentro de la página, por eso es interna.

4 · 4096 − 13000 = 3384 bytes asignados que nadie usa. Como el desperdicio está dentro de la página, es fragmentación interna. Con páginas de 1 KiB, gcc ocuparía 13 páginas y solo perdería 13 · 1024 − 13000 = 312 bytes, pero su tabla tendría 13 entradas en vez de 4.

Tamaño de páginaFragmentación internaTabla de páginas
PequeñoMenor (se pierde menos en la última página)Más grande (más páginas, más entradas)
GrandeMayorMás pequeña

La tabla de páginas en la RAM: dos accesos por cada uno

¿Dónde se guarda la tabla de páginas? En máquinas antiguas como el DEC PDP-11 (direcciones de 16 bits, páginas de 8 KiB = 2^13), la dirección tenía 3 bits de página y la tabla, 2^3 = 8 entradas, cabía en registros rápidos de la CPU. Hoy las tablas son enormes (un proceso de 20 MB con páginas de 1 KiB necesita 20K entradas), así que la tabla vive en la memoria principal, y la CPU tiene dos registros para encontrarla:

  • RBTP (registro base de la tabla de páginas): dónde empieza la tabla en la RAM.
  • RLTP (registro de longitud de la tabla de páginas): cuántas entradas tiene.

El problema salta a la vista: para leer un dato hacen falta dos accesos a la RAM, uno a la tabla de páginas y otro al dato. Todos los programas irían al doble de lento. La solución es la TLB.

La TLB: un atajo para la traducción

La TLB (Translation Lookaside Buffer, en español búfer de traducción adelantada) es una pequeña caché asociativa dentro del procesador que guarda unas pocas entradas de la tabla de páginas. Se consulta casi tan rápido como un registro de la CPU. Sirve para acelerar la traducción y evitar accesos a la tabla de páginas.

¿Cómo funciona?

  • Guarda pares clave/valor ⟨página lógica, marco físico⟩.
  • Para traducir la página P, se busca P en todas las entradas de la TLB a la vez (por eso es asociativa).
  • Acierto de TLB: P está; se obtiene su marco y no hace falta mirar la tabla de páginas.
  • Fallo de TLB: P no está; se lee la tabla de páginas en la RAM y se guarda el par ⟨P, marco⟩ en la TLB para la próxima vez.
Acierto y fallo de TLBCon acierto, la TLB da el marco y solo se accede una vez a la RAM (65 ns). Con fallo, hay que leer la tabla de páginas en la RAM y después el dato (125 ns).La TLB: un atajo para no leer la tabla de páginasCPU(p, d)TLB(0→5) (1→9)5 nsaciertodato en la RAM60 nsfallotabla de páginasen la RAM · 60 ns+ se copia en la TLBacierto: 5 + 60 = 65 nsfallo: 5 + 60 + 60 = 125 ns
El camino verde es el que interesa repetir: un solo viaje a la RAM.

Con una TLB de 5 ns y una RAM de 60 ns, un acierto cuesta 5 + 60 = 65 ns (la TLB y el dato) y un fallo, 5 + 60 + 60 = 125 ns (la TLB, la tabla y el dato).

La TLB acceso a acceso

El simulador de Compruébalo con Python traduce once accesos de gcc con una TLB de solo 2 entradas. Cuando está llena y hay un fallo, sale la entrada menos usada recientemente:

Traza de la TLBOnce accesos de gcc: siete aciertos, tres fallos y una excepción (página 5). Al cargar la página 3 sale la 1, la menos usada.La TLB de 2 entradas, acceso a acceso (salida del simulador)lógicapág.TLBTLB despuéssalens0x00040fallo(0→5)1250x0FF00acierto(0→5)650x1A2C1fallo(0→5) (1→9)1250x10041acierto(0→5) (1→9)650x01000acierto(1→9) (0→5)650x30003fallo(0→5) (3→12)(1→9)1250x3FFC3acierto(0→5) (3→12)650x02000acierto(3→12) (0→5)650x50085excepción(3→12) (0→5)–0x30103acierto(0→5) (3→12)650x00080acierto(3→12) (0→5)657 aciertos de 10 accesos válidos = 70 % · media 83 ns
La página 0 se usa tanto que nunca sale de la TLB: eso es la localidad.

Sigue la columna «TLB después»:

AccesoQué pasa
0x0004Fallo: la TLB está vacía. Entra (0→5)
0x0FF0Acierto: también es la página 0
0x1A2CFallo: entra (1→9); ya hay dos entradas
0x0100Acierto de la página 0, que pasa a ser la más reciente
0x3000Fallo con la TLB llena: sale (1→9), la menos usada, y entra (3→12)
0x5008La página 5 no está en la tabla: excepción, no se traduce

Al final, 7 aciertos de 10 accesos válidos: un 70 %. La página 0 se usa tanto que nunca sale de la TLB. Eso es la localidad: los programas usan una y otra vez las mismas pocas páginas, y por eso una TLB minúscula acierta tanto.

Tiempo efectivo de acceso

Con una tasa de aciertos h, el tiempo efectivo medio de acceso a memoria es:

t = h · (TLB + RAM) + (1 − h) · (TLB + RAM + RAM)

El ejemplo de clase: TLB de 5 ns, RAM de 60 ns y un 90 % de aciertos:

t = 0,9 · (5 + 60) + 0,1 · (5 + 60 + 60) = 58,5 + 12,5 = 71 ns

Solo 11 ns más que un acceso directo a la RAM, frente a los 120 ns que costaría sin TLB. En nuestro simulador, con un 70 %, sale 0,7 · 65 + 0,3 · 125 = 45,5 + 37,5 = 83 ns.

Tiempo efectivo con TLBRecta del tiempo efectivo entre 125 ns (0 % de aciertos) y 65 ns (100 %): 83 ns al 70 %, 71 ns al 90 % y 66,2 ns al 98 %.Tiempo efectivo de acceso según la tasa de aciertos6580951101250 %20 %40 %60 %80 %100 %83.0 nsnuestro simulador71.0 nsejemplo de clase66.2 nsnst = h·(5+60) + (1−h)·(5+60+60) → 0,9·65 + 0,1·125 = 71 ns
Con tasas reales (80-98 %) la paginación cuesta poco más que un acceso directo a la RAM.

En procesadores reales se aciertan entre el 80 % y el 98 % de las veces, con TLB muy pequeñas: 22 entradas en el Motorola 68030, 32 en el Intel 80486 y entre 4 y 64 en los Intel Skylake (2015).

Olvidar sumar el tiempo de la TLB en el fallo. Para saber que no está, primero has tenido que mirarla: fallo = TLB + tabla + dato, no tabla + dato. Y otro clásico: pensar que con un acierto no se toca la RAM. Sí se toca, una vez, para el dato.

Situaciones especiales de la TLB

  • Fallo con la TLB llena: hay que reemplazar una entrada. ¿Cuál? La más vieja, la menos usada, una al azar… En nuestro simulador, la menos usada recientemente.
  • Cambio de contexto: las traducciones de gcc no valen para spotify (su página 0 está en otro marco). Hay que vaciar la TLB o cargarla con el contenido que tenía el proceso que entra en la CPU.

Protección en la paginación

La paginación también protege:

  • Bits de protección en cada entrada de la tabla: lectura, escritura y ejecución. Así el código puede ser de solo lectura y ejecución, y los datos, de lectura y escritura.
  • Protección contra accesos fuera del espacio lógico del proceso.

Para lo segundo hay tres medidas posibles:

  • El RLTP: si el número de página no es menor que la longitud de la tabla, excepción.
  • El bit de validez: las entradas que no corresponden a páginas del proceso se marcan como no válidas.
  • La tabla de páginas invertida, en la que solo figuran las páginas válidas.

Eso es lo que pasó con 0x5008 en la traza: la página 5 no es de gcc y la traducción termina en excepción.

El tamaño de la tabla de páginas

Echemos cuentas con una arquitectura de 32 bits y páginas de 4 KiB:

Tamaño de la tabla de páginasCon 32 bits y páginas de 4 KiB hay 20 bits de página: 2^20 entradas de 4 bytes, 4 MiB por proceso. Con páginas de 4 MiB, 10 bits y una tabla de 4 KiB.¿Cuánto ocupa la tabla de páginas? (direcciones de 32 bits)Páginas de 4 KiBpágina: 20 bitsdesplazamiento: 12 bits2²⁰ = 1 048 576 entradas × 4 B = 4 MiB por procesoPáginas de 4 MiBpágina: 10 bitsdesplazamiento: 22 bits2¹⁰ = 1024 entradas × 4 B = 4 KiB por procesoAgrandar la página encoge la tabla, pero dispara la fragmentación interna.
Cada bit que se pasa del desplazamiento a la página duplica las entradas de la tabla.

12 bits de desplazamiento, así que quedan 20 bits de página: 2^20 = 1 048 576 entradas. Si cada entrada ocupa 4 bytes, la tabla de un solo proceso ocupa 4 MiB. Con direcciones de 48 o 64 bits es mucho peor.

La solución «fácil» es agrandar la página: con páginas de 4 MiB quedarían 10 bits de página y una tabla de 4 KiB… a cambio de disparar la fragmentación interna. Las soluciones serias son tres: paginación jerárquica, tabla de páginas invertida y tabla hash de páginas.

El cálculo siempre es el mismo. Bits de desplazamiento = log2 del tamaño de página. Bits de página = bits de la dirección − bits de desplazamiento. Entradas = 2^bits de página. Tamaño = entradas × bytes por entrada.

Paginación jerárquica

La propia tabla de páginas se pagina, y se forma una jerarquía de niveles. En el Intel x86 de 32 bits, la dirección se parte en 10 + 10 + 12 bits:

Paginación de dos nivelesLa dirección de 32 bits se parte en p1, p2 y d (10, 10 y 12 bits). p1 indexa la tabla exterior, p2 la interior y d el marco: tres accesos a la RAM.Paginación jerárquica (x86 de 32 bits): la tabla también se paginap1 · 10 bitsp2 · 10 bitsd · 12 bitstabla exterioracceso ① a la RAMtabla interioracceso ② a la RAMmarco: el datoacceso ③ a la RAMSin acierto de TLB: niveles + 1 accesos → 2 niveles = 3, 3 niveles = 4, x86-64 (4 niveles) = 5.Por eso la TLB se vuelve todavía más necesaria.
Solo se guardan las tablas interiores que el proceso usa: ahí está el ahorro.
  • p1 (10 bits) indexa la tabla exterior (primaria), que dice dónde está la tabla interior.
  • p2 (10 bits) indexa esa tabla interior (secundaria), que da el marco.
  • d (12 bits) es el desplazamiento dentro del marco.

¿Dónde está el ahorro? En que solo hace falta guardar las tablas interiores que el proceso usa de verdad. El precio: sin acierto de TLB, hacen falta tres accesos a la RAM (dos tablas y el dato), así que la TLB se vuelve todavía más necesaria. En general, con una jerarquía de n niveles son n + 1 accesos.

El x86 de 64 bits usa hasta cuatro niveles (page map level 4, page directory pointer table, page directory y page table, 9 bits cada uno, más 12 de desplazamiento; los bits 63 a 48 no se usan) y páginas de 4 KiB, 2 MiB o 1 GiB.

Tabla de páginas invertida

Le da la vuelta a la idea: en lugar de una tabla por proceso con una entrada por página, hay una sola tabla global con una entrada por cada marco real de la RAM. La entrada K dice qué página lógica hay en el marco K y a qué proceso (pid) pertenece.

Tabla de páginas invertidaSe busca el par (pid 412, página 1) en la tabla global; aparece en la posición 9, que es el marco, y la física es (9, 0xA2C).Tabla de páginas invertida: una entrada por marco, para todos(412, 1, 0xA2C)pid · página · desplaz.marco · (pid, página)0377, 41377, 02412, 23500, 14–5412, 0……9412, 1……se busca(9, 0xA2C)posición = marcoAhorra muchísima memoria, pero buscar es lento: se usa hash y se confía en la TLB.
La tabla es una sola para todo el sistema y su tamaño depende de la RAM, no de los procesos.

La CPU emite ⟨pid, página, desplazamiento⟩. Se busca el par ⟨pid, página⟩ en la tabla, y la posición donde aparece es el marco. En el dibujo, ⟨412, 1⟩ (gcc, página 1) está en la posición 9: la física es ⟨9, 0xA2C⟩, la misma 0x9A2C de antes.

  • Ventaja: grandísimo ahorro de memoria, porque el tamaño de la tabla depende de la RAM, no del número de procesos.
  • Inconveniente: buscar en la tabla es lentísimo. Se confía en los aciertos de la TLB o se implementa como una tabla hash. La han usado PowerPC, UltraSPARC e IA-64.

Contigua, segmentación y paginación, frente a frente

Contigua, segmentación y paginacióngcc en un bloque (contigua), en tres segmentos de distinto tamaño (segmentación) o en cuatro marcos sueltos (paginación), con la fragmentación de cada una.Tres formas de colocar a gcc en la RAMcontiguaun bloquesegmentacióntrozos lógicos de tamaño variablepaginaciónpáginas de tamaño fijogccpilacódigodatosmarcos 2, 5, 9 y 12frag. externa: síinterna: no (MVT)frag. externa: menosinterna: nofrag. externa: nointerna: sí (última página)
De izquierda a derecha, cada vez más trozos y menos fragmentación externa.
Contigua (MVT)SegmentaciónPaginación
TrozosUnoSegmentos lógicos, de tamaño variablePáginas de tamaño fijo
Dirección lógicaUn número⟨segmento, desplazamiento⟩⟨página, desplazamiento⟩
Traducciónbase + lógica (si < límite)base[s] + d (si d < límite[s])marco[p] · tamaño + d
Fragmentación externaSíMenos, pero síNo
Fragmentación internaNoNoSí, en la última página
¿La ve el programador?NoSí: son las partes de su programaNo

Errores típicos

Dividir entre 1000 en lugar de entre 1024. 1 KiB = 1024 bytes, 4 KiB = 4096. Es mi anécdota del principio y cuesta el ejercicio entero aunque tengas bien la página y el marco.

Traducir también el desplazamiento. Al pasar de lógica a física solo cambia la página por el marco; el desplazamiento se copia tal cual.

En segmentación, aceptar un desplazamiento igual al límite. El límite es el tamaño del segmento: si mide 1500, los desplazamientos válidos van de 0 a 1499. 1500 ya da excepción.

Pensar que el mejor ajuste siempre gana. Primer y mejor ajuste rinden parecido; el que es peor es el peor ajuste. Un ejemplo concreto no demuestra nada general.

Contar mal los accesos con paginación jerárquica. Sin acierto de TLB son niveles + 1: uno por cada tabla y uno para el dato.

Compruébalo con Python

Dos simuladores cortos con datos fijos, ejecutados una vez. Puedes copiarlos y ejecutarlos con python3.

Simulador 1: traducción con paginación y TLB

gcc tiene las cuatro páginas de la tabla del dibujo (0 → 5, 1 → 9, 2 → 2, 3 → 12), las páginas miden 4 KiB y la TLB tiene 2 entradas. El simulador traduce once direcciones, cuenta los aciertos y calcula el tiempo medio de dos formas: midiendo acceso a acceso y con la fórmula.

# Traducción de direcciones con paginación y una TLB de 2 entradas (datos fijos)
PAGINA = 4096                       # páginas de 4 KiB = 2**12 → 12 bits de desplazamiento
TABLA = {0: 5, 1: 9, 2: 2, 3: 12}   # tabla de páginas de gcc: página → marco
T_TLB, T_RAM = 5, 60                # ns: mirar la TLB y un acceso a la RAM
accesos = [0x0004, 0x0FF0, 0x1A2C, 0x1004, 0x0100, 0x3000, 0x3FFC, 0x0200, 0x5008, 0x3010, 0x0008]
tlb = []                            # pares (página, marco); el primero es el menos usado
aciertos = validos = total_ns = 0
print(f'{"lógica":<8}  {"pág":>4}  {"desp":>6}   {"TLB":<9}  {"marco":>5}  {"física":>8}  {"ns":>4}')
for dl in accesos:
    p, d = divmod(dl, PAGINA)       # p = 4 bits altos, d = 12 bits bajos
    marco = next((m for (q, m) in tlb if q == p), None)
    if marco is not None:           # acierto: la TLB ya sabía el marco
        tlb.remove((p, marco)); tlb.append((p, marco))
        estado, ns = 'acierto', T_TLB + T_RAM            # TLB + el dato
        aciertos += 1
    elif p in TABLA:                # fallo: hay que leer la tabla de páginas en la RAM
        marco = TABLA[p]
        estado, ns = 'fallo', T_TLB + T_RAM + T_RAM      # TLB + tabla + el dato
        if len(tlb) == 2:
            tlb.pop(0)              # TLB llena: sale la menos usada (LRU)
        tlb.append((p, marco))
    else:                           # la página no está en la tabla: acceso ilegal
        print(f'0x{dl:04X}    {p:>4}  {d:>6}   {"excepción":<9}  {"-":>5}  {"-":>8}  {"-":>4}')
        continue
    df = marco * PAGINA + d         # el desplazamiento se copia tal cual
    validos += 1; total_ns += ns
    print(f'0x{dl:04X}    {p:>4}  {d:>6}   {estado:<9}  {marco:>5}  {"0x%04X" % df:>8}  {ns:>4}')
h = aciertos / validos
print(f'aciertos de TLB: {aciertos}/{validos} = {h:.0%}')
print(f'tiempo medio medido: {total_ns / validos:.1f} ns')
print(f'fórmula: {h:.2f}·({T_TLB}+{T_RAM}) + {1 - h:.2f}·({T_TLB}+{T_RAM}+{T_RAM}) = {h * 65 + (1 - h) * 125:.1f} ns')

Esta es su salida real:

lógica     pág    desp   TLB        marco    física    ns
0x0004       0       4   fallo          5    0x5004   125
0x0FF0       0    4080   acierto        5    0x5FF0    65
0x1A2C       1    2604   fallo          9    0x9A2C   125
0x1004       1       4   acierto        9    0x9004    65
0x0100       0     256   acierto        5    0x5100    65
0x3000       3       0   fallo         12    0xC000   125
0x3FFC       3    4092   acierto       12    0xCFFC    65
0x0200       0     512   acierto        5    0x5200    65
0x5008       5       8   excepción      -         -     -
0x3010       3      16   acierto       12    0xC010    65
0x0008       0       8   acierto        5    0x5008    65
aciertos de TLB: 7/10 = 70%
tiempo medio medido: 83.0 ns
fórmula: 0.70·(5+60) + 0.30·(5+60+60) = 83.0 ns

Las piezas importantes:

  • divmod(dl, PAGINA) hace la división entera y el resto a la vez: la página y el desplazamiento.
  • La lista tlb guarda pares (página, marco) ordenados de menos a más usados. En un acierto, el par se quita y se vuelve a añadir al final (pasa a ser el más reciente); en un fallo con la TLB llena, tlb.pop(0) saca el primero, el menos usado. Es una TLB con reemplazo LRU.
  • marco * PAGINA + d es la fórmula de la física: el desplazamiento no se toca.
  • Las direcciones se imprimen en hexadecimal con 0x{dl:04X} para que se vea el truco de los tres últimos dígitos.

Qué observar:

  1. En todas las filas, los tres últimos dígitos hexadecimales de la lógica y de la física son iguales: 0x1A2C → 0x9A2C, 0x3FFC → 0xCFFC. Solo cambia el primero (la página por el marco; el 12 es la C).
  2. Los fallos cuestan 125 ns y los aciertos 65: un fallo es casi el doble.
  3. En 0x3000 la TLB está llena con las páginas 1 y 0. Sale la 1 porque la 0 se acaba de usar en 0x0100. Si después se pidiera otra vez la página 1, fallaría.
  4. 0x5008 no se traduce: la página 5 no está en TABLA, así que no hay marco ni tiempo, y no cuenta para la media.
  5. La media medida (83 ns) y la de la fórmula coinciden, porque la fórmula es precisamente la media ponderada de 65 y 125 con la tasa de aciertos (0,70).

Simulador 2: primer, mejor y peor ajuste

Los cinco huecos y las cuatro peticiones del dibujo de las políticas. La función elige filtra los huecos que caben y escoge según la política.

# Asignación contigua (MVT): primer, mejor y peor ajuste con los mismos huecos (datos fijos)
HUECOS = [(100, 120), (300, 480), (900, 210), (1200, 300), (1600, 560)]   # (inicio, tamaño) en KiB
PETICIONES = [('gcc', 210), ('spotify', 430), ('campus', 110), ('onedrive', 470)]


def elige(huecos, tam, politica):
    validos = [h for h in huecos if h[1] >= tam]             # solo sirven los que caben
    if not validos:
        return None
    if politica == 'primer':
        return validos[0]                                    # el primero de la lista
    if politica == 'mejor':
        return min(validos, key=lambda h: h[1])              # el que deja menos resto
    return max(validos, key=lambda h: h[1])                  # peor: el más grande


for politica in ('primer', 'mejor', 'peor'):
    huecos = list(HUECOS)                                    # copia: cada política empieza igual
    print(f'== {politica} ajuste')
    for nombre, tam in PETICIONES:
        h = elige(huecos, tam, politica)
        if h is None:
            libre = sum(t for _, t in huecos)
            print(f'  {nombre:<8} {tam:>3} KiB → no cabe (libres {libre} KiB, mayor hueco {max(t for _, t in huecos)})')
            continue
        i = huecos.index(h)
        huecos[i] = (h[0] + tam, h[1] - tam)                 # el resto sigue siendo un hueco
        print(f'  {nombre:<8} {tam:>3} KiB → en {h[0]:>4} (hueco de {h[1]}, resto {h[1] - tam})')
    huecos = [h for h in huecos if h[1] > 0]
    print('  huecos:', ' '.join(str(t) for _, t in huecos))

Su salida real:

== primer ajuste
  gcc      210 KiB → en  300 (hueco de 480, resto 270)
  spotify  430 KiB → en 1600 (hueco de 560, resto 130)
  campus   110 KiB → en  100 (hueco de 120, resto 10)
  onedrive 470 KiB → no cabe (libres 920 KiB, mayor hueco 300)
  huecos: 10 270 210 300 130
== mejor ajuste
  gcc      210 KiB → en  900 (hueco de 210, resto 0)
  spotify  430 KiB → en  300 (hueco de 480, resto 50)
  campus   110 KiB → en  100 (hueco de 120, resto 10)
  onedrive 470 KiB → en 1600 (hueco de 560, resto 90)
  huecos: 10 50 300 90
== peor ajuste
  gcc      210 KiB → en 1600 (hueco de 560, resto 350)
  spotify  430 KiB → en  300 (hueco de 480, resto 50)
  campus   110 KiB → en 1810 (hueco de 350, resto 240)
  onedrive 470 KiB → no cabe (libres 920 KiB, mayor hueco 300)
  huecos: 120 50 210 300 240

Lo importante:

  • validos deja solo los huecos con tamaño suficiente; si no queda ninguno, la petición fracasa.
  • min(..., key=lambda h: h[1]) y max(...) eligen por tamaño, que es el segundo elemento de cada pareja (inicio, tamaño).
  • huecos[i] = (h[0] + tam, h[1] - tam): el resto del hueco empieza donde acaba lo asignado. Si el resto es 0, el hueco desaparece al final.
  • list(HUECOS) hace una copia para que las tres políticas empiecen con los mismos huecos.

Qué observar:

  1. El primer ajuste mete a gcc en el hueco de 480 (el primero que le cabe) aunque el de 210 le iba justo.
  2. El mejor ajuste mete a gcc en el de 210 con resto 0: aprovecha el hueco entero.
  3. Cuando onedrive no cabe, hay 920 KiB libres y el mayor hueco es de 300: fragmentación externa de manual.
  4. El peor ajuste entrega el hueco de 560 a gcc y lo deja en 350; luego campus se come parte de ese resto. Al final no queda ningún hueco grande.

Cambia el orden de PETICIONES (por ejemplo, onedrive primero) y vuelve a ejecutar. Verás que el resultado depende mucho del orden de llegada, que es otra razón por la que un solo ejemplo no basta para decidir qué política es mejor.

Resumen

ConceptoEn una frase
Dirección lógica / físicaLa que genera la CPU / la que llega a la RAM
MMUHardware que traduce lógicas a físicas en cada acceso
Reubicación estática / dinámicaLa hace el cargador al cargar / el hardware al ejecutar
Base y límiteFísica = base + lógica, solo si lógica < límite; si no, excepción
MFT / MVTParticiones fijas al arrancar / variables con lista de huecos
Primer, mejor, peor ajustePrimero que cabe / el que deja menos resto / el más grande
Fragmentación externa / internaHuecos libres sueltos / espacio sin usar dentro de lo asignado
Segmentación⟨s, d⟩ y tabla de segmentos con base y límite
Paginación⟨p, d⟩, páginas y marcos del mismo tamaño, tabla de páginas
TLBCaché de ⟨página, marco⟩; t = h·(T+M) + (1−h)·(T+2M)
Tamaño de la tabla2^bits de página × tamaño de entrada
Jerárquica / invertidaLa tabla se pagina (niveles + 1 accesos) / una entrada por marco para todo el sistema

Tres ideas para llevarte:

  1. La MMU traduce cada acceso: con base y límite, con tabla de segmentos o con tabla de páginas, siempre se comprueba primero y se suma (o se sustituye) después.
  2. Trocear el proceso ataca la fragmentación externa: la segmentación la atenúa y la paginación la elimina, a cambio de algo de fragmentación interna.
  3. La paginación duplicaría el tiempo de acceso sin la TLB, y con direcciones grandes su tabla sería enorme sin niveles o tabla invertida.

Ahora toca demostrarlo. Primero, en la práctica guiada de gestión de memoria traducimos direcciones y calculamos el tiempo con TLB paso a paso; después, en los ejercicios tipo examen de gestión de memoria tienes un test con todos los formatos, cinco cálculos (traducir direcciones, tiempo efectivo, ajustes, tamaño de la tabla y segmentación) y preguntas de desarrollo. Y en el próximo tema daremos el paso que falta: la memoria virtual, que permite ejecutar procesos que no caben enteros en la RAM.

Siguiente paso: prácticaGestión de memoria — práctica con problemas guiados paso a paso: traducir con paginación, segmentación con límites, la TLB y un ejercicio con el formato del examen (y la dirección física que di para un segmento que no existía)

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →