Organizzazione dello spazio degli indirizzi logici del kernel Linux


8

Secondo "Write Great Code" in quasi tutti i sistemi operativi la memoria di runtime è organizzata nelle seguenti regioni:

OS | Stack | Heap | Testo | Statico | Stoccaggio / BSS

[In modo crescente indirizzo]

Il processo dello spazio utente utilizza un'area di memoria superiore per i suoi diversi tipi di oggetti dati.

Il processo dello spazio kernel ha anche diversi tipi di oggetti dati. Questi oggetti condividono le aree di memoria dello spazio utente (stack, heap ecc.) O hanno le loro sottosezioni separate (heap, stack ecc.) Situate nella regione del sistema operativo. In tal caso, qual è l'ordine in cui sono disposti . Grazie,

Risposte:


5

È sbagliato sull'ordinamento. Il sistema operativo si trova nella parte superiore della memoria, che è generalmente sopra il segno di 3 GB (0xC0000000) nel kernel a 32 bit, e nel kernel a 64 bit è il punto a metà strada di 0x800000000000000000 IIRC.

La posizione dello stack e dell'heap sono casuali. Non esiste una regola reale sull'ordinamento dei segmenti text / data / bss all'interno del programma principale, e ogni libreria dinamica ne ha il proprio set, quindi ce ne sono molti sparsi in tutta la memoria.

Ai tempi in cui i dinosauri governavano la terra (oltre 20 anni fa), lo spazio degli indirizzi del programma era lineare (senza buchi) e l'ordine era testo, dati, bss, stack, heap. All'epoca non c'erano librerie dinamiche o threading. Tutto è cambiato con la memoria virtuale.

I processi del kernel sono interamente contenuti nella parte del kernel dello spazio degli indirizzi; la parte dell'utente viene ignorata. Ciò consente al kernel di accelerare il cambio di contesto tra i thread del kernel perché non è necessario aggiornare le tabelle di pagine poiché tutti i processi condividono la stessa porzione di kernel delle tabelle di pagine.


4

Questo non è vero per "quasi tutti i sistemi operativi". I tipi di aree di memoria rappresentate sono abbastanza tipici, ma non c'è motivo per cui debbano essere in un ordine particolare e può esserci più di un pezzo di un determinato tipo.

Sotto Linux, puoi guardare lo spazio degli indirizzi di un processo con cat /proc/$pid/mapsdove si $pidtrova l'ID del processo, ad esempio cat /proc/$$/mapsper guardare la shell da cui stai eseguendo cato cat /proc/self/mapsper esaminare i catmapping propri del processo. Il comando pmapproduce un output leggermente più gradevole.

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]

Puoi vedere il codice e i dati di lettura-scrittura (testo e BSS) dall'eseguibile, quindi l'heap, quindi un file mappato in memoria, quindi un po 'più di dati di lettura-scrittura, quindi codice, dati di sola lettura e lettura- scrivere dati da una libreria condivisa (di nuovo testo e BSS), più dati di lettura-scrittura, un'altra libreria condivisa (più precisamente, il linker dinamico) e infine lo stack del thread unico.

Il codice del kernel utilizza i propri intervalli di indirizzi. Su molte piattaforme, Linux utilizza la parte superiore dello spazio degli indirizzi per il kernel, spesso la parte superiore da 1 GB. Idealmente, questo spazio sarebbe sufficiente per mappare il codice del kernel, i dati del kernel e la memoria di sistema (RAM) e ogni dispositivo mappato in memoria. Sui tipici PC a 32 bit di oggi, questo non è possibile, il che richiede contorsioni che interessano solo gli hacker del kernel.

Mentre il codice del kernel gestisce una chiamata di sistema, idealmente (quando le suddette contorsioni non sono presenti) la memoria del processo è mappata agli stessi indirizzi. Ciò consente ai processi di passare i dati al kernel e il kernel può leggere direttamente dal puntatore. Non è un grande vantaggio, tuttavia, poiché i puntatori devono comunque essere convalidati (in modo che il processo non possa indurre il kernel a leggere dalla memoria a cui il processo non dovrebbe avere accesso).

Le zone di memoria all'interno dello spazio del kernel Linux sono piuttosto complesse. Esistono diversi pool di memoria e le principali distinzioni non riguardano l'origine della memoria, ma piuttosto con chi è condivisa. Se sei curioso di conoscerli, inizia con LDD3 .


1

Non una risposta, ma una FYI che ha bisogno di più spazio.

Non penso che la tua concezione del layout dell'indirizzo logico sia corretta.

Puoi compilare ed eseguire questo programma per vedere cosa ha un processo userland per gli indirizzi:

#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;
}

Il Red Hat Enterprise Server che ho in esecuzione ha readelf, che può essere usato per dire dove il kernel carica (logicamente) un file eseguibile:

readelf -S where

Mi mostra molte delle stesse informazioni di indirizzamento fornite dall'output di where.

Non penso che readelffunzionerà facilmente su un kernel Linux (/ boot / vmlinuz o qualcosa del genere), e penso che il kernel di default inizi a 0x80000000 nel suo spazio di indirizzi: non è mappato in un processo userland, nonostante usi un indirizzo sopra la parte superiore dello stack di userland a 0x7fffffff (x86, indirizzamento a 32 bit).


Grazie per l'esempio! Solo la mia nota sulla parte Linux - ho appena provato questo esempio come where.c, usando Ubuntu 11.04 gcc where.c -o where; riporta "principale a 0x80483c4". Ho provato readelf -S where, e riporta, dire "[13] .text PROGBITS 08048310 ..." che sembra giusto? Anche se ottengo anche "ac a 0xbfb035a0" e "local a 0xbfb0358c" e l'intervallo di indirizzi (0xbf ...) sembra non essere segnalato da readelf -S.
sdaau,

@sdaau - Gli argomenti ace avla variabile automatica localavranno probabilmente indirizzi diversi su ogni invocazione. La maggior parte dei kernel Linux moderni ha "Randomizzazione del layout dello spazio degli indirizzi" per rendere più difficile lo sfruttamento degli overflow del buffer.
Bruce Ediger,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.