Hlavní navigace

Kompilujeme ze zdrojového kódu - knihovny

19. 8. 2002
Doba čtení: 9 minut

Sdílet

Knihovny jsou univerzální programátorskou strukturou a dnes se bez nich neobejde prakticky žádný projekt. A protože náš seriál je o kompilaci, budeme mluvit o dynamických a statických knihovnách, jejich vlastnostech a správě v systému.

Knihovny existují ve dvou odlišných typech – statické a dynamické.

Statické knihovny

Při kompilaci vzniká jako meziprodukt objektový kód. Je to jakýsi hrubý strojový kód programu. Soubory s ním mívají příponu .o (pokud je připraven k použití ve sdílené knihovně, mívá též příponu .lo) a nelze jej spustit. Odkazy do jiných částí programu totiž nejsou vyhodnoceny a odkazuje se na ně z určitého místa kódu pouze jménem. Objektový kód může být uložen v paměti počítače na libovolném místě – o tom, jak je ho nutné upravit pro konkrétní umístění, informuje relokační tabulka.

K tomu, aby z několika objektových kódů vznikl spustitený program, musí projít procesem, zvaným linkování (spojování). Speciální aplikace – linker – tyto dílčí kódy projde, upraví je pro umístění na konkrétní místo v paměti a vyřeší všechny odkazy – namísto odkazů jménem se na příslušná místa uloží adresy podporogramů.

Prvním typem knihoven, které vzniky, byly prosté archivy objektových kódů, známé též jako statické knihovny. K jejich tvorbě se používá program ar. Program se podobá běžným archivačním programům, umí však navíc uchovat rejstřík symbolů. Generuje ho program ranlib a urychluje práci při linkování. Archiv mívá příponu .a (pokud se jedná o archiv s ladicími informacemi, bývá přípona _g.a, pokud obsahuje profilovací informace, bývá _p.a).

Jeho výhody jsou jasné – zcela přirozeně se včleňuje do procesu kompilace a výsledný kód je identický s kódem linkovaným přímo. Má tedy nulovou režii při spouštění. Z knihovny se použijí pouze funkce, které opravdu potřebujeme. Při vlastním běhu programu již knihovna není potřeba.

Stejně silné jsou však i jeho nevýhody. Objeví-li se chyba v knihovně, nestačí překompilovat knihovnu. Je nutné překompilovat také všechny aplikace, které chybnou funkci používají.

Pokud spouštíme zároveň více programů, které používají tytéž knihovní funkce, neexistuje žádný způsob, jak zajistit, aby mohl být jejich obraz v paměti počítače sdílen. V době dávných textových aplikací to nebyl velký problém, ale s příchodem grafických aplikaci se paměťové nároky při současném spouštění více programů stávaly neúnosné.

Musel tedy přijít zcela nový způsob používání knihoven. Tím se staly dynamické knihovny.

Dynamické knihovny

Dynamické knihovny jsou vytvořeny jiným způsobem. Linker objektové kódy předzpracuje tak, aby mohly být s co nejmenší režií připojeny ke kódu programu až za chodu. To znamená především předvyhodnocení odkazů do jiných objektových kódů a knihoven. Při spouštění programu je pak knihovna umístěna do sdílené paměti, kde se o ni podělí všechny programy, které ji potřebují. Proto se těmto knihovnám také říká sdílené knihovny.

Podporu pro sdílené knihovny musí obsahovat již samotné jádro systému. Opravdoví linuxoví pamětníci možná ještě vzpomenou na formáty a.out. V nich byly dynamické knihovny kompilovány pro určité umístění v paměti, a to musely dodržet všechny programy. Takový systém měl sice malou režii při spouštění, ale s růstem operačních systémů i velikostí vlastních knihoven se stalo čím dál tím obtížnější udržovat systém nikdy se nepřekrývajících adresových prostorů knihoven.

Proto přišel zcela nový systém – ELF (Executable and Linkable Format). Ten usnadnil používání dynamických knihoven. Novým prvkem je možnost, aby tatáž sdílená knihovna byla ve virtuálním paměťovém prostoru různých aplikaci umístěna na různých adresách. Musí však být k tomuto účelu speciálně zkompilována jako tzv. pozičně nezávislý kód (position-independent code).

ELF je pro dynamické knihovny výhodný formát. Má však také své nevýhody – je to především velká režie při spouštění, zvlášť u aplikací, které se skládají z mnoha knihoven a obsahují mnoho exportovaných symbolů (KDE, Mozilla, Gnome). Tuto režii se pokoušejí snížit projekty Prelink a Objprelink.

Další nevýhodou je menší efektivita pozičně nezávislého kódu na některých platformách.

Tím se dostáváme k nevýhodám sdílených knihoven. Potřebujeme-li jednu funkci z obrovské knihovny, musíme bohužel přilinkovat celou knihovnu. Ani to ještě není všechno. Potřebuje-li některá funkce ve sdílené knihovně A ke své činnosti jinou sdílenou knihovnu B, musíme na mnoha platformách (Linux k nim patří) tuto knihovnu přilinkovat i ke všem aplikacím, používajícím knihovnu A, byť je pro námi potřebnou funkci zbytečná (linker při kompilaci neověřuje, které knihovny opravdu potřebujeme, a důvěřuje argumentům na příkazovém řádku).

Jakou knihovnu použít?

Z tohoto malého přehledu lze odhadnout, kdy je který formát knihovny výhodnější:

Statické knihovny použijeme jako vnitřní, pracovní a pomocné knihovny při vytváření aplikací. Stejně tak jsou vhodné pro velké knihovny nesourodých prvků (knihovny neobvyklých matematických a statistických funkcí). Dynamické knihovny také nemají žádné opodstatnění, pokud by je měla používat pouze jediná aplikace. Naproti tomu tam, kde tutéž složitou knihovnu použije mnoho různých aplikací (grafické aplikace), je dynamická knihovna nanejvýš vhodná.

Moduly

Poslední, trochu atypickou skupinou knihoven, jsou dynamicky nahrávané moduly. Ty se používají tam, kde jsou podobné požadavky, jaké bývají na statické knihovny, ale o použití konkrétní funkce se rozhoduje až za běhu aplikace. Proto každý modul obsahuje vždy pouze podporu omezeného rozsahu funkcí. Moduly nejsou nijak standardizované a způsob použití se liší podle aplikace. Používají je např. jádro Linuxu, GNU C Library (pro národní prostředí a správu hesel), XFree86 (verze 4 pro hardwarové a jiné ovladače) a importní, exportní a funkční moduly mnoha programů (např. Gimp).

Manipulace s knihovnami

Je vidět, že manipulace s knihovnami není nijak jednoduchá a je navíc silně platformně závislá. Proto vznikl projekt libtool. Ten nabízí jednotné rozhraní pro vytváření a instalaci obou typů knihoven na všech platformách. Používá soubory s příponou .la, ve kterých si ukládá informace pro linkování při kompilaci.

Pokud je k dispozici jak statická, tak dynamická verze knihovny, kompilátor dává přednost dynamické verzi.

Základní knihovny

Nejzákladnější dynamickou knihovnou je dynamický linker (ld.so). Ten se stará (ve spolupráci s jádrem) o veškerou režii dynamických knihoven. S běžnými sdílenými knihovnami lze s jeho pomocí pracovat též podobně jako s moduly. Slouží k tomu speciální proměnné dynamického linkeru. Z nich je nejznámější LD_PRELOAD, která vnutí nahrátí zadané knihovny a použití jejích funkcí, a to dokonce na úkor odpovídajících funkcí ze standardně linkovaných knihoven. Více najdete v dokumentaci ld.so(8).

Základní knihovnou všech GNU-Linuxových systémů je GNU C Library (glibc). Je to knihovna základních funkcí, používaných aplikacemi v jazyce C. Je přilinkována k naprosté většině programů jako dynamická knihovna (jejím specifikem je, že několik funkcí se v její sdílené podobě nenachází a je připojováno vždy staticky při kompilaci z knihovny libc_nonshared.a). GNU C Library se stará o práci se soubory, pamětí, textovými řetězci, o komunikaci mezi aplikacemi, národní prostředí a další. Její součástí je též knihovna základních matematických funkcí libm.

Podobně existuje knihovna libstdc++, kterou (vedle glibc) používají všechny aplikace psané v C++.

Další knihovny se připojují k programům podle potřeby.

Verze knihoven a nekompatibility

Zatímco u statických knihoven nepotřebujeme pro chod systému žádnou nainstalovanou knihovnu a pro kompilaci potřebujeme jen tu poslední verzi, u dynamických knihoven je situace složitější.

Knihovny se postupně vyvíjejí a vyvíjí se i jejich rozhraní. Při větších změnách by se snadno mohlo stát, že nově nainstalovaná knihovna způsobí náhlou nefunkčnost aplikací zkompilovaných s její předchozí verzí (jak to jistě znají uživatelé některých operačních systémů). Druhým extrémem by byla situace, kdyby dříve zkompilované aplikace odmítaly použít novou verzi knihovny s opravou. Proto byla vytvořena pravidla pro jména sdílených objektů (shared object name – soname) a číslování verzí knihoven.

Každá dynamická knihovna v GNU-Linuxu by měla obsahovat (ale nemusí) jméno sdíleného objektu. Při kompilaci se toto jméno uloží do spustitelné aplikace, a ta pak žádá knihovnu se stejným sdíleným jménem.

Čísla knihoven bývají vícedílná. První číslo je i součástí jména sdíleného objektu, další čísla pak již udávají pouze dílčí verzi. Pokud se v knihovně provede zásadní změna, je změněno i jméno sdíleného objektu. Všechny dříve zkompilované aplikace budou nadále (až do rekompilace) používat starší verzi knihovny. Pokud je změna dílčí, provede se pouze oprava dílčích čísel a dříve zkompilované aplikace automaticky použijí novou verzi.

Detailní pravidla pro přidělování čísel verzí najdeme v dokumentaci k programu libtool.

Pro správu celé této struktury slouží program ldconfig. Ten projde dostupné knihovny a postará se o to, aby symbolický odkaz stejnojmenný se jménem sdíleného objektu ukazoval vždy na poslední z použitelných verzí knihovny. Odkaz bez označení verze pak bývá k dispozici pro kompilátor.

Předvedeme si to na malé ukázce:

libstdc++.la
libstdc++.so -> libstdc++.so.4.0.0
libstdc++.so.3 -> libstdc++.so.3.0.4
libstdc++.so.3.0.3
libstdc++.so.3.0.4
libstdc++.so.4 -> libstdc++.so.4.0.0
libstdc++.so.4.0.0

Programy zkompilované v době, kdy poslední verzí byla libstdc++.so.3.0.3, použily jméno sdíleného objektu

libstdc++.so.3. Když byla nainstalována nová verze libstdc++.so.3.0.4, všechny programy ji okamžitě začaly používat. Soubor libstdc++.so.3­.0.3 je tedy možné smazat.

Jiná situace nastala, když byla nainstalována libstdc++.so.4­.0.0. Je binárně nekompatibilní a používá nové jméno sdíleného objektu – libstdc++.so.4. Dříve zkompilované programy budou tedy nadále používat libstdc++.so.3.0.4.

Je třeba upozornit, že vývojáři občas udělají chybu a vydají nekompatibilní verzi se stejným jménem sdíleného objektu (příklady z poslední doby: imlib, libpng). Se smazáním starší verze tedy vyčkejte až do důkladného otestování.

Jiní vývojáři dělají opačnou chybu – každou verzi knihovny vydávají s novým jménem sdíleného objektu (nebo jméno sdíleného objektu zcela chybí, a pak je vyžadována přesná shoda jména souboru s knihovnou). V takové situaci můžeme starou verzi knihovny nahradit symbolickým odkazem na novou verzi. Obecně je však tento postup jistou cestou k nestabilitě.

Knihovny, hlavičkové soubory a devel balíky

Zatímco sdílená knihovna je potřebná při běhu aplikací, hlavičkové soubory, popisující funkce dostupné v knihovně, a statickou knihovnu potřebujeme pouze při kompilaci. Proto správci balíčků většinou dělí knihovny do dvou podbalíčků – vlastní dynamická knihovna a devel balík. Ten obsahuje hlavičkové soubory a případnou statickou nebo ladicí verzi knihovny.

Závislosti

Z praktického hlediska se knihovny dělí na ty, které jsou součástí projektu, a ty, které projekt vyžaduje nainstalovány v systému. Jedná-li se o statické knihovny a hlavičkové soubory, jsou nutné pouze při kompilaci. Jde-li o sdílené knihovny, musí být v systému přítomny i pro běh aplikace, a tak vznikají známé a neoblíbené závislosti. Jejich tabulka definuje, které knihovny (a jaké jejich verze) potřebuje aplikace či balík k běhu.

Závislosti dané aplikace lze ověřit příkazem ldd. Na příkladu programu /sbin/cfdisk vidíme, že kromě standardních knihoven vyžaduje pouze knihovnu ncurses. Složité grafické aplikace však často závisejí na desítkách knihoven.

libncurses.so.5 => /lib/libncurses.so.5 (0x46001000)
libc.so.6 => /lib/libc.so.6 (0x45193000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x437e3000)

Od závislostí programů se odvíjejí i závislosti distribučních balíků, které musí pokrýt všechny potřebné knihovny.

Knihovny a configure

Protože volba knihoven je tak důležitá, má pro ni skript configure standardní argumenty:
 –enable-shared povolí tvorbu dynamických knihoven
 –enable-static povolí tvorbu statických knihoven (implicitní)
 –disable-shared zakáže tvorbu dynamických knihoven (implicitní)
 –disable-static zakáže tvorbu statických knihoven

Ukázky

Nejjednodušší je vytvořit objektový kód:

gcc program.c -o program.o
gcc program2.c -o program2.o

Z něj vytvoříme statickou knihovnu:

ar cru libprogram.a program.o program2.o
ranlib libprogram.a

Jen nepatrně složitější je vytvořit objektový kód pro sdílenou knihovnu:

gcc -fPIC program.c -o program.lo
gcc -fPIC program2.c -o program2.lo

A vlastní knihovnu:

gcc -shared  program.lo program2.lo\
 -Wl,-soname -Wl,libprogram.so.0 -o libprogram.so.0.0

Nebo přímo pomocí linkeru:

UX DAy - tip 2

ld -shared -soname libprogram.so.0\
 -o libprogram.so.0.0 program.lo program2.lo

A tuto knihovnu pak můžeme použít v dalším programu:

gcc program3.c -o program3 -lprogram

Je však lépe vše svěřit programu libtool, který vytvoří knihovnu pro libovolnou platformu. O tom však až někdy příště…

Byl pro vás článek přínosný?