Zajímalo by mě, jak Linux spravuje sdílené knihovny. (ve skutečnosti mluvím o Maemo Fremantle, distribuci založené na Debianu vydaném v roce 2009 a běžící na 256 MB RAM).
Předpokládejme, že máme dva spustitelné soubory propojené s libQtCore.so.4 a používající jeho symboly (pomocí jeho tříd a funkcí). Pro jednoduchost jim říkejme a
a b
. Předpokládáme, že oba spustitelné soubory odkazují na stejné knihovny.
Nejprve spustíme a
. Knihovna se musí načíst. Načítá se celá, nebo se do paměti načítá pouze ta část, která je vyžadována (protože nepoužíváme každou třídu, načítá se pouze kód týkající se použitých tříd)?
Poté spustíme b
. Předpokládáme, že a
stále běží. b
také odkazuje na libQtCore.so.4 a používá některé třídy, které a
používá, ale také některé, které a
nepoužívá . Bude knihovna načtena dvakrát (samostatně pro a
a zvlášť pro b
)? Nebo použijí stejný objekt již v RAM. Pokud b
nepoužívá žádné nové symboly a a
již běží, zvýší se RAM používaná sdílenými knihovnami? (Nebo bude rozdíl zanedbatelný)
Přijatá odpověď:
POZNÁMKA:Budu předpokládat, že váš počítač má jednotku mapování paměti (MMU). Existuje linuxová verze (µClinux), která MMU nevyžaduje, a tato odpověď zde neplatí.
Co je MMU? Je to hardware – součást procesoru a/nebo řadiče paměti. Pochopení propojení sdílených knihoven nevyžaduje, abyste přesně rozuměli tomu, jak MMU funguje, pouze MMU umožňuje, aby existoval rozdíl mezi logickými adresy paměti (ty používané programy) a fyzické adresy paměti (ty, které se skutečně nacházejí na paměťové sběrnici). Paměť je rozdělena na stránky, v Linuxu obvykle o velikosti 4K. U 4k stránek jsou logické adresy 0–4095 stránka 0, logické adresy 4096–8191 jsou stránka 1 atd. MMU je mapuje na fyzické stránky paměti RAM a každá logická stránka může být typicky mapována na 0 nebo 1 fyzickou stránku. Daná fyzická stránka může odpovídat více logickým stránkám (takto je sdílena paměť:více logických stránek odpovídá stejné fyzické stránce). Toto platí bez ohledu na OS; je to popis hardwaru.
Při přepínání procesů jádro změní mapování stránek MMU, takže každý proces má svůj vlastní prostor. Adresa 4096 v procesu 1000 může být (a obvykle je) zcela odlišná od adresy 4096 v procesu 1001.
Téměř vždy, když vidíte adresu, je to logická adresa. Programy v uživatelském prostoru se s fyzickými adresami téměř nikdy nezabývají.
Nyní existuje několik způsobů, jak vytvářet knihovny. Řekněme, že program volá funkci foo()
v knihovně. CPU neví nic o symbolech nebo volání funkcí – jen ví, jak skočit na logickou adresu a spustit jakýkoli kód, který tam najde. Existuje několik způsobů, jak to udělat (a podobné věci platí, když knihovna přistupuje ke svým vlastním globálním datům atd.):
- Mohl by napevno zakódovat nějakou logickou adresu, na kterou by to bylo možné volat. To vyžaduje, aby knihovna vždy být načten na přesně stejné logické adrese. Pokud dvě knihovny vyžadují stejnou adresu, dynamické propojení selže a program nelze spustit. Knihovny mohou vyžadovat jiné knihovny, takže to v podstatě vyžaduje, aby každá knihovna v systému měla jedinečné logické adresy. Je to ale velmi rychlé, pokud to funguje. (Takto to dělal a.out a druh nastavení, který dělá předběžné propojení).
- Mohlo by to napevno zakódovat falešnou logickou adresu a říct dynamickému linkeru, aby při načítání knihovny upravil tu správnou. Při načítání knihoven to stojí poměrně dost času, ale poté je to velmi rychlé.
- Mohlo by to přidat vrstvu nepřímosti:použijte registr CPU k udržení logické adresy, na které je knihovna načtena, a pak ke všemu přistupujte jako offset z tohoto registru. To vyžaduje výkonnostní náklady na každý přístup.
#1 už skoro nikdo nepoužívá, alespoň ne na systémech pro všeobecné použití. Udržování tohoto jedinečného seznamu logických adres je nemožné na 32bitových systémech (není jich dostatek) a administrativní noční můra na 64bitových systémech. Pre-linking to však dělá na základě jednotlivých systémů.
Zda se použije #2 nebo #3, závisí na tom, zda byla knihovna vytvořena pomocí GCC -fPIC
(kód nezávislý na pozici) možnost. #2 je bez, #3 je s. Obecně jsou knihovny sestaveny pomocí -fPIC
, takže se stane #3.
Další podrobnosti naleznete v dokumentu Ulrich Drepper’s How to Write Shared Libraries (PDF).
Takže konečně na vaši otázku lze odpovědět:
- Pokud je knihovna postavena s
-fPIC
(jak by to téměř jistě mělo být), naprostá většina stránek je naprosto stejná pro každý proces, který jej načítá. Vaše procesya
ab
může dobře načíst knihovnu na různých logických adresách, ale ty budou ukazovat na stejné fyzické stránky:paměť bude sdílena. Data v paměti RAM se navíc přesně shodují s tím, co je na disku, takže je lze načíst pouze v případě potřeby obslužným programem chyb stránky. - Pokud je knihovna postavena bez
-fPIC
, pak se ukáže, že většina stránek knihovny bude potřebovat úpravy odkazů a bude se lišit. Musí se tedy jednat o samostatné fyzické stránky (protože obsahují různá data). To znamená, že nejsou sdíleny. Stránky neodpovídají tomu, co je na disku, takže bych se nedivil, kdyby byla načtena celá knihovna. Lze jej samozřejmě následně vyměnit na disk (ve swapfile).
Můžete to prozkoumat pomocí pmap
nebo přímo kontrolou různých souborů v /proc
. Zde je například (částečný) výstup pmap -x
na dvou různých nově vytvořených bc
s. Všimněte si, že adresy zobrazené pmap jsou jako typické logické adresy:
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
Můžete vidět, že knihovna je načtena v několika částech, a pmap -x
poskytuje podrobnosti o každém zvlášť. Všimnete si, že logické adresy se mezi těmito dvěma procesy liší; rozumně byste očekávali, že budou stejné (protože běží stejný program a počítače jsou obvykle takto předvídatelné), ale existuje bezpečnostní funkce zvaná randomizace rozvržení adresního prostoru, která je záměrně randomizuje.
Z rozdílu ve velikosti (kbajty) a rezidentní velikosti (RSS) můžete vidět, že nebyl načten celý segment knihovny. Nakonec můžete vidět, že pro větší mapování je dirty 0, což znamená, že přesně odpovídá tomu, co je na disku.
Můžete znovu spustit pomocí pmap -XX
, a ukáže vám – v závislosti na verzi jádra, které používáte, protože výstup -XX se liší podle verze jádra –, že první mapování má Shared_Clean
ze 176, který přesně odpovídá RSS
. Shared
paměť znamená, že fyzické stránky jsou sdíleny mezi více procesy, a protože se shoduje s RSS, znamená to, že je sdílena veškerá knihovna, která je v paměti (další vysvětlení sdílené vs. soukromé naleznete v části Viz také níže):
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
Viz také
- Získání informací o využití paměti procesem z /proc/pid/smaps pro vysvětlení celé čisté/špinavé sdílené/soukromé věci.