IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 9 - Céčko a Linux - Statické a dynamické knihovny

V minulé lekci, Céčko a Linux - getopt_long a shell, jsme dokončili filtry. Dnes se podíváme na tvorbu a použití externích knihoven.

Knihovna

Co to vůbec je? Knihovna je, jednoduše řečeno, sbírka funkcí, které můžeme volat z našeho programu.

Jistě znáte standardní knihovnu C (např. stdio) a nejspíš víte, že je možné tvořit knihovny vlastní. S největší pravděpodobností jste již i někdy nějakou vytvořili. Přesto bych se na dané téma chtěl dnes podívat blíže a je pravděpodobné, že se dozvíte i něco nového. :)

Vytvoříme si velmi jednoduchou knihovnu, která bude obsahovat pouze dvě funkce.

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ásledně k ní uděláme rozhraní (hlavičkový soubor), kde budou definovány všechny funkce, které je možné zavolat z jiných částí programu.

libvypocet.h

extern int vypocet(int a, int b);

Všimněte si funkce mezivypocet, která v hlavičce zapsána není - tuto funkci můžeme volat pouze v rámci naší knihovny. Za zmínku stojí také řádka static int mezivypocet(int a);. Té se říká úplný funkční vzor (pokud se pletu, prosím opravit) a je dobrým zvykem je napsat na začátek souboru pro každou funkci. Umožňují totiž volat z funkcí funkce, jejichž definice je až za funkcí, ze které se volá. V zásadě říká překladači, co funkce vrací a jaké má parametry. Ten to pak může zkontrolovat. ;)

Funkční vzory veřených funkcí jsou vypsány v hlavičkovém souboru a jeho vložením na začátek naší knihovny si vlastně ušetříme psaní.

Zde je náš jednoduchý program, který jen zavolá funkci z naší knihovny a vypíše výsledek na stdout.

main.c

#include <stdio.h>

#include "libvypocet.h"

int main()
{
  printf("Vysledek vypoctu z knihovny: %d\n", vypocet(5, 5));
  return 0;
}

Teď si ukážeme, jak můžeme náš program zkompilovat, aby použil námi vytvořenou knihovnu. Je nutné říct, že existují dva druhy knihoven - statické a dynamické.

Statické knihovny

Statické knihovny nejspíš znáte. Je to velmi jednoduchý způsob, jak rozdělit program do více modulů a zpřehlednit tak kód. Často se používá u projektů, kde spolupracuje více programátorů - každý udělá jeden modul (knihovnu), otestuje ho a nadefinuje rozhraní. Moduly se pak jen spojí a aplikace je hotová.

Z technického hlediska je statická knihovna přímo součást binárky. S každou úpravou knihovny musíme program znovu sestavit.

Samotná posloupnost příkazů je pro statické knihovny velmi 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 pak můžeme jednoduše spouštět a potřebujeme jen námi přeloženou binárku. Nevýhodou tohoto přístupu je větší velikost programů a také pracná změna v knihovně - nutnost nového sestavení programu. Tyto nevýhody jsou zanedbatelné, pokud knihovnu používáme jen v jednom programu - v takovém případě je použití statických knihoven rozhodně vhodné.

Dynamické knihovny

Problém nastává, když máme knihovnu, kterou používá velké množství aplikací. Zaprvé bude zabírat více místa - velikost knihovny * počet aplikací, které ji používají. Na disku to obvykle není problém. Z hlediska RAM už ale ano. Mnohem závažnější je ale otázka bezpečnosti. Aktualizovat knihovnu po objevení bezpečnostní díry není problém. Pokud bychom ale zároveň museli znovu zkompilovat 100 aplikací, nastává hodně velký problém..

Řešením obou problémů jsou dynamické knihovny. Ty nejsou přímo v programu, ale načítají se až když jsou potřeba a to většinou ze systémové složky lib (dnes většinou usr/lib). Aktualizace knihovny tedy spočívá v přepsání jednoho souboru. Navíc se naše knihovna do paměti načte jen jednou a všechny programy budou přistupovat k té stejné kopii. To vyřeší nejen problém s místem, ale také může přístup k ní urychlit. Programy ji nemusí při každém spuštění načítat a pokud je často používaná, určitě se vyskytne i v cache procesoru.

Dynamické knihovny je nutné překládat jiným způsobem. Nejdříve vytvoříme samotnou knihovnu:

$ gcc -fPIC -c libvypocet.c
$ gcc -shared libvypocet.o -o libvypocet.so

Přepínač -fPIC říká překladači, že má vytvořit tzv. "Position Independent Code" - tedy kód nezávislý na svém umístění. V praxi to znamená, že kód nebude obsahovat konkrétní adresy v paměti (pro funkce a proměné), které teď ani není možné zjistit.

Když máme objektový kód, tak už jen pomocí -shared překladači sdělíme, že chceme dynamickou (sdílenou) knihovnu. Je dobré dodržovat zavedené zvyklosti - jméno knihovny začíná lib a přípona je .so (Windows používá příponu .dll).

$ gcc -c main.c
$ gcc main.o -lvypocet -L. -o prog

U sestavování programu pak musíme překladači říct, že chceme přilinkovat sdílenou knihovnu vypocet (podobně jako používáme -lm). Kromě toho ještě použijeme přepínač -L., který umožňuje hledání této knihovny i v adresáři s naším kódem.

Program zkusíme spustit.

$ ./prog
./prog: error while loading shared libraries: libvypocet.so: cannot open shared object file: No such file or directory

Ten nám ale zahlásí, že nemůže najít knihovnu.. Jak to vyřešit si povíme hned. Nejdřív bych ale chtěl zmínit program ldd, který zjistí, které knihovny program potřebuje a pokusí se je najít.

$ 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še knihovna v systému opravdu nikde není. Máme ji sice ve složce s programem, ale z důvodu bezpečnosti tam systém hledat nebude (nebylo by pak těžké, aby nám třeba někdo podstrčil knihovnu se škodlivým kódem).

Teď máme několik možností, jak naši knihovnu zpřístupnit programům. Jedna z nich je přidat cestu do /etc/ld.so.cong (konfigurační soubor programu, které dynamické knihovny načítá). Další, podstatně jednodušší možností je použít proměnnou prostředí LD_LIBRARY_PATH, do které můžeme přidat cestu k naší knihovně.

$ export LD_LIBRARY_PATH=`pwd`

Program se pak bez problému spustí. Nejjednodušší a nejlepší řešení je prostě naši knihovnu "nainstalovat" - tedy zkopírovat do /usr/lib. Po tom, co to uděláme, musíme ještě spustit program ldconfig, aby se obnovila cache dostupných knihoven.

$ sudo cp libvypocet.so /usr/lib
$ sudo ldconfig

Dalším efektem tohoto kroku je, že již nemusíme použít přepínač -L. při sestavování programů, které naši knihovnu využívají - gcc si ji jednoduše najde ve standardním umístění.

$ gcc -o prog -lvypocet main.o

Některé možná napadlo, jestli by nešel podobně zpřístupnit i hlavičkový soubor naší knihovny. Odpověď je samozřejmě ano.

$ sudo cp libvypocet.h /usr/include

Teď se naše knihovna chová jako jakákoliv jiná sdílená systémová knihovna. Náš progam můžeme upravit následovně:

#include <stdio.h>
#include <libvypocet.h>

int main()
{
  printf("Vysledek vypoctu z knihovny: %d\n", vypocet(5, 5));
  return 0;
}

Pokud bychom chtěli naši knihovnu zase odinstalovat, je to opravdu velmi jednoduché:

$ sudo rm /usr/include/libvypocet.h
$ sudo rm /usr/lib/libvypocet.so

Celá ukázka je dole ke stažení včetně ukázky jednoduchého Makefilu, který program přeloží a nainstaluje. Pokud máte někdo jakékoliv otázky nebo připomínky, určitě je napište do komentářů. :)


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 46x (3 kB)
Aplikace je včetně zdrojových kódů v jazyce C

 

Předchozí článek
Céčko a Linux - getopt_long a shell
Všechny články v sekci
Linux a programování v jazyce C
Článek pro vás napsal David Novák
Avatar
Uživatelské hodnocení:
3 hlasů
Autor se zajímá především o nízkoúrovňové programování (C/C++, ASM) a návrh hardwaru (VHDL).
Aktivity