Caricamento di librerie condivise e utilizzo della RAM


41

Mi chiedo come Linux gestisce le librerie condivise. (attualmente sto parlando di Maemo Fremantle, una distro basata su Debian rilasciata nel 2009 con 256 MB di RAM).

Supponiamo di avere due eseguibili che si collegano a libQtCore.so.4 e usano i suoi simboli (usando le sue classi e funzioni). Per semplicità, chiamiamoli ae b. Partiamo dal presupposto che entrambi gli eseguibili si collegano alle stesse librerie.

Per prima cosa lanciamo a. La libreria deve essere caricata. È caricato interamente o è caricato nella memoria solo nella parte richiesta (poiché non utilizziamo ogni classe, viene caricato solo il codice relativo alle classi utilizzate)?

Quindi lanciamo b. Partiamo dal presupposto che aè ancora in esecuzione. bsi collega anche a libQtCore.so.4 e utilizza alcune delle classi che autilizza, ma anche alcune non utilizzate da a. La libreria verrà caricata due volte (separatamente per ae separatamente per b)? O useranno lo stesso oggetto già nella RAM. Se bnon utilizza nuovi simboli ed aè già in esecuzione, aumenterà la RAM utilizzata dalle librerie condivise? (O la differenza sarà insignificante)

Risposte:


54

NOTA: suppongo che la macchina disponga di un'unità di mappatura della memoria (MMU). Esiste una versione di Linux (µClinux) che non richiede una MMU e questa risposta non si applica qui.

Che cos'è un MMU? È hardware, parte del processore e / o controller di memoria. Comprendere il collegamento di librerie condivise non richiede di capire esattamente come funziona un MMU, ma solo che un MMU consente di distinguere gli indirizzi di memoria logica (quelli utilizzati dai programmi) e quelli fisiciindirizzi di memoria (quelli effettivamente presenti sul bus di memoria). La memoria è suddivisa in pagine, in genere di dimensioni 4K su Linux. Con 4k pagine, gli indirizzi logici 0–4095 sono la pagina 0, gli indirizzi logici 4096–8191 sono la pagina 1, ecc. La MMU esegue il mapping a pagine fisiche di RAM e ogni pagina logica può in genere essere mappata su 0 o 1 pagine fisiche. Una determinata pagina fisica può corrispondere a più pagine logiche (è così che la memoria è condivisa: più pagine logiche corrispondono alla stessa pagina fisica). Nota che questo vale indipendentemente dal sistema operativo; è una descrizione dell'hardware.

Al passaggio al processo, il kernel modifica i mapping della pagina MMU, in modo che ogni processo abbia il suo spazio. L'indirizzo 4096 nel processo 1000 può essere (e di solito è) completamente diverso dall'indirizzo 4096 nel processo 1001.

Praticamente ogni volta che vedi un indirizzo, è un indirizzo logico. I programmi di spazio utente quasi mai gestiscono indirizzi fisici.

Ora, ci sono anche diversi modi per creare librerie. Diciamo che un programma chiama la funzione foo()nella libreria. La CPU non sa davvero nulla dei simboli o delle chiamate di funzione, sa solo come saltare a un indirizzo logico ed eseguire qualsiasi codice che trovi lì. Ci sono un paio di modi per farlo (e cose simili si applicano quando una libreria accede ai propri dati globali, ecc.):

  1. Potrebbe codificare un indirizzo logico per chiamarlo. Ciò richiede che la libreria sia sempre caricata nello stesso indirizzo logico esatto. Se due librerie richiedono lo stesso indirizzo, il collegamento dinamico non riesce e non è possibile avviare il programma. Le librerie possono richiedere altre librerie, quindi in pratica ciò richiede che tutte le librerie sul sistema dispongano di indirizzi logici univoci. È molto veloce, tuttavia, se funziona. (Ecco come hanno fatto le cose, e il tipo di installazione che fa il prelinking, in un certo senso).
  2. Potrebbe codificare un indirizzo logico falso e dire al linker dinamico di modificare quello corretto durante il caricamento della libreria. Questo richiede un bel po 'di tempo quando si caricano le librerie, ma dopo è molto veloce.
  3. Potrebbe aggiungere un livello di riferimento indiretto: utilizzare un registro CPU per contenere l'indirizzo logico in cui è caricata la libreria, quindi accedere a tutto come offset da quel registro. Ciò impone un costo prestazionale su ciascun accesso.

Praticamente nessuno usa più il numero 1, almeno non su sistemi di uso generale. Mantenere quell'elenco di indirizzi logici univoco è impossibile sui sistemi a 32 bit (non ce ne sono abbastanza per andare in giro) e un incubo amministrativo sui sistemi a 64 bit. Una sorta di pre-collegamento lo fa, tuttavia, per ogni sistema.

L'utilizzo del n. 2 o del n. 3 dipende dal fatto che la libreria sia stata creata con l' -fPICopzione GCC (codice indipendente dalla posizione). # 2 è senza, # 3 è con. Generalmente, le librerie sono costruite con -fPIC, quindi # 3 è ciò che accade.

Per maggiori dettagli, vedere Come scrivere librerie condivise di Ulrich Drepper (PDF) .

Quindi, finalmente, puoi rispondere alla tua domanda:

  1. Se la libreria è costruita con -fPIC (come quasi certamente dovrebbe essere), la stragrande maggioranza delle pagine è esattamente la stessa per ogni processo che la carica. I tuoi processi ae bpotrebbero anche caricare la libreria su indirizzi logici diversi, ma quelli punteranno alle stesse pagine fisiche: la memoria sarà condivisa. Inoltre, i dati nella RAM corrispondono esattamente a ciò che è sul disco, quindi possono essere caricati solo quando necessario dal gestore degli errori di pagina.
  2. Se la libreria è costruita senza -fPIC , allora risulta che la maggior parte delle pagine della libreria avrà bisogno di modifiche ai collegamenti e sarà diversa. Pertanto, devono essere pagine fisiche separate (in quanto contengono dati diversi). Ciò significa che non sono condivisi. Le pagine non corrispondono a ciò che è sul disco, quindi non sarei sorpreso se l'intera libreria fosse caricata. Naturalmente può successivamente essere scambiato su disco (nel file di scambio).

Puoi esaminarlo con lo pmapstrumento o direttamente controllando vari file in /proc. Ad esempio, ecco un output (parziale) di pmap -xdue diversi bcs appena generati . Si noti che gli indirizzi mostrati da pmap sono, come tipico, indirizzi logici:

pmap -x 14739
Address           Kbytes     RSS   Dirty Mode  Mapping
00007f81803ac000     244     176       0 r-x-- libreadline.so.6.2
00007f81803e9000    2048       0       0 ----- libreadline.so.6.2
00007f81805e9000       8       8       8 r---- libreadline.so.6.2
00007f81805eb000      24      24      24 rw--- libreadline.so.6.2


pmap -x 17739
Address           Kbytes     RSS   Dirty Mode  Mapping
00007f784dc77000     244     176       0 r-x-- libreadline.so.6.2
00007f784dcb4000    2048       0       0 ----- libreadline.so.6.2
00007f784deb4000       8       8       8 r---- libreadline.so.6.2
00007f784deb6000      24      24      24 rw--- libreadline.so.6.2

Puoi vedere che la libreria è caricata in più parti e pmap -xti fornisce dettagli su ciascuna separatamente. Noterai che gli indirizzi logici sono diversi tra i due processi; ti aspetteresti ragionevolmente che siano uguali (dato che è lo stesso programma in esecuzione e i computer sono generalmente prevedibili in quel modo), ma esiste una funzionalità di sicurezza chiamata randomizzazione del layout dello spazio degli indirizzi che li randomizza intenzionalmente.

Dalla differenza di dimensioni (Kbyte) e dimensioni residenti (RSS) è possibile notare che l'intero segmento della libreria non è stato caricato. Infine, puoi vedere che per i mapping più grandi, dirty è 0, il che significa che corrisponde esattamente a ciò che è sul disco.

Puoi rieseguire pmap -XX, e ti mostrerà - a seconda della versione del kernel che stai eseguendo, poiché l'output -XX varia in base alla versione del kernel - che il primo mapping ha un Shared_Clean176, che corrisponde esattamente al RSS. Sharedmemoria significa che le pagine fisiche sono condivise tra più processi e, poiché corrisponde all'RSS, ciò significa che tutta la libreria in memoria è condivisa (vedere Vedi anche sotto per ulteriori spiegazioni su condiviso vs. privato):

pmap -XX 17739
         Address Perm   Offset Device   Inode  Size  Rss Pss Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous AnonHugePages Swap KernelPageSize MMUPageSize Locked                   VmFlagsMapping
    7f784dc77000 r-xp 00000000  fd:00 1837043   244  176  19          176            0             0             0        176         0             0    0              4           4      0       rd ex mr mw me sd  libreadline.so.6.2
    7f784dcb4000 ---p 0003d000  fd:00 1837043  2048    0   0            0            0             0             0          0         0             0    0              4           4      0             mr mw me sd  libreadline.so.6.2
    7f784deb4000 r--p 0003d000  fd:00 1837043     8    8   8            0            0             0             8          8         8             0    0              4           4      0       rd mr mw me ac sd  libreadline.so.6.2
    7f784deb6000 rw-p 0003f000  fd:00 1837043    24   24  24            0            0             0            24         24        24             0    0              4           4      0    rd wr mr mw me ac sd  libreadline.so.6.2


Guarda anche


Ciò significa che il prelinking non serve più (e che l' -fPICutilizzo è completamente cambiato qualche tempo fa)?
Hauke ​​Laging,

@crisron Grazie per le correzioni. Cordiali saluti, Markdown conta per te: l'output renderizzato del mio ripetuto 1. era corretto. Inoltre, ho apportato alcune modifiche a ciò che hai fatto: "indirizzo iniziale" è un gergo tecnico, probabilmente ho causato confusione mettendo "logico" nel mezzo. L'ho cambiato per sbarazzarmi del gergo. Inoltre, le pagine sono equivalenti a quegli indirizzi, AFAIK non è possibile che quegli indirizzi siano mai una pagina diversa. Ho provato di nuovo, scambiando l'ordine, speriamo che sia più chiaro.
derobert,

accidenti, questa è una risposta !!!
Evan Carroll,
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.