Segmentace programu paměti
Paměť programu je rozdělena na pět segmentů: text, data, bss (block started,by
symbol, segment neinicializovaných dat), heap ( halda) a stack (
zásobník).,Každý segment reprezentuje speciální část paměti, která je
vymezena,pro určité účely.,Segment text se někdy značí i jako code. To je místo,
kde se nacházejí,instrukce strojového jazyka. Vykonávání instrukcí v tomto
segmentu,není lineární, diky výše zmíněným vysokoúrovňovým řídicím strukturám,a
funkcím, které se zkompilují do instrukcí větvení ( branch), skoků ( jump),a
volání ( call). V okamžiku spuštění programu se EIP nastaví na první
instrukci,segmentu text. Procesor potom následuje vykonávací smyčku, která
dělá následující:
1. Přečti instrukci, na kterou ukazuje EIP.
2. Přičti k
EIP délku instrukce.
3. Vykonej instrukci přečtenou v kroku 1.
4. Jdi na
krok 1.
Někdy je přečtená instrukce instrukcí skoku nebo volání, která mění
EIP na,jinou adresu. Procesor se nestará o změny, neboť stejně předpokládá
nelineární,vykonávání. Takže pokud se v kroku 3 EIP nějak změní, procesor bude
dále pokračovat v kroku 1, přečte a vykoná další instrukci, na kterou EIP
ukazuje, ať už bude jakákoliv. Právo zápisu je v segmentu text vypnuto, neboť
neslouží k uchovávání proměnných, ale jen kódu. To zabrání lidem modifikovat
programový kód a jakýkoliv pokus o zápis do tohoto segmentu paměti způsobí
zobrazení varování uživateli, že se stalo něco špatného, a okamžité ukončení
programu. Další výhodou toho, že je tento segment pouze pro čtení, je umožnění
jeho bezproblémového sdílení při spuštění více kopií téhož programu. Také je
vhodné poznamenat, že tento segment má fixní velikost. Segmenty data a bss
se používají pro uchování globálních a statických programových proměnných. V
segmentu data se ukrývají inicializované globální proměnné, řetězce a další
konstanty, které jsou v programu užívány. V segmentu bss jsou neinicializované
proměnné. Ačkoliv jsou tyto segmenty zapisovatelé, také mají fixní velikost.
Segment heap (halda) se používá pro ostatní programové proměnné. Segment haldy
nemá konstantní velikost a podle potřeby může jeho velikost růst i klesat. Celá
tato paměť je řízena alokačními a dealokačními algoritmy, které rezervují část
paměti pro pozdější použití a zpětně odstraňují rezervace, aby se oblast mohla
později opět využít. Halda bude růst a klesat v závislosti na tom, kolik paměti
má pro tyto účely rezervováno. Růst haldy začíná na nižších a postupuje do
vyšších adres paměti. Segment stack (zásobník) má také proměnou velikost a
používá se jako dočasné úložiště pro kontext během volání funkcí. Když program
zavolá funkci, bude mít vlastní sadu proměnných a kód funkce bude v jiné části
segmentu text (nebo code). Protože se kontext a EIP musí při volání funkce
změnit, zásobník slouží k zapamatování všech proměnných a EIP, na který se po
skončení funkce program opět vrátí. V obecných počítačových termínech je
zásobník abstraktní datová struktura, která se velmi často používá. Má řazení
typu FILO (First-In, Last-Out – první dovnitř, poslední ven), což znamená, že
první položka, která se do stacku dostane je tou poslední, která ze stacku může
ven. Je to jako skládání korálků na šňůru, která má zavázaný druhý konec –
nemůžete dostat ven první korálek bez toho, aniž byste nejdříve nevytáhli
ostatní korálky. Přidání položky na zásobník se říká pushing a odebírání
popping. Jak napovídá jméno, paměťový segment zásobníku je ve skutečnosti datová
struktura. Registr ESP se používá k udržování adresy na konci zásobníku, který
se neustále mění, tak jak jsou neustále přidávány a odebírány položky. Vzhledem
k dynamickému chování této struktury je zřejmé, že stack nemá fixní velikost.
Opačně než halda velikost zásobníku roste z vyšších adres k nižším. Použití
koncepce FILO pro implementaci zásobníku se může zdát zbytečné, ale protože se
stack používá pro uchovávání kontextu, je to velmi užitečné. Když se zavolá
funkce, potřebné údaje se vloží na zásobník ve formě tzv. rámce zásobníku (
stack frame). Registr EBP, občas nazývaný jako frame pointer ( FP, ukazatel na
rámec) nebo local base pointer ( LB, ukazatel na lokální bázi), se použije k
odkazování na proměnné v aktuálním zásobníkovém rámci. Každý takový rámec
obsahuje parametry funkcí, její lokální proměnné a dva pointery důležité pro
navrácení věcí do stavu, v jakém byly před samotným voláním: saved frame pointer
( SFP, uložený ukazatel na rámec) a návratová adresa ( return value). Pointer na
rámec zásobníku se používá k obnovení EBP na jeho předchozí hodnotu a návratová
adresa k obnovení EIP na další instrukcí hned za voláním funkce. Zde je příklad
testovací funkce a hlavní funkce: void test_function(int a, int b, int c, int d)
{
char flag;
char buffer[10];
}
void main()
{
test_function(1,
2, 3, 4);
}
V tomto krátkém kousku kódu se deklaruje testovací funkce se
čtyřmi argumenty, které jsou deklarovány jako celá čísla: a, b, c a d. Lokální
proměnné pro funkci zahrnuje jeden znak nazvaný flag a desetiznakový buffer
nazvaný buffer. Funkce main() se po spuštění programu spustí jako první a
jednoduše zavolá testovací funkci. Když se funkce test_function zavolá z funkce
main, uloží se na zásobník rozličné hodnoty a vytvoří se rámec zásobníku.
Argumenty funkce se uloží v opačném pořadí (protože je stack FILO), tedy d, c, b
a nakonec a. V okamžiku, kdy je spuštěna instrukce call pro volání funkce test_
function(), se na zásobník uloží návratová adresa, jejíž hodnota bude EIP
ukazující na instrukci za instrukcí volání (tedy adresa instrukce call +
velikost samotné instrukce). Za návratovou adresou se nachází tzv. prolog
procedury. V tomto kroku se do zásobníku uloží hodnota registru EBP, což je
uložený ukazatel na rámec a později se použije pro obnovení do původního stavu.
Aktuální hodnota registru ESP se potom zkopíruje do registru EBP a tím se
nastaví ukazatel na nový rámec. Nakonec se alokuje paměť pro lokální proměnné
funkce (flag a buffer) na zásobníku zmenšením ESP. Toto tedy je rámec
zásobníku. Na lokální proměnné se odkazuje odečítáním z ukazatele na rámec
(registr EBP) a na argumenty funkce přičítáním. Když se zavolá funkce, EIP se
změní na adresu začátku funkce v segmentu text (nebo code). Paměť na zásobníku
se použije pro lokální proměnné funkce a její argumenty. Když se funkce ukončí,
celý rámec zásobníku se ze zásobníku odstraní a EIP se nastaví na uloženou
návratovou adresu, takže program může dále pokračovat ve vykonávání. Jestliže by
se z dané funkce zavolala jiná funkce, vytvořil by se pro ni v zásobníku další
rámec a tak dále. Při každém ukončení funkce se rámec zásobníku odstraní a tak
se může vykonávání kódu vrátit na předešlou funkci. Kvůli tomuto chování je
tento segment paměti organizován jako FILO. Rozličné segmenty paměti jsou za
sebou poskládány v pořadí, v jakém byly uvedeny, od nižších adres do vyšších.
Protože většinou jsme zvyklí prohlížet si seznamy ze shora dolů, jsou nižší
adresy uvedeny nahoře. Protože jsou segmenty heap a stack dynamické, rostou oba
proti sobě. To minimalizuje plýtvání místem a možnost, že by se tyto segmenty
střetly.