Organización del espacio de direcciones lógicas del kernel de Linux

8

De acuerdo con "Write Great Code" en casi todos los SO, la memoria de tiempo de ejecución está organizada en las siguientes regiones:

OS | Pila | Montón | Texto | Estática | Almacenamiento / BSS

[En forma creciente de direcciones]

El proceso de espacio de usuario utiliza una región de memoria más alta para sus diferentes tipos de objetos de datos.

El proceso de espacio del kernel también tiene diferentes tipos de objetos de datos. ¿Estos objetos comparten las regiones de memoria del espacio del usuario (pila, montón, etc.) o tienen sus propias subsecciones separadas (montón, pila, etc.) ubicadas en la región del sistema operativo? Y, en caso afirmativo, cuál es el orden en que están organizadas . Gracias,

gkt
fuente

Respuestas:

5

Está mal acerca del pedido. El sistema operativo se encuentra en la parte superior de la memoria, que generalmente está por encima de la marca de 3 GB (0xC0000000) en el núcleo de 32 bits, y en el núcleo de 64 bits es el punto medio de 0x8000000000000000 IIRC.

La ubicación de la pila y el montón son aleatorios. No existe una regla real sobre el orden de los segmentos de texto / datos / bss dentro del programa principal, y cada biblioteca dinámica tiene su propio conjunto de estos, por lo que hay muchos de ellos dispersos por toda la memoria.

Cuando los dinosaurios gobernaban la tierra (hace más de 20 años), el espacio de direcciones del programa era lineal (sin agujeros) y el orden era texto, datos, bss, pila, montón. Tampoco había bibliotecas dinámicas o subprocesos en aquel entonces. Todo eso cambió con la memoria virtual.

Los procesos del núcleo están completamente contenidos dentro de la parte del núcleo del espacio de direcciones; la porción del usuario se ignora. Esto permite que el kernel acelere el cambio de contexto entre los hilos del kernel porque no tiene que actualizar las tablas de páginas, ya que todos los procesos comparten la misma porción de kernel de las tablas de páginas.

psusi
fuente
4

Esto no es cierto para "casi todos los sistemas operativos". Los tipos de áreas de memoria representados son bastante típicos, pero no hay ninguna razón por la que deberían estar en un orden particular, y puede haber más de una pieza de un tipo determinado.

En Linux, puede ver el espacio de direcciones de un proceso con cat /proc/$pid/mapsdónde $pidestá la ID del proceso, por ejemplo, cat /proc/$$/mapspara ver el shell desde el que se está ejecutando cato cat /proc/self/mapspara ver las catasignaciones propias del proceso. El comando pmapproduce una salida ligeramente más agradable.

08048000-08054000 r-xp 00000000 08:01 828061     /bin/cat
08054000-08055000 r--p 0000b000 08:01 828061     /bin/cat
08055000-08056000 rw-p 0000c000 08:01 828061     /bin/cat
08c7f000-08ca0000 rw-p 00000000 00:00 0          [heap]
b755a000-b7599000 r--p 00000000 08:01 273200     /usr/lib/locale/en_US.utf8/LC_CTYPE
b7599000-b759a000 rw-p 00000000 00:00 0 
b759a000-b76ed000 r-xp 00000000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76ed000-b76ee000 ---p 00153000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76ee000-b76f0000 r--p 00153000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76f0000-b76f1000 rw-p 00155000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76f1000-b76f4000 rw-p 00000000 00:00 0 
b770b000-b7712000 r--s 00000000 08:01 271618     /usr/lib/gconv/gconv-modules.cache
b7712000-b7714000 rw-p 00000000 00:00 0 
b7714000-b7715000 r-xp 00000000 00:00 0          [vdso]
b7715000-b7730000 r-xp 00000000 08:01 263049     /lib/ld-2.11.1.so
b7730000-b7731000 r--p 0001a000 08:01 263049     /lib/ld-2.11.1.so
b7731000-b7732000 rw-p 0001b000 08:01 263049     /lib/ld-2.11.1.so
bfbec000-bfc01000 rw-p 00000000 00:00 0          [stack]

Puede ver el código y los datos de lectura-escritura (texto y BSS) del ejecutable, luego el montón, luego un archivo mapeado en memoria, luego un poco más de datos de lectura-escritura, luego código, datos de solo lectura y lectura- escribe datos de una biblioteca compartida (texto y BSS nuevamente), más datos de lectura-escritura, otra biblioteca compartida (más precisamente, el enlazador dinámico) y finalmente la pila del único hilo.

El código del kernel usa sus propios rangos de direcciones. En muchas plataformas, Linux usa la parte superior del espacio de direcciones para el núcleo, a menudo el 1GB superior. Idealmente, este espacio sería suficiente para mapear el código del kernel, los datos del kernel y la memoria del sistema (RAM) y cada dispositivo mapeado en memoria. En las PC típicas de 32 bits de hoy en día, esto no es posible, lo que requiere contorsiones que solo son de interés para los piratas informáticos del kernel.

Mientras el código del kernel maneja una llamada del sistema, idealmente (cuando las contorsiones antes mencionadas no están en su lugar) la memoria del proceso se asigna a las mismas direcciones. Esto permite que los procesos pasen datos al núcleo, y el núcleo puede leer directamente desde el puntero. Sin embargo, no es una gran ganancia, ya que los punteros deben ser validados de todos modos (para que el proceso no pueda engañar al núcleo para que lea desde la memoria a la que no se supone que el proceso tenga acceso).

Las zonas de memoria dentro del espacio del kernel de Linux son bastante complejas. Hay varios grupos de memoria diferentes, y las principales distinciones no se refieren a la procedencia de la memoria sino a con quién se comparte. Si tiene curiosidad sobre ellos, comience con LDD3 .

Gilles 'SO- deja de ser malvado'
fuente
1

No es una respuesta, sino un FYI que necesita más espacio.

No creo que su concepción del diseño lógico de direcciones sea correcta.

Puede compilar y ejecutar este programa para ver qué tiene un proceso de usuario para direcciones:

#include <stdio.h>
long global_initialized = 119234;
long global_uninitialized;
extern int _end, _edata, _etext;
int
main(int ac, char **av)
{
        long local;

        printf("main at 0x%lx\n", main);
        printf("ac at   0x%lx\n", &ac);
        printf("av at   0x%lx\n", &av);
        printf("av has  0x%lx\n", av);
        printf("initialized global at 0x%lx\n", &global_initialized);
        printf("global at             0x%lx\n", &global_uninitialized);
        printf("local at              0x%lx\n", &local);
        printf("_end at               0x%lx\n", &_end);
        printf("_edata at             0x%lx\n", &_edata);
        printf("_etext at             0x%lx\n", &_etext);
        return 0;
}

El servidor de Red Hat Enterprise que estoy ejecutando, tiene readelf, que se puede usar para decir dónde cargaría el núcleo (lógicamente) un archivo ejecutable:

readelf -S where

Me muestra mucha de la misma información de direccionamiento que la salida de whereda.

No creo readelfque funcione fácilmente en un kernel de Linux (/ boot / vmlinuz o algo así), y creo que el kernel por defecto comienza en 0x80000000 en su propio espacio de direcciones: no está mapeado en un proceso de usuario, a pesar de usar un dirección por encima de la parte superior de la pila del usuario en 0x7fffffff (x86, direccionamiento de 32 bits).

Bruce Ediger
fuente
Gracias por el ejemplo! Solo mi nota sobre la parte de Linux: acabo de probar este ejemplo como where.c, en Ubuntu 11.04 usando gcc where.c -o where; informa "principal en 0x80483c4". Intenté readelf -S where, e informa, di "[13] .text PROGBITS 08048310 ..." que parece correcto? Aunque también obtengo "ac at 0xbfb035a0" y "local at 0xbfb0358c", y ese rango de direcciones (0xbf ...) parece no ser informado por readelf -S.
sdaau
@sdaau: los argumentos acy avla variable automática localprobablemente tendrán diferentes direcciones en cada invocación. La mayoría de los núcleos modernos de Linux tienen "Aleatorización del diseño del espacio de direcciones" para dificultar la explotación de los desbordamientos del búfer.
Bruce Ediger