← Gestión de Memoria Bloque 4 · Segmentación · Tema 16 de 52

Tema 16

Tabla de segmentos, protección y compartición

La tabla de segmentos es la estructura que la MMU consulta para traducir y proteger. Cada entrada tiene base, límite y permisos. En este tema verás cómo se organiza, cómo protege el acceso por segmento, cómo se comparte código entre procesos y por qué la famosa "falla de segmentación" (SIGSEGV) no es un error de segmentación en el sentido clásico.

1. La tabla de segmentos

Cada proceso tiene su propia tabla de segmentos, mantenida por el sistema operativo y consultada por la MMU. La tabla es un array de entradas; el número de segmento (s) es el índice. Al cambiar de proceso, el SO carga el registro que apunta a la tabla de ese proceso.

La tabla puede estar en memoria (y la MMU la lee en cada acceso, con caché) o, en arquitecturas modernas, en registros dedicados.

2. Campos de cada entrada

CampoSignificado
BaseDirección física donde empieza el segmento en la RAM.
LímiteTamaño del segmento (en bytes o palabras).
PermisosBits de lectura (R), escritura (W) y ejecución (X).
PresenteSi el segmento está en la RAM.
Privilegio (DPL)Nivel de privilegio requerido para acceder (en x86).

3. Protección por segmento

La MMU comprueba, en cada acceso, que:

  1. El segmento s sea válido (exista en la tabla).
  2. El desplazamiento d sea menor que el límite del segmento.
  3. El acceso (lectura/escritura/ejecución) esté permitido por los permisos del segmento.
  4. El nivel de privilegio del proceso sea suficiente.

Si alguna condición falla, se lanza una excepción de protección. El sistema operativo la atiende y, normalmente, envía SIGSEGV al proceso.

Esto permite, por ejemplo, que el segmento de código sea de solo lectura y ejecución (r-x), impidiendo que un programa modifique su propio código (W^X).

4. Compartición de código

La compartición es natural con segmentación: varios procesos pueden tener entradas en su tabla que apunten al mismo segmento físico (misma base y límite). Así, el código de libc se carga una sola vez en la RAM y se comparte entre todos los procesos que la usan.

Dos procesos comparten el mismo segmento de código físico Tabla A Tabla B Segmento de código base = 0x10000 límite = 0x3000 perm = r-x entrada 0 entrada 0 Datos A base = 0x20000 Datos B base = 0x30000
Dos procesos comparten el mismo segmento de código (misma base, límite y permisos), pero tienen segmentos de datos distintos.

5. El origen de la falla de segmentación

La "falla de segmentación" (segmentation fault, SIGSEGV) es la señal que el sistema envía a un proceso cuando intenta acceder a una dirección que no le pertenece o con permisos inadecuados. A pesar del nombre, no tiene nada que ver con la segmentación en el sentido clásico: es el mecanismo de protección de memoria funcionando.

El nombre histórico viene de los sistemas con segmentación, donde un acceso fuera de los límites de un segmento generaba una "falla de segmento". Hoy, con paginación, el mismo mecanismo (la MMU detecta el acceso inválido y lanza una excepción) se llama SIGSEGV por compatibilidad.

Causas típicas de SIGSEGV:

Una SIGSEGV es una buena noticia

Sin protección, el programa leería o escribiría en silencio la memoria de otro. El sistema prefiere detener el proceso culpable en el acto, señalando dónde falló.

6. Ejemplo numérico

Tabla de segmentos de un proceso:

SegBaseLímitePermisos
00x100000x3000r-x
10x200000x2000rw-
20x300000x1000rw-

Accesos:

AccesoResultadoMotivo
Leer seg 0, off 0x1000OKDentro del límite, permiso r
Escribir seg 0, off 0x1000SIGSEGVSin permiso w
Leer seg 1, off 0x2000SIGSEGVoff ≥ límite
Ejecutar seg 1, off 0x0000SIGSEGVSin permiso x
Leer seg 5, off 0x0000SIGSEGVSegmento inexistente

7. Experimento en C

El programa siguiente simula la protección por segmento y muestra qué accesos son válidos:

proteccion_seg.c
#include <stdio.h>

typedef struct { unsigned long base, limite; int r, w, x; } Segmento;

static int acceder(Segmento *t, int s, unsigned long off, char modo)
{
    if (s < 0 || s >= 3) return 0;
    if (off >= t[s].limite) return 0;
    if (modo == 'r' && !t[s].r) return 0;
    if (modo == 'w' && !t[s].w) return 0;
    if (modo == 'x' && !t[s].x) return 0;
    return 1;
}

int main(void)
{
    Segmento t[3] = {
        {0x10000, 0x3000, 1, 0, 1},  /* código: r-x */
        {0x20000, 0x2000, 1, 1, 0},  /* datos: rw- */
        {0x30000, 0x1000, 1, 1, 0}   /* pila: rw- */
    };
    struct { int seg; unsigned long off; char modo; } casos[] = {
        {0, 0x1000, 'r'}, {0, 0x1000, 'w'}, {1, 0x2000, 'r'},
        {1, 0x0000, 'x'}, {5, 0x0000, 'r'}
    };
    for (int i = 0; i < 5; i++) {
        int ok = acceder(t, casos[i].seg, casos[i].off, casos[i].modo);
        printf("seg %d off 0x%04lx modo %c -> %s\\n",
               casos[i].seg, casos[i].off, casos[i].modo, ok ? "OK" : "SIGSEGV");
    }
    return 0;
}
terminal · protección por segmento
$ gcc -std=c17 -Wall -Wextra -o proteccion_seg proteccion_seg.c
$ ./proteccion_seg
seg 0 off 0x1000 modo r -> OK
seg 0 off 0x1000 modo w -> SIGSEGV
seg 1 off 0x2000 modo r -> SIGSEGV
seg 1 off 0x0000 modo x -> SIGSEGV
seg 5 off 0x0000 modo r -> SIGSEGVseg 5 off 0x0000 modo r -> SIGSEGV

8. Probar con un simulador

Este simulador evalúa un acceso (segmento, desplazamiento, modo) contra una tabla de segmentos editable con base, límite y permisos.

Simulador · protección por segmento

CampoValor
Base
Límite
Permisos
¿Dentro del límite?
¿Permiso concedido?
Resultado

Tabla de segmentos (editable: base, límite, r, w, x):

9. En Linux y x86 hoy

En Linux moderno, la protección no se hace por segmentos (que son planos en x86-64) sino por páginas (bits de permisos en la tabla de páginas). Pero el concepto de segmentación sigue en:

10. Resumen

11. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. ¿Qué campos tiene una entrada de la tabla de segmentos?

Base, límite, permisos (r, w, x), presente y nivel de privilegio.

2. ¿Qué comprueba la MMU en cada acceso?

Que el segmento sea válido, que el desplazamiento sea menor que el límite y que el modo de acceso esté permitido.

3. ¿Cómo se comparte código entre procesos con segmentación?

Varios procesos tienen entradas en su tabla que apuntan al mismo segmento físico (misma base y límite).

4. ¿Por qué la compartición requiere que el segmento sea de solo lectura?

Para evitar que un proceso modifique el código o datos compartidos de otro.

5. ¿Qué es SIGSEGV y por qué se llama "falla de segmentación"?

Es la señal que se envía cuando un acceso es inválido. El nombre es histórico, de los sistemas con segmentación.

6. ¿Qué causas típicas provocan SIGSEGV?

Puntero NULL, use-after-free, buffer overflow, escritura en memoria de solo lectura, dirección no mapeada.

7. ¿Cómo se cambian los permisos de una región en Linux?

Con mprotect(), que modifica los permisos de las páginas de una región (tema 42).

8. ¿Qué es la política W^X?

Escribir o ejecutar, pero no ambas: una región no puede ser writable y executable a la vez.

Para practicar

  1. Modifica proteccion_seg.c para añadir un segmento de heap (seg 3) con permisos rw- y límite 0x2000.
  2. Evalúa los accesos (3, 0x1FFF, 'w'), (3, 0x2000, 'r') y (0, 0x0000, 'w') con el simulador.
  3. Explica por qué la compartición de código ahorra memoria en un sistema con muchos procesos.
  4. Investiga cómo implementa Linux la compartición de bibliotecas (.so) con mmap.
  5. Usa mprotect para cambiar una región a solo lectura y verifica que escribir en ella provoca SIGSEGV.
  6. Explica la diferencia entre una SIGSEGV por desplazamiento fuera de rango y una por permiso denegado.
  7. Dibuja la tabla de segmentos de un proceso con código, datos y pila, y marca los permisos de cada uno.