Hlavní navigace

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.

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?

13. 8. 2009 12:00

I/O (neregistrovaný)

Já teda o tom právě po přečtení tohohle dílu přesvědčen nejsem. Je to napsané stylem, jako když student mlží u zkoušky. Navíc – jak už tu poznamenali jiní – některé pasáže jsou přímo zavádějící:

„Můžeme jim říkat popisovače tabulek“ – jakých tabulek? Snad tabulky popisovačů, ne? Vlastně celé to souvětí je jakési pomatené.

Zmínka o stránkování a segmentaci je příliš zjednodušená i na pouhý laický popis, natož na článek údajně pojednávající o psaní OS.

„Stránkování se také využívá pro úč…

17. 8. 2009 11:53

BLEK. (neregistrovaný)

To, že se kód zahazuje a přepisuje, dělají všichni vývojáři, i Microsoft. Ještě před 10 lety dodával na desktopy Windows 98 a ME, které neměly ochranu paměti (a daly se shodit dvojicí instrukcí CLI;HLT). A ještě před 20 lety byl běžný DOS. A současné Windows DOSové a Win16 programy stále pouští, ale kód byl zcela přepsán.

A třeba první Apple uměl pustit jen jeden program, pak to začalo uživatelům vadit, tak udělali možnost pustit víc programů, stále jim vadilo, že si ty programy mohou lézt do p…

Podnikatel.cz: Komunikace firem na Facebooku? Otřes!

Komunikace firem na Facebooku? Otřes!

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Měšec.cz: U levneELEKTRO.cz už reklamaci nevyřídíte

U levneELEKTRO.cz už reklamaci nevyřídíte

Měšec.cz: Air Bank zruší TOP3 garanci a zdražuje kurzy

Air Bank zruší TOP3 garanci a zdražuje kurzy

Vitalia.cz: Nestlé vyvinula nový typ „netloustnoucího“ cukru

Nestlé vyvinula nový typ „netloustnoucího“ cukru

Podnikatel.cz: Na poslední chvíli šokuje výjimkami v EET

Na poslední chvíli šokuje výjimkami v EET

Podnikatel.cz: Podnikatelům dorazí varování od BSA

Podnikatelům dorazí varování od BSA

Root.cz: Vypadl Google a rozbilo se toho hodně

Vypadl Google a rozbilo se toho hodně

Vitalia.cz: To není kašel! Správná diagnóza zachrání život

To není kašel! Správná diagnóza zachrání život

DigiZone.cz: Rádio Šlágr má licenci pro digi vysílání

Rádio Šlágr má licenci pro digi vysílání

DigiZone.cz: ČT má dalšího zástupce v EBU

ČT má dalšího zástupce v EBU

Podnikatel.cz: Víme první výsledky doby odezvy #EET

Víme první výsledky doby odezvy #EET

Vitalia.cz: Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

120na80.cz: Horní cesty dýchací. Zkuste fytofarmaka

Horní cesty dýchací. Zkuste fytofarmaka

Měšec.cz: Kdy vám stát dá na stěhování 50 000 Kč?

Kdy vám stát dá na stěhování 50 000 Kč?

DigiZone.cz: Flix TV má set-top box s HEVC

Flix TV má set-top box s HEVC

Lupa.cz: Co se dá měřit přes Internet věcí

Co se dá měřit přes Internet věcí

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Měšec.cz: Finančním poradcům hrozí vracení provizí

Finančním poradcům hrozí vracení provizí

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky