Programujeme OS: jak na GDT a IDT

Tomáš Jędrzejek 13. 8. 2009

V minulém díle našeho seriálu o vývoji operačního systému jsme společně probrali řízení VGA, dnes přišly na řadu podivné zkratky GDT a IDT. Vysvětlíme si, co to vlastně je, k čemu se nám hodí a hlavně jak to vlastně můžeme nastavit. Na konci na vás čeká funkční kód s ukázkou přerušení.

Co to tedy je GDT a IDT

Můžeme jim říkat popisovače tabulek, představme si je jako pole obsahující tzv. příznaky (flags) a další speciální bity popisující operaci, kterou má segmentační systém vzít v úvahu, pokud se jedná o GDT nebo operace určující vektor přerušení, pak je to IDT.

GDT neboli Global Descriptor Table

Architektura x86 má dvě metody, kterými lze chránit paměťový prostor – jsou jimi segmentace a stránkování. Pokud mluvíme o segmetaci, každý přístup do paměti je vázán na určitý segment. V praxi to znamená, že se adresa daného bodu v paměti přičte k tzv. base adrese patřičného segmentu, poté se překontroluje jeho velikost. Segment si lze představit jako okno v adresním prostoru, přičemž ho aplikace nevidí, celý adresní prostor se jeví jako běžná lineární paměť.

 Stránkování je poměrně odlišné – adresní prostor je rozdělen na tzv. stránky, které mají každá svou velikost (většinou je to 4 kB, lze ale změnit). Každá ze stránek představuje blok paměti, který je namapován na fyzickou paměť. Stránkování se také využívá pro čel virtuální paměti – každý proces v systému si např. může myslet, že je na adrese odpovídající 128 MB, přičemž fyzicky má každý z nich adresu jinou (jinak by se navzájem přepsaly).

U 32bit x86, respektive ia-32 procesorů lze namapovat virtuální paměť až do výše 4 GB, přičemž ve skutečnosti může mít daný stroj menší paměť RAM.Obě metody ochrany paměti mají své výhody i nevýhody, stránkování je však užitečnější – o tom ale jindy. Jedna z výhod, kterou však stránkování poskytnout nemůže je tzv. ring (0 – 3), kde se jedná o oprávnění pro běžící procesy vůči adresnímu prostoru. Ring na úrovni 0 je nejvíce privilegovaný, takže se bude určitě hodit pro naše jádro. Ring 1 a 2 se v dneštních operačních systémech příliš nevyskytuje – nemají k tomu důvod, avšak najdeme takové, které toho využívají (některé OS s mikrojádry). Poslední ring na úrovni 3 je nejméně privilegovaný, takže se uživá pro user-space procesy. Jednoduše si pak software nebude moci dělat, co se mu zlíbí.

Jak nastavit GDT ?

Pro začátečníka může být tato operace obtížná, pokud se ale seznámíme s postupem „nahození“ Global Descriptor Tabulky v ukázkovém kódu, bude to daleko jednodušší. Nejdříve představím datovou strukturu, která koresponduje s x86 – tato struktura má samozřejmě všude stejný „tvar“, takže ji lze definovat takto:

/* Struktura obsahuje hodnoty pro nastavení jedné položky z GDT */
typedef struct {
    unsigned short limit;   /* konec adresního prostoru (prvních 16bitů) */
    unsigned short base_f;  /* začátek adresního prostoru (1/3) (16bitů) */
    unsigned char base_s;   /* začátek adresního prostoru (2/3) (8bitů) */
    unsigned char attrib;   /* nastavení příznaku přístupu (včetně ring) */
    unsigned char gran; /* nastavení masky granularity (8bitů) */
    unsigned char base_t;   /* začátek adresního prostoru (3/3) (8bitů) */
} __attribute__ ((packed)) gdt_entry_t;

Vidíme před sebou jeden záznam, který tvoří onu GDT – my jich budeme potřebovat 5. První bude nulový, bez kterého by se mohly dít zajímavé věci (více i386 dokumentace). Druhý segment kódu a třetí segment dat. Další dva budou opět pro kód a data s tím rozdílem, že budou pracovat v ring na úrovni 3 – tj. pro uživatelské procesy. Všechny krom prvního (který je opravdu nulový) budou přístupné v celém adresním prostoru (0×0 až 0×ffffffff), limit lze také definovat pomocí ~0 (což dává nejvyšší možnou hodnotu pro daný typ proměnné).

Proměnná gran je velikosti 1 bajt a představuje masku k nastavení granularity. Slouží tedy k nastavení několika klíčových parametrů segmentu, podobně jako proměnná attrib (např. nastavení ringu nebo typu segmentu).Teď nastala otázka, jak oznámit procesoru, aby naši GDT použil – naštěstí existuje instrukce lgdt, ta funguje tak, že jí předáme ukazatel na adresu, kde se nachází struktura gdt_ptr_t. Vypadá takto:

/* Struktura představuje ukazatel na GDT tabulku, nutný pro instrukci lgdt */
typedef struct {
    unsigned short limit;   /* velikost GDT tabulky */
    unsigned int base;  /* adresa GDT tabulky */
} __attribute__ ((packed)) gdt_ptr_t;

Ta instrukci lgdt sdělí, kde že se ta tabulka nachází a jak je dlouhá (velikost v bajtech –1). Proměnná base obsahuje adresu na začátek pole struktur gdt_entry_t. Instrukce lgdt je volána v proceduře gdt_flush, viz. start.s.

IDT neboli Interrupt Descriptor Table

Občas se nám může hodit tzv. přerušení, ať už vnější nebo vnitřní – to si lze představit jako nečekaný impuls procesoru, který říká, že se něco stalo. Máte např. uživatelský program, ve kterém nemůžete přistupovat na adresy jako 0×b0000, apod. ale máte za cíl vypsat text na obrazovce. Jak to tedy udělat ? Použijeme přerušení :) – jeho instrukce se jmenuje int a jako parametr se používá hodnota, která označuje jeho druh.

V moderních systémech se používají desítky až stovky přerušení, přitom každé dělá něco jiného. Dokonce i skoro každý hardware generuje přerušení v závislosti na událostech, takže je nutné je nějakým způsobem obstarat. Pokud se vrátíme zpět k uživatelskému programu a usmyslíme si, že přerušení s ID 0×80 bude vypisovat znak, který předáme pomocí registru, může ho náš program směle zavolat. V ten moment procesor přeskočí na jaderný kód a tam se požadavek programu obslouží. Po vykonání samozřejmě vše vrátí do původního stavu (registry) a pokračuje se v kódu uživatelském.

Jak nastavit IDT ?

Nastavení je velmi podobné tomu z GDT, nyní nenastavujeme segmenty, ale jednotlivé přerušení. Každý záznam v tabulce zaregistruje funkci, která se vykoná, když se zavolá konkrétní přerušení. Architektura x86 má však vyhrazené prvních 32 přerušení pro své potřeby. Jedná se o stavy jako např. dělení nulou nebo důležitý general protection fault (GPF), který nám říká, že došlo v paměti k chybě. Je tedy nutné, abychom všechny tyto chybové stavy zaregistrovali a patřičně na ně zareagovali.

/* Tato struktura popisuje položku pro IDT */
typedef struct {
    unsigned short base_l;  /* Adresa funkce, na kterou má procesor skočit při přerušení (1/2) */
    unsigned short sel; /* Offset pro nastavení jaderného segmentu */
    unsigned char zero; /* Sem patři nula */
    unsigned char attrib;   /* Nastavení příznaků */
    unsigned short base_h;  /* Adresa funkce, na kterou má procesor skočit při přerušení (2/2) */
} __attribute__ ((packed)) idt_entry_t;

/* Tato struktura se používá pro specifikaci IDT tabulky k volání instrukce lidt */
typedef struct {
    unsigned short limit;   /* Délka IDT tabulky */
    unsigned int base;      /* Adresa IDT tabulky */
} __attribute__ ((packed)) idt_ptr_t;

První struktura opět znamená jeden záznam v IDT, druhá se naopak použije pro specifikaci adresy a délky celé tabulky pro instrukci zvanou lidt – volá se prostřednictvím procedury idt_flush. Nahlédneme-li do souboru int.s, vidíme proceduru isr_common_stub, která je volána pokaždé, nastane-li přerušení s hodnotou v rozmezí 0 až 31. Její činnost spočívá v uložení všech registrů (např. uživatelský program) a zavolání obslužné funkce definované v isr.c. Její povinností je vypořádat se s příp. chybou. Nám bude prozatím stačit, když oznámíme na obrazovce pomocí jádra informaci o přerušení, viz. funkce isr_handler (). Nakonec procedura isr_common_stub zpět obnoví registry aby nedošlo k chybě v kódu, který přerušení vyvolal.

widgety

Shrnutí

GDT i IDT máme nastaveny, jak ale poznáme, že to je právě tak, jak by mělo být? Prvním příznakem je spuštění samotného systému, pokud se bavíme o GDT. Druhým příznakem, jestli je řeč o IDT, je fakt, že při zavolání instrukce přerušení z kódu jádra, nám hezky oznámí co se stalo. Dnes jsme si tedy vytvořili užitečnou ochranu našeho jádra proti poškození paměti či dalším nežádaným vlivům. Úplně vše co bych chtěl zmínit zde bohužel není – pomalu už to je na přepis i386 dokumentace, doporučuji však omrknout již zmiňovanou granularitu, ringy, atributy tabulek ID, GD a také segmentaci. V ukázce můžete shlédnout následující kus kódu (main.c):

int r = 1 / 0;

Věřím, že každý po tomto dílu přijde na to, co přesně se na tomto řádku stane a také to, že do funkčního jádra nepatří. Ukázkový kód lze stáhnout na ZeXOS.org.

Našli jste v článku chybu?
Lupa.cz: Patička e-mailu závazná jako vlastnoruční podpis?

Patička e-mailu závazná jako vlastnoruční podpis?

Podnikatel.cz: Poslanci chtějí sebrat majetek Bakalovi

Poslanci chtějí sebrat majetek Bakalovi

Vitalia.cz: dTest odhalil ten nejlepší kečup

dTest odhalil ten nejlepší kečup

DigiZone.cz: Sony MP-CL1A: miniaturní projektor

Sony MP-CL1A: miniaturní projektor

Vitalia.cz: Tohle jsou nejlepší česká piva podle odborníků

Tohle jsou nejlepší česká piva podle odborníků

Měšec.cz: TEST: Vyzkoušeli jsme pražské taxikáře

TEST: Vyzkoušeli jsme pražské taxikáře

Podnikatel.cz: Znáte už 5 novinek k #EET

Znáte už 5 novinek k #EET

Lupa.cz: Cimrman má hry na YouTube i vlastní doodle

Cimrman má hry na YouTube i vlastní doodle

DigiZone.cz: Rapl: seriál, který vás smíří s ČT

Rapl: seriál, který vás smíří s ČT

DigiZone.cz: Parlamentní listy: kde končí PR...

Parlamentní listy: kde končí PR...

DigiZone.cz: DVB-T2 ověřeno: seznam TV zveřejněn

DVB-T2 ověřeno: seznam TV zveřejněn

Lupa.cz: Další Češi si nechali vložit do těla čip

Další Češi si nechali vložit do těla čip

Vitalia.cz: Jak Ondra o astma přišel

Jak Ondra o astma přišel

Vitalia.cz: Voda z Vltavy před a po úpravě na pitnou

Voda z Vltavy před a po úpravě na pitnou

Podnikatel.cz: EET pro e-shopy? Postavené na hlavu

EET pro e-shopy? Postavené na hlavu

DigiZone.cz: Funbox 4K v DVB-T2 má ostrý provoz

Funbox 4K v DVB-T2 má ostrý provoz

Lupa.cz: Aukro.cz mění majitele. Vrací se do českých rukou

Aukro.cz mění majitele. Vrací se do českých rukou

Vitalia.cz: Tahák, jak vyzrát nad zápachem z úst

Tahák, jak vyzrát nad zápachem z úst

DigiZone.cz: Světový pohár v přímém přenosu na ČT

Světový pohár v přímém přenosu na ČT

Lupa.cz: Hackeři mají data z půlmiliardy účtů Yahoo

Hackeři mají data z půlmiliardy účtů Yahoo