1. El problema: la asignación contigua es rígida
Con base y límite (tema 11) y particiones (tema 12), el programa es un bloque contiguo en la RAM. Esto tiene inconvenientes:
- Si el programa crece (más heap, más pila), hay que ampliar el bloque, y puede no haber espacio contiguo.
- No se puede compartir código entre procesos (cada uno necesita su bloque).
- La protección es tosca: todo el bloque tiene los mismos permisos.
La segmentación resuelve estos problemas dividiendo el programa en partes lógicas independientes.
2. La idea: segmentos lógicos
Un programa no es un bloque uniforme: tiene código, datos, pila y heap, cada uno con tamaño y permisos distintos. La segmentación trata cada parte como un segmento independiente, con su propia base y límite.
Cada segmento se carga en un bloque contiguo de la RAM, pero los segmentos de un mismo proceso pueden estar en posiciones distintas (no contiguos entre sí). Así, el código puede estar en una zona y la pila en otra.
Segmentación es la técnica que divide el espacio de direcciones de un proceso en segmentos lógicos (código, datos, pila, heap), cada uno con su propia base, límite y permisos, asignables de forma independiente en la RAM.
3. Dirección lógica: segmento + desplazamiento
La dirección deja de ser un número y pasa a ser un par (s, d):
- s: número de segmento (índice en la tabla de segmentos).
- d: desplazamiento dentro del segmento.
La MMU traduce: si d < límite[s], la dirección física es base[s] + d. Si no, excepción de protección.
En hardware, la dirección lógica suele codificarse con los bits altos indicando el segmento y los bajos el desplazamiento.
4. Segmentos típicos
Un programa suele tener estos segmentos:
| Segmento | Contenido | Permisos | Crecimiento |
|---|---|---|---|
| Código (text) | Instrucciones | r-x | No (fijo) |
| Datos (data) | Globales inicializadas | rw- | No (fijo) |
| BSS | Globales no inicializadas | rw- | No (fijo) |
| Heap | Memoria dinámica | rw- | Hacia arriba |
| Pila (stack) | Variables locales | rw- | Hacia abajo |
El heap y la pila crecen en direcciones opuestas dentro de sus propios segmentos, sin afectar a los demás.
5. Ventajas
- Protección por segmento: cada segmento tiene sus propios permisos (código r-x, datos rw-).
- Compartición: varios procesos pueden compartir el segmento de código (misma base, mismo límite).
- Crecimiento independiente: el heap y la pila pueden crecer sin mover el código ni los datos.
- Reubicación por segmento: cada segmento se carga donde haya espacio; no necesita ser contiguo con los demás.
- Modularidad: segmentos de bibliotecas se cargan solo cuando se necesitan.
6. Limitaciones
- Fragmentación externa: cada segmento es un bloque contiguo; quedan huecos entre segmentos.
- Segmentos grandes: un segmento muy grande (ej. un array global de 1 GiB) requiere un bloque contiguo grande.
- Complejidad: la tabla de segmentos es por proceso; la MMU debe buscar en ella.
- Sin memoria virtual por páginas: no se puede cargar solo una parte de un segmento.
Estas limitaciones motivaron la paginación (tema 18), que combina segmentación y paginación (segmentación paginada).
7. Ejemplo numérico
Un proceso tiene 3 segmentos:
| Segmento | Base | Límite | Permisos |
|---|---|---|---|
| 0 (código) | 0x10000 | 0x3000 | r-x |
| 1 (datos) | 0x20000 | 0x2000 | rw- |
| 2 (pila) | 0x30000 | 0x1000 | rw- |
Traducción de direcciones:
| Dirección lógica | Segmento | Desplazamiento | ¿Válida? | Física |
|---|---|---|---|---|
| (0, 0x0500) | 0 | 0x0500 | Sí | 0x10500 |
| (1, 0x1FFF) | 1 | 0x1FFF | Sí | 0x21FFF |
| (2, 0x0FFF) | 2 | 0x0FFF | Sí | 0x30FFF |
| (0, 0x3000) | 0 | 0x3000 | No (límite) | — |
| (1, 0x2000) | 1 | 0x2000 | No (límite) | — |
8. Experimento en C
El programa siguiente simula la traducción con segmentación:
#include <stdio.h>
typedef struct { unsigned long base, limite; int perm; } Segmento;
static int traducir(Segmento *t, int s, unsigned long d, unsigned long *fis)
{
if (s < 0 || s >= 3) return 0; /* segmento inválido */
if (d >= t[s].limite) return 0; /* desplazamiento fuera de rango */
*fis = t[s].base + d;
return 1;
}
int main(void)
{
Segmento t[3] = {
{0x10000, 0x3000, 5}, /* código: r-x */
{0x20000, 0x2000, 6}, /* datos: rw- */
{0x30000, 0x1000, 6} /* pila: rw- */
};
int segs[] = {0, 1, 2, 0, 1};
unsigned long offs[] = {0x0500, 0x1FFF, 0x0FFF, 0x3000, 0x2000};
for (int i = 0; i < 5; i++) {
unsigned long f;
if (traducir(t, segs[i], offs[i], &f))
printf("seg %d off 0x%04lx -> fisica 0x%04lx\\n", segs[i], offs[i], f);
else
printf("seg %d off 0x%04lx -> EXCEPCION\\n", segs[i], offs[i]);
}
return 0;
}
$ gcc -std=c17 -Wall -Wextra -o segmentacion segmentacion.c
$ ./segmentacion
seg 0 off 0x0500 -> fisica 0x10500
seg 1 off 0x1fff -> fisica 0x21fff
seg 2 off 0x0fff -> fisica 0x30fff
seg 0 off 0x3000 -> EXCEPCION
seg 1 off 0x2000 -> EXCEPCIONseg 1 off 0x2000 -> EXCEPCION
9. Probar con un simulador
Este simulador traduce direcciones lógicas (segmento, desplazamiento) usando una tabla de segmentos editable.
Simulador · segmentación
| Campo | Valor |
|---|---|
| Base del segmento | |
| Límite del segmento | |
| ¿Dentro del límite? | |
| Dirección física |
Tabla de segmentos (editable):
10. En Linux y x86 hoy
En x86-64, la segmentación se usa de forma plana: todos los segmentos (CS, DS, SS, ES) tienen base 0 y límite máximo. La protección y la traducción real se hacen con paginación. Pero el concepto de segmentos sigue presente en:
- ELF: los segmentos LOAD del binario (código, datos) son segmentos lógicos.
- FS y GS: en x86-64 se usan para acceder a datos del hilo (TLS) y del núcleo.
- Memoria virtual: cada región de /proc/PID/maps es un "segmento" lógico.
11. Resumen
- La segmentación divide el proceso en segmentos lógicos (código, datos, pila, heap).
- La dirección lógica es un par (segmento, desplazamiento).
- La MMU traduce: si desplazamiento < límite, física = base + desplazamiento.
- Ventajas: protección por segmento, compartición de código, crecimiento independiente.
- Limitaciones: fragmentación externa, segmentos grandes, sin memoria virtual por páginas.
- En x86-64 la segmentación es plana; la protección real se hace con paginación.
12. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Qué es un segmento?
Una parte lógica del proceso (código, datos, pila, heap) con su propia base, límite y permisos.
2. ¿Cómo se forma una dirección lógica en segmentación?
Como un par (segmento, desplazamiento): el número de segmento indexa la tabla, y el desplazamiento se suma a la base.
3. ¿Qué ventaja tiene la segmentación sobre la asignación contigua?
Permite asignar cada segmento de forma independiente, compartir código y proteger por segmento.
4. ¿Qué limitación tiene la segmentación?
Fragmentación externa (cada segmento es un bloque contiguo) y la necesidad de bloques grandes para segmentos grandes.
5. ¿Cómo se traduce una dirección (s, d)?
Si d < límite[s], la física es base[s] + d; si no, excepción de protección.
6. ¿Por qué la pila y el heap pueden crecer independientemente?
Porque son segmentos separados con sus propias bases y límites; crecer uno no afecta al otro.
7. ¿Se usa la segmentación en x86-64 moderno?
De forma plana (base 0, límite máximo) para CS/DS/SS/ES; FS y GS se usan para TLS y datos del núcleo.
8. ¿Qué problema resuelve la paginación que la segmentación no?
La fragmentación externa y la necesidad de bloques contiguos grandes.
Para practicar
- Modifica
segmentacion.cpara añadir un segmento de heap (segmento 3) con base 0x40000 y límite 0x2000. - Traduce a mano las direcciones (0, 0x2FFF), (3, 0x1FFF) y (1, 0x2000). ¿Cuáles son válidas?
- Explica por qué la compartición de código es natural con segmentación.
- Dibuja la RAM con 3 segmentos de un proceso y huecos entre ellos. ¿Hay fragmentación externa?
- Investiga cómo usa Linux los segmentos FS y GS en x86-64.
- Compara la segmentación con la paginación: ¿qué ventaja tiene cada una?
- Implementa en C una tabla de segmentos con permisos (r, w, x) y comprueba los accesos.