9. diel - Céčko a Linux - Statické a dynamické knižnice
V minulej lekcii, Céčko a Linux - getopt_long a shell , sme dokončili filtre. Dnes sa pozrieme na tvorbu a použitie externých knižníc.
Knižnica
Čo to vôbec je? Knižnica je, jednoducho povedané, zbierka funkcií, ktoré môžeme volať z nášho programu.
Iste poznáte štandardné knižnicu C (napr. Štádiách) a najskôr viete, že je možné tvoriť knižnice vlastné. S najväčšou pravdepodobnosťou ste už aj niekedy nejakú vytvorili. Napriek tomu by som sa na danú tému chcel dnes pozrieť bližšie a je pravdepodobné, že sa dozviete aj niečo nové.
Vytvoríme si veľmi jednoduchú knižnicu, ktorá bude obsahovať iba dve funkcie.
libvypocet.c
#include "libvypocet.h" static int mezivypocet(int a); int vypocet(int a, int b) { return mezivypocet(a) + b; } int mezivypocet(int a) { return a*a; }
Následne k nej urobíme rozhrania (hlavičkový súbor), kde budú definované všetky funkcie, ktoré je možné zavolať z iných častí programu.
libvypocet.h
extern int vypocet(int a, int b);
Všimnite si funkcie mezivypocet
, ktorá v hlavičke zapísaná
nie je - túto funkciu môžeme volať iba v rámci našej knižnice. Za zmienku
stojí tiež riadok static int mezivypocet(int a);
. Tej sa hovorí
úplný funkčný vzor (ak sa mýlim, prosím opraviť) a je dobrým zvykom je
napísať na začiatok súboru pre každú funkciu. Umožňujú totiž volať z
funkcií funkcie, ktorých definícia je až za funkcií, z ktorej sa volá. V
zásade hovorí prekladači, čo funkcia vracia a aké má parametre. Ten to
potom môže skontrolovať.
Funkčné vzory poverených funkcií sú vypísané v hlavičkovom súbore a jeho vložením na začiatok našej knižnice si vlastne ušetríme písania.
Tu je náš jednoduchý program, ktorý len zavolá funkciu z našej knižnice a vypíše výsledok na stdout.
main.c
#include <stdio.h> #include "libvypocet.h" int main() { printf("Vysledek vypoctu z knihovny: %d\n", vypocet(5, 5)); return 0; }
Teraz si ukážeme, ako môžeme náš program skompilovať, aby použil nami vytvorenú knižnicu. Je nutné povedať, že existujú dva druhy knižníc - statické a dynamické.
Statické knižnice
Statické knižnice zrejme poznáte. Je to veľmi jednoduchý spôsob, ako rozdeliť program do viacerých modulov a sprehľadniť tak kód. Často sa používa pri projektoch, kde spolupracuje viac programátorov - každý urobí jeden modul (knižnicu), otestuje ho a nadefinuje rozhrania. Moduly sa potom len spoja a aplikácie je hotová.
Z technického hľadiska je statická knižnica priamo súčasť binárky. S každou úpravou knižnice musíme program znovu zostaviť.
Samotná postupnosť príkazov je pre statické knižnice veľmi jednoduchá:
$ gcc -c libvypocet.c # přeložení knihovny $ gcc -c main.c # přeložení programu $ gcc libvypocet.o main.o -o prog # sestavení binárky
Program potom môžeme jednoducho spúšťať a potrebujeme len nami preloženú binárku. Nevýhodou tohto prístupu je väčšia veľkosť programov a tiež prácna zmena v knižnici - potrebu nového zostavenie programu. Tieto nevýhody sú zanedbateľné, ak knižnicu používame len v jednom programe - v takom prípade je použitie statických knižníc rozhodne vhodné.
Dynamické knižnice
Problém nastáva, keď máme knižnicu, ktorú používa veľké množstvo aplikácií. Po prvé bude zaberať viac miesta - veľkosť knižnice * počet aplikácií, ktoré ju používajú. Na disku to zvyčajne nie je problém. Z hľadiska RAM už ale áno. Oveľa závažnejšie je ale otázka bezpečnosti. Aktualizovať knižnicu po objavení bezpečnostné diery nie je problém. Ak by sme ale zároveň museli znova skompilovať 100 aplikácií, nastáva veľa veľký problém ..
Riešením oboch problémov sú dynamické knižnice. Tie nie sú priamo v programe, ale načítavajú sa až keď sú potrebné a to väčšinou zo systémovej zložky lib (dnes väčšinou usr / lib). Aktualizácia knižnice teda spočíva v prepísanie jedného súboru. Navyše sa naša knižnica do pamäte načíta len raz a všetky programy budú pristupovať k tej rovnaké kópiu. To vyrieši nielen problém s miestom, ale tiež môže prístup k nej urýchliť. Programy ju nemusí pri každom spustení načítať a ak je často používaná, určite sa vyskytne aj v cache procesora.
Dynamické knižnice je potrebné prekladať iným spôsobom. Najskôr vytvoríme samotnú knižnicu:
$ gcc -fPIC -c libvypocet.c $ gcc -shared libvypocet.o -o libvypocet.so
Prepínač -fPIC
hovorí prekladači, že má vytvoriť tzv.
"Position Independent Code" - teda kód nezávislý na svojom umiestnení. V
praxi to znamená, že kód nebude obsahovať konkrétne adresy v pamäti (pre
funkcie a premien), ktoré teraz ani nie je možné zistiť.
Keď máme objektový kód, tak už len pomocou -shared
prekladači oznámime, že chceme dynamickú (zdieľanú) knižnicu. Je dobré
dodržiavať zavedené zvyklosti - meno knižnice začína lib
a
prípona je .so
(Windows používa príponu .dll
).
$ gcc -c main.c $ gcc main.o -lvypocet -L. -o prog
U zostavovaní programu potom musíme prekladači povedať, že chceme
prilinkovať zdieľanú knižnicu vypocet
(podobne ako používame
-lm). Okrem toho ešte použijeme prepínač -L.
, Ktorý umožňuje
hľadanie tejto knižnice aj v adresári s naším kódom.
Program skúsime spustiť.
$ ./prog ./prog: error while loading shared libraries: libvypocet.so: cannot open shared object file: No such file or directory
Ten nám ale zahlási, že nemôže nájsť knižnicu .. Ako to vyriešiť si
povieme hneď. Najskôr by som ale chcel spomenúť program ldd
,
ktorý zistí, ktoré knižnice program potrebuje a pokúsi sa ich nájsť.
$ ldd prog linux-vdso.so.1 => (0x00007ffd2bf3a000) libvypocet.so => not found libc.so.6 => /lib64/libc.so.6 (0x00007f8e3b2e1000) /lib64/ld-linux-x86-64.so.2 (0x00007f8e3b69e000)
Vidíme, že naša knižnica v systéme naozaj nikde nie je. Máme ju síce v priečinku s programom, ale z dôvodu bezpečnosti tam systém hľadať nebude (nebolo by potom ťažké, aby nám treba niekto podstrčil knižnicu so škodlivým kódom).
Teraz máme niekoľko možností, ako našu knižnicu sprístupniť
programom. Jedna z nich je pridať cestu do /etc/ld.so.cong
(konfiguračný súbor programu, ktoré dynamické knižnice načítava).
Ďalšie, podstatne jednoduchšie možnosťou je použiť premennú prostredia
LD_LIBRARY_PATH
, do ktorej môžeme pridať cestu k našej
knižnici.
$ export LD_LIBRARY_PATH=`pwd`
Program sa potom bez problému spustí. Najjednoduchšie a najlepšie
riešenie je jednoducho našu knižnicu "nainštalovať" - teda skopírovať do
/usr/lib
. Po tom, čo to urobíme, musíme ešte spustiť program
ldconfig
, aby sa obnovila cache dostupných knižníc.
$ sudo cp libvypocet.so /usr/lib $ sudo ldconfig
Ďalším efektom tohto kroku je, že už nemusíme použiť prepínač
-L.
pri zostavovaní programov, ktoré našu knižnicu využívajú
- gcc si ju jednoducho nájde v štandardnom umiestnení.
$ gcc -o prog -lvypocet main.o
Niektoré možno napadlo, či by nešiel podobne sprístupniť aj hlavičkový súbor našej knižnice. Odpoveď je samozrejme áno.
$ sudo cp libvypocet.h /usr/include
Teraz sa naša knižnica chová ako akákoľvek iná zdieľaná systémová knižnica. Náš Progam môžeme upraviť nasledovne:
#include <stdio.h> #include <libvypocet.h> int main() { printf("Vysledek vypoctu z knihovny: %d\n", vypocet(5, 5)); return 0; }
Ak by sme chceli našu knižnicu zase odinštalovať, je to naozaj veľmi jednoduché:
$ sudo rm /usr/include/libvypocet.h $ sudo rm /usr/lib/libvypocet.so
Celá ukážka je dole k stiahnutiu vrátane ukážky jednoduchého Makefile, ktorý program preloží a nainštaluje. Ak máte niekto akékoľvek otázky alebo pripomienky, určite je napíšte do komentárov.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 50x (3 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C