Tartalmi kivonat
Az x86-os architektúra alapjai Most kezdődő cikksorozatunkban napjaink legelterjedtebb processzor architektúráját, a 32 bites, x86-os architektúrát vizsgáljuk meg (rendszer)programozói oldalról. Ez az architektúra, pontosabban az ilyen architektúrájú processzorok és processzorú PC-k szinte kellékei életünknek, főképp az informatikusok életének. Ugyanakkor ezen architektúrájú processzorok mélyebb működéséről magyar szakirodalom híján, nem vagyunk oly tájékozottak, mint amilyen fontos szerepet tölt be az informatika, számítástechnika jelenlegi életében. Napjainkban, pontosabban 1985 óta vezető szerepet tölt be az Intel, processzorai illetve ezen 32 bites, x86-os architektúrája által, így számos rendszer ezen alapul. Az informatikusoknak, programfejlesztőknek ezen rendszerek működésének megértéséhez elengedhetetlen az architektúra alapos ismerete. Az architektúráról már megjelent egy-két kevésbé részletes magyar nyelvű
kézikönyv, mely ugyan hiány pótló, ugyanakkor a szakemberek számára nem világítja meg a működést elég alapos módon, így nem alakulhat ki a rendszerek átfogó szemlélete. Így marad hiányos az operációs rendszerekről alkotott kép, így áll tétlenül az átlag informatikus a Windows 9x/ME kék képernyője és 0Eh vagy 0Dh kivétel hibája előtt. Vagy a Windows NT "ezer kis számmal" zsúfolt kék képernyője előtt csodálkozik, miközben tudatlanságában általánosságban szidja az operációs rendszert és készítőjét és sejtelme sincs, hogy hol a hiba, noha a Windows 9x/ME is megmondja: 0Eh vagy 0Dh, stb. vagy a Windows NT is, sőt jelentősen érthetőbb formában: PAGE FAULT IN NON PAGED AREA, vagy IRQL NOT LESS OR EQUAL * Address. - NTFSsys, stb Ha megérjük mi a hiba, "hol fáj az operációs rendszernek" sejthetjük milyen gyógyír segíthet. Talán - Windows NT-t alapul véve - az újonnan feltelepített program drivere
(*.sys) a hibás, vagy vírusos a gépem, vagy csak egy jó fagyás eredménye képen íródott felül pár bájt vagy szektor és így megsérült pár rendszer driver? Nos reméljük sorozatunk mindezen kérdésekhez is segítséget nyújt. A cikksorozatban a 32 bites, x86-os processzorok védett módú programozását taglaljuk, szorítkozva a legfontosabb kérdésekre: a rendszer architektúra, a védett módú memória kezelés, az architektúra által biztosított és követelt védelem mibenlétére. Tervezzük egy következő cikksorozatban a gyakorlati részek bemutatását, bepillantást nyújtva az operációs rendszerek "lelkébe", a kernelbe, egy saját gyakorlati példa bemutatása által, mely nagy mértékben elősegíti az informatikusokat és programozókat az operációs rendszerek jobb tolerálására. (A gyakorlati részekig eljutott Olvasó, a processzor és az operációs rendszer csodálatába kerül, ugyanis átlátja, hogy valóban "milyen
csoda" egy operációs rendszer működése, az óriási komplexitás illetve precizitás igény miatt.) Cikksorozatunk nagyrészt a processzor gyártó cégek - főképp az Intel - által kiadott hivatalos dokumentációkra épül. Ezek jól bevált tematikáját követjük, s saját rendszerprogramozói tapasztalataimmal magyarázom A 32 bites, x86-os architektúra története Az x86-os architektúra kezdete A 32 bites, x86-os (Intel) architektúrához vezető fejlesztések visszavezethetőek a 8085-ös és a 8080-as mikroprocesszorokon keresztül egészen a 4004-es mikroprocesszorig (ez az első mikroprocesszor amit terveztek, ezt az Intel tervezte 1969-ben). Annak ellenére, hogy az Intel Architektúra (az x86-os) processzor család első mikroprocesszora a 8086-os, ezt gyorsan követte egy kisebb rendszerekbe szánt olcsóbb megoldás, a 8088-as mikroprocesszor. A tárgykódú programok, melyek ezekre a processzorokra készültek 1978-tól kezdődően, még a mai, 32 bites
x86-os architektúrájú processzorok mindegyikén futnak, így pl. futhat a mai legújabb ilyen alap architektúrával - is - rendelkező Intel Pentium 4, vagy akár az AMD Athlon (K7) processzorokon (feltéve, hogy nem tartalmaz alaplap vagy időzítés specifikus megoldásokat). Az Intel 8086-os processzor A 8086 processzor 16 darab regiszterrel, 16 bites külső adatbusszal rendelkezik, és 20 bites memóriacímzést támogat ezáltal 1 MBájt címtartományt biztosítva. A 8088-as processzor a 8086-tal megegyezik, csak annyiban tér el, hogy 8 bites külső adatbusszal rendelkezik. Ezek a processzorok vezették be az Intel Architektúra szegmentálást, de csak "valós módban": a 16 bites regiszterek mutatóként használhatóak a szegmensen belüli címzésben egészen 64 KBájtos méretig. A négy szegmens regiszter (ténylegesen) tartalmazhatja a 20 bites kezdőcímét az éppen aktív szegmensnek; egészen 256 KByte címezhető meg anélkül, hogy szegmenst
váltanánk, és a teljes címezhető tartomány pedig 1 MBájt. Az Intel 80286-os processzor Az Intel 80286-os processzor vezette be a Védett Módot (Protected Mode) az Intel, x86-os architektúrába. Ez az új mód a szegmens regiszterek tartalmát, mint kiválasztókat vagy mutatókat használja egy leíró táblázatban (GDT Global Descriptor Table - Globális Leíró Táblázat). A leírók 24 bites kezdőcímet biztosítanak, ezzel maximálisan 16 MBájt fizikai memóriát engedélyezve, támogatva a virtuális memória kezelést (VMM - Virtual Memory Management) a szegmens alap, illetve különböző védelmi mechanizmusainak váltogatásának lehetőségével. Ebbe beletartozik a szegmens határ ellenőrzés, a csak olvasható és futtatható szegmensek lehetősége, és egészen négy privilégium szintet támogat, hogy megvédje az operációs rendszert az applikációtól, a felhasználói programoktól. Továbbá támogatja hardveresen a taszk váltást, és a
Lokális Leíró Tábla (LDT - Local Descriptor Table) segítségével az operációs rendszer megvédheti az alkalmazásokat illetve felhasználói programokat egymástól. Az Intel 386-os processzor Az Intel386-os processzor vezette be a 32 bites regisztereket az architektúrába, mind a számításokhoz használt operandusoknál és mind a címzésnél használhatóak. Minden 32 bites regiszter alsó része megtartotta a tulajdonságait a két korábbi generáció processzorainak, így a 32 bites regiszterek alsó részét használhatjuk a korábbi generáció processzorainak 16 bites regisztereként. Így tartva meg a tökéletes visszamenőleges kompatibilitást (tehát nem igényel kód változtatást az új technológia). Egy új (működési) módot, a virtuális-8086-os módot vezették be, hogy nagyobb hatékonyságot érjenek el, amikor a 8086 illetve 8088-as processzorokra írt programokat futtatják ezen az új 32 bites gépen. A 32 bites címzés egy 32 bites külső
címsínnel lett megvalósítva, ezáltal biztosítva a 4 GBájtos címtartományt, és az architektúra fejlesztése révén minden egyes szegmens akár 4 GBájt méretű is lehetett. Az eredeti utasítások új, 32 bites operandusú és címzésű formákkal lettek kibővítve, továbbiakban teljesen új utasítások is bevezetésre kerültek, s természetesen a hozzájuk tartozó új bit manipulációs utasítások is helyet kaptak. Az Intel386-os processzor vezette be a lapozást (paging) az Intel, x86-os, immár 32 bites architektúrába, a fix 4 KBájtos méretű lapok lehetőséget nyújtanak egy olyan virtuális memória kezelésre, mely jelentőségteljesen jobb egy általános szegmens használatnál (ez sokkal hatékonyabb az operációs rendszerek számára, a felhasználói programok illetve applikációk számára pedig abszolút áttetsző és ezt jelentősebb - végrehajtási - sebesség csökkenés nélkül valósítja meg). Továbbá az a lehetőség, hogy akkora
szegmenst definiálhatunk, amekkora a fizikai címtartomány - akár 4 GBájtosat , a lapozással együtt, megvalósítható a védett sík modell, mely címzési rendszert széles körben használt a UNIX típusú operációs rendszerekben. Mindezen újítások által 1985-ben definiálta az Intel a 32 bites x86-os architektúrát, mely a legelterjedtebb processzor architektúra napjainkban. Az így kifejlesztett architektúrát azóta sok más processzor gyártó cég is alkalmazza, mely nagy mértékben elősegítette az architektúra elterjedését. Ma felhasználói számítógépeink, PC-ink jelentős többsége ilyen, a 32 bites, x86-os architektúrájú processzoron fut. Ez az architektúra szinte nem változott az elmúlt 16 évben (1985-2001) és mégis újabb és újabb processzorok jelennek meg. Ennek oka, hogy az 1985-ben avagy 1985-re megalkotott 32 bites, akkor forradalmian új, Intel, x86 architektúra teljesen átgondolt, konzekvens. Látható, hogy nem volt szükség
az architektúra jelentősebb - gyökeresebb - módosítására a 16 év során, hisz az jól használható a modernebb és jócskán megváltozott körülmények között. Ami még meglepőbb, hogy egyes szakértők szerint még 1825 évig használatban marad a 32 bites, x86-os architektúra (én ennél az idő intervallumnál azért óvatosabb vagyok, "csak": 12-18 évet prognosztizálok). Az architektúra változatlansága (a kisebb változásokról alább) mellett újabb és újabb processzorok jöttek, jogosan merül fel a kérdés, hogy akkor pontosan mi is változott. Röviden: A 32 bites, x86os architektúrát (utasítás készlet és felépítés) kiszolgáló mikro-architektúra (a processzor belső áramkörei) Újabb és újabb belső, fizikai felépítésű processzorok jelentek meg, amik nem csak az órajelben gyorsultak, hanem egy órajel alatt is egy, majd egyre több (3-4) utasítást tudtak, tudnak végrehajtani. Így futva be a pályát a kezdeti CISC 32
bites, x86-os processzoroktól a mai OOO (Out Of Order) végrehajtású, inkább RISC jellegű, 32 bites x86-os processzorokig. Tehát az utasításkészlet, az utasításkészlet architektúra szinte változatlan az elmúlt 16 év során Architektúrai változások az Intel386-os processzor óta Az Intel386-os processzor valósította meg, "alapította meg" a 32 bites, x86-os architektúrát, s azóta csak kis számú változtatás történt magában az architektúrában, az utasítás architektúrában. Igazából az 1993-ben bejelentett Intel Pentium processzor tartalmazott egy-két új utasítás architektúra szintű újítást. Bevezették a 4 MBájtos lapok használatát a 4 KBájtos lapok mellett, illetve a virtuális-8086-os módban kisebb fejlesztések történtek. 1995-ben a Pentium Pro-val történtek további kis fejlesztések az utasítás architektúrában. Kibővítették az ekkora - nagy gépes környezetben - lassan szűkössé váló 4 GBájtos, 32 bites
címzési módot 36 bites címzésűre, mellyel már 64 GBájt fizikai memóriát kezelhetünk. A kompatibilitás megőrzése végett csak új biteket - melyek eddig fenntartottak voltak definiáltak az újítások lefedésére Így lehetőség nyílik a kompatibilitás megőrzése mellett az új funkciókat implementálva 64 GBájt fizikai memória elérésére, az immár 2 MBájtos új méretű lapok segítségével (ez jobb, mint a Pentium 4 MBájtos nagyságú, túl nagy lapmérete). 1995 óta napjainkig nem történt változás az architektúrában, s jó ideig nem is várható, bár elvi lehetősége természetesen létezik, hisz vannak még módosítható, eddig fenntartott bitek. Bár a jelenlegi fejlődés nem ebbe az irányba mutat, hisz nagy erőkkel fejleszti az Intel a "jövő architektúráját" az IA-64-et, a 64 bites új architektúrát, melynek első működő processzora az Itanium és rendszere már működik és "kapható". A rendszer szintű
architektúra áttekintése A 32 bites x86-os architektúrájú processzorok széleskörű támogatást nyújtanak az operációs rendszerek, illetve a rendszerfejlesztő szoftverek részére. A processzor rendszer szintű architektúrája az alábbi lehetőségeket támogatja: • Memória menedzsment • • • • • • • A program modulok védelme Multitaszking Kivétel (exception) és megszakítás (interrupt) kezelés Több processzoros rendszerek Cache (gyorsító memória) kezelés A hardware erőforrások illetve az energiakezelés menedzsment Debugging és teljesítmény felügyelet A továbbiakban az alacsony - rendszer - szintű felépítést tekintjük át. Szó lesz továbbá a rendszer és vezérlő regiszterekről, illetve az operációs rendszer által alkalmazható utasítások közül is foglalkozunk a fontosabbakkal. Alábbi ábránkon a 32 bites x86-os processzorok rendszer szintű felépítését mutatjuk be. Ábránk regiszterek, adatstruktúrák,
rendszer szintű utasítások, és a memória menedzsment blokk sémáját mutatja be. 32 bites x86-os processzorok rendszer szintű felépítése Globális és lokális leíró táblák Amikor védett módban működik processzorunk (ezt alább, a Processzor működési módjainál részletezzük) az összes memória hozzáférés az úgynevezett globális leíró táblán (GDT - Global Descriptor Table) vagy az opcionális lokális leíró táblán (LDT - Local Descriptor Table) keresztül történik. Az LDT használata opcionális, nagyobb jelentősége multitaszkingnál van, míg a GDT használata ki nem kerülhető. (A fenti ábránk mutatja be a GDT és LDT táblákat) Ezen tábláknak a bejegyzéseit szegmens leírónak (segmet descriptor) nevezzük. A szegmens leírók tartalmazzák a szegmens kezdő címét (segment base address), az elérési jogait, típusát, és használati információkat. Minden szegmens leírót (tehát azt a bejegyzést, ami a szegmensről
információt tárol) egy szegmens kiválasztóval (segment selector) érhetjük el. Ez nem más, mint a leíró "indexe" A kiválasztókat később részletesen tárgyaljuk (Összefoglalva: ha egy szegmenst el akarunk érni, pl. a DS regiszterbe szoktuk valós módban a fizikai címet tölteni, akkor védett módban DS-be a kiválasztót írjuk, s az ezen kiválasztó által prezentált leíró által meghatározott szegmenshez férünk hozzá. A leírót - amire a kiválasztóval hivatkozunk - már előbb elkészítettük egy kernel utasítással. Az, hogy a GDT illetve LDT az-az globális illetve lokális leírótábla szegmens leírójára hivatkozunk az úgyszintén a DS-ben, a 2. bitben adjuk meg Ezzel bővebben, a következő fejezetben foglalkozunk) Ahhoz, hogy a szegmensben hozzáférjünk egy bájthoz a szegmens kiválasztót és a 32 bites ofszetet is be kell állítani. (Miért 32 bites? Mert a 32 bites x86-os processzorok általános illetve címző-ofszet
regiszterei 32 bitesek. Ezeket a regisztereket Exx módon nevezzük meg: EAX, EBX, ECX, EDX, ESP, EBP, ESI, EDI) A szegmens leíróból veszi a processzor a szegmens lineáris kezdőcímét. (Mi a lineáris cím: az operatív memória bájtjai egymás után szépen sorban: 0. bájttól egészen 4 GB vagy 64 GB-ig) Az ofszet pedig ehhez a kezdő címhez képest határozza meg relatív módon a bájt helyét. Ofszetnek pozitív számot adunk, illetve csak pozitívat adhatunk meg Tehát a szegmens leíró, illetve az ofszet segítségével elérhetünk bármely bájtot, így használható ez a mechanizmus mind kód, adat, illetve verem elérésére. (Az, hogy melyik szegmens mit - kód / adat / verem - tartalmaz azt a szegmens leíró határozza meg.) A fenti ábrán jól látható a lineáris címtartomány, a GDT, LDT illetve ezeken keresztül a szegmensek elérése Azt is láthatjuk, hogy a GDT-re egy regiszter mutat. Ez a GDTR (GDT regiszter), mely többek között tartalmazza a
táblázat lineáris címét is. Innét tudja a processzor, hogy hol kezdődik a GDT (Bővebben alább, a memória menedzselő regisztereknél írunk róla.) Hasonlóan a LDT-re is az LDTR mutat, de nem a lineáris címét, hanem az LDT kiválasztóját tartalmazza. Rendszer szegmensek, szegmens leírók, és kapuk (gates) Mindamellett, hogy a kód-, adat-, illetve a veremszegmensek már megteremtették egy program futásának a lehetőségét, létezik még két rendszer szegmens a taszk állapot szegmens (TSS - Task-State Segment) és az LDT. (A GDT nem tekintjük külön szegmensnek mert nem érhető el kiválasztóval, illetve nincs leírója.) Továbbá még létezik sok speciális leíró amiket funkciójuk alapján kapuknak nevezünk (call gate, interrupt gate, és task gate). A kapuk szolgálnak átjáróként védett módban a különböző védelmi szinten álló procedúrák, rutinok, illetve megszakítások között. (Röviden a védelmi szintekről: van az operációs
rendszer, pl. Windows NT, Linux Az operációs rendszernek olyan fontos rendszer részei vannak, mint a kernel és eszközvezérlők, melyeket nem érhet el egy átlagos felhasználói program pl. egy szövegszerkesztő Annak kiküszöbölésére, hogy egy sima program beleírjon az operációs rendszer memória területébe vezették be a védelmi szinteket, így megelőzve azt, hogy az össze-vissza irkálástól lefagyjon az operációs rendszer. Különböző szintek vannak a védelemre, a több joggal rendelkező eléri az alatta lévőket, viszont fordítva nem.) A védelmi szinteket is később tárgyaljuk Példa a kapuk használatára: pl. egy program hívás a CALL kapun keresztül valósulhat meg, így férhetünk hozzá egy olyan kódszegmensben lévő rutinhoz (procedúrához) amelyik ugyanaz, vagy alacsonyabb számú (0-3) az-az privilegizáltabb. (0 - operációsrendszer kernel, 3 - felhasználói program) Tehát ha van lehetőség ilyen hívásra, akkor azt így lehet
megvalósítani, pl. alkalmazás hívja a kernel rutint Az ilyen hívás megvalósításához egy call kapu kiválasztót kell megadni. Lásd a fenti ábrát Miután kijelöltük a kiválasztóval a call kapu leírót a processzor megnézi, hogy van e jogunk elérni a kívánt rutint. Összehasonlítja a CPL-t (CPL - Current Privilege Level; aktuális privilégium szint. Az a privilégium szint mellyel a éppen futó programrész rendelkezik) a call kapu privilégium szintjével és a meghívandó kódszegmensét a kapuéval. Ha a meghívás megengedett a védelem megállapítása alapján, akkor a processzor a call kapuban a leíró által meghatározott szegmenssel illetve a megadott ofszettel elérhetjük a kívánt rutint, memória címet. Ha a meghívás (call) privilégium szintváltást igényel, akkor a processzor áttér ennek a privilégium szintnek a vermére. Ugyanis minden privilégium szint - mint sok más is - külön veremmel rendelkezik Az új szegmens kiválasztót a
veremhez a TSS-ből veszi a processzor. (Az éppen futó program taszk állapot szegmenséből, TSS-éből.) Végül, ha nem lehetséges a hozzáférés a más privilégiumú szegmenshez, akkor kivétel (~ hiba jelző-, feldolgozó rutin) generálódik. A kapuk továbbá használatosak a 16 illetve 32 bites kód keverésekor, illetve ezek közötti átjáráskor. Még sok esetben fogunk találkozni kapun keresztüli hozzáféréssel Taszk állapot szegmens és taszk kapuk A taszk állapot szegmens (TSS - Task-State Segment) nagyon fontos a multitaszkos környezetekben. A TSS (lásd a fenti ábrát) határozza meg az éppen futó - vagy csak a memóriában bent lévő - taszk végrehajtási környezetét és állapotát. (Több program látszólag egyszerre történő futása során az alkalmazásokat, programokat taszkoknak nevezzük.) A TSS tartalmazza az általános regiszterek, a szegmens regiszterek, az EFLAG regiszter, az EIP regiszter (32 bites utasítás mutató regiszter,
Instruction Pointer regiszter, az éppen végrehajtandó utasítás ofszetjét tartalmazza) elmentett értékét, illetve három szegmens kiválasztót és mutatót a három verem szegmens részére. (Három verem szegmens: mindegyik privilégium szintnek egyet: privilégium szint 0, 1, 2. Ezt tekintettük át fent, hogy innen veszi a másik privilégium szint verem szegmensének címét.) Továbbá az adott taszkhoz tartozó LDT-re mutató kiválasztó és a lapcímtár lineáris címét. (Lapcímtár (page directory): a spec védett memóriakezeléshez, a védett módú memória-kezelés fejezetben foglalkozunk részletesen vele) A lapcímtár lineáris címe a TSS-ből közvetlenül a CR3 (PDBR) regiszterbe kerül a taszk váltás során. Minden védett módban futtatott program taszkként fut. Tehát, ha egy program fut csak, akkor is használt a multitaszk környezet rendszer regiszterei - típusai, csak az adott taszkra. (Szükséges megjegyezni, felettébb ritka, hogy csak egy
taszk fusson, hisz az operációs rendszer kernelje, meg egy felhasználói program is már több mint két taszkot takar.) Az éppen futó taszknak a TSS kiválasztója egy e célra létrehozott regiszterben a taszk regiszterben (TR) van tárolva. A legegyszerűbb módja a taszk váltásnak egy meghívás (call) vagy egy ugrás (jump) a másik taszkra. Híváskor, illetve ugráskor szegmens kiválasztónak a másik taszk TSS-ét adjuk meg. A processzor az alábbiakat teszi meg - automatikusan - taszk váltásának bekövetkezésekor: 1. 2. 3. 4. Az éppen futó taszk (az a taszk amiről éppen elváltunk) állapotát elmenti az aktuális TSS-be. A taszk regiszterbe - TR - az új, futtatandó taszk szegmens kiválasztója kerül Az új TSS-hez a GDT egy leíróján keresztül fér hozzá Betölti az új taszk állapotát az új taszk TSS-éből a megfelelő általános regiszterekbe, szegmens regiszterekbe, az LDTR-be, a CR3 vezérlő regiszterbe (a lapcímtár kezdőcíme), az EFLAG
regisztert és az EIP regisztert is beállítja. 5. Ezek után kezdődik el az új taszk futása az CS-ben lévő kiválasztó és az EIP által meghatározott helyen Egy taszkhoz taszk kapun keresztül is hozzá lehet férni. A taszk kapu a call kapuhoz hasonló, annyi kivétellel, hogy a taszk kapu nem kódszegmenshez, hanem TSS-hez ad elérést, ha adható elérés. Megszakítás- és kivétel-kezelés A külső-, a szoftver megszakítások és a kivételek a megszakítás leíró táblán (IDT - Interrupt Description Table) keresztül vannak kezelve. Az IDT-t láthatjuk a fenti (21-es) ábrán Az IDT kapu leírók kollekcióját tartalmazza, melyek lehetőséget nyújtanak a megszakítás illetve kivétel kezelésre. Ahogy a GDT, úgy az IDT se szegmens (ugyanis nincs leírója, s ezáltal kiválasztója se). Az IDTR (IDT regiszter) tartalmazza a lineáris kezdőcímét illetve a hosszát Az IDT-ben elhelyezkedő leírók lehetnek mind megszakítás, csapda (tarp), vagy taszkkapu
típusúak. Ahhoz, hogy elérjük a megszakítást illetve a kivételt, a processzornak először a megszakítás vektort (megszakítás számot) kell fogadnia a belső hardvertől, vagy külső megszakítás vezérlőtől, vagy a programból a szoftver megszakítást, amit INT, INTO, INT 3 vagy BOUND parancsokkal adhatunk ki. Így a megszakítás vektor egy indexet fog jelenteni az IDT-ben egy kapu leíróra. Ha a kiválasztott kapu leíró egy megszakítás vagy csapda (trap) kapu akkor, a párosított kezelő rutin (amit a hívott indexű pl. megszakítás) a call kapun keresztüli meghíváshoz szinte teljesen hasonlóan fut le Ha a leíró taszk kapu volt, akkor a kiszolgáló rutin egy taszk átváltáson keresztül fog végrehajtódni. Memóriakezelés A processzor támogatja mind a közvetlen fizikai illetve mind a virtuális (lapozás, paging által történő) memóriakezelést. Amikor fizikai címzést használunk, akkor a lineáris címmel a processzor úgy bánik, mint a
fizikaival: egy az egybe. (Fontos: ne felejtsük el, hogy nem közvetlenül a memóriát címezzük, hanem a memória modellt, s ez a memória modell fogja a lineáris címet fizikaiként értelmezni, s csak ezen keresztül érjük el a memóriát. A védett módú memóriakezelés egy egész fejezetet kitesz, így ezzel részletesen csak később foglalkozunk.) Így, ha lapozást használunk (lapozás, paging: a memória területet szempontok alapján lapokra, egységekre bontják, ez segíti elő a virtuális memóriakezelést), tehát ha lapozást használunk, akkor akár az összes kód-, adat-, verem-, illetve rendszerszegmenst, a GDT-t és az IDT-t is lapozhatjuk (ezáltal háttértárolóra lementhetjük), s ezáltal csak a legfontosabb lapok maradnak bent a fizikai memóriában, míg a többi amíg nem használjuk, a HDD-n tárolódik. A lapokat, pontosabban fogalmazva a "lap kereteket" (pages; page frames) a fizikai memóriában két (bizonyos esetben látni fogjuk a
Pentium Pro-tól kezdve lehet három) különböző rendszer típusban találhatjuk meg: a lapcímtárakban (page directory) és laptáblázatok (page table) összességében. Mind kettőjük valahol a memóriában van, ezt a fenti ábra jól mutatja. A lapcímtár olyan elemekből épül fel, melyek tartalmazzák egy laptáblázat kezdődő fizikai címét, hozzáférési jogait, és memóriakezelési információkat. A laptáblázat elemei pedig az elérni kívánt lapnak (ugyanis a memória teljesen lapokra van osztva) a kezdő fizikai címét adja meg. Ehhez adjuk az ofszetet, s el is érhetjük a kívánt bájtot. (A lapozást a védett módú memóriakezelés fejezetben részletezzük) A lapcímtárnak a fizikai kezdő címe a CR3 vezérlő regiszterben (PDBR) van. Ahhoz, hogy ezt a lapozási mechanizmust használhassuk, a lineáris címet három részre osztották az architektúra tervezői, így szolgáltatva különálló ofszetet a lapcímtárba, a laptáblázatba és a
lapkereten belül. A rendszer állhat egy lapos lapcímtárból, vagy több laposból. Pl minden taszknak lehet saját lapcímtára Ez nyílván való, hisz a CR3 regiszter a TSS-ben el van mentve, és így taszk váltáskor megadhatunk új CR3-at, ezáltal új lapcímtárat. Rendszer regiszterek Részint a processzort támogatják initializálásban, illetve az operációs rendszert segítik kontrollálni az alább tárgyalt FLAG illetve rendszer regiszterek. • • • • • • A rendszer flagek (Mi az a flag? Egy vagy pár bitből álló jelző vagy vezérlő bit(cspoport). Ha ez illetve ezek a bitek be vannak kapcsolva, akkor jelzik, hogy ezt lehet vagy, nem lehet csinálni. Előfordul, hogy több biten vannak pl. számok letárolva, ilyen az IOPL, két bites 0-3-ig számokat tartalmazhat melyek, amit később tárgyaljuk a privilégium szintet tárolják.), tehát a rendszer flagek, illetve az IOPL az EFLAGS regiszterben kontrollálják a taszk váltást, a mód váltást
(valós - védett - virtuális valós), megszakítás kezelést, nyomkövetést - debugging, és a hozzáférési jogokat. Ezekről az EFLAGS regiszter tárgyalásakor alább ejtünk szót. A vezérlő regiszterek (CR - Controll Register), CR0, CR2, CR3 és CR4. Ezek a regiszterek meglehetősen sok rendszer szintű flaget, illetve adat mezőt tartalmaznak a rendszer szintű utasítások megvalósítása érdekében. Ezekről a vezérlő regisztereket tárgyaló részben beszélünk bővebben. (Hogy miért maradt ki a CR1? Ugyanis nem létezik, pontosabban fenntartott a későbbi fejlesztésekre.) Léteznek specifikus debug ("hibakereső") regiszterek is. A már említett GDTR, LDTR, illetve IDTR regiszterek a védett memória-kezelés leíró táblázatainak kezdő címét illetve méretét tartalmazza. Ezekről az EFLAGS regiszter után beszélünk A TR (Task Register) melyről már szót ejtettünk, az aktuális TSS lineáris kezdő címét illetve méretét tartalmazza.
Hasonlóan az EFLAGS után tárgyaljuk Vannak még az egyenlőre számunkra nem oly fontos modell (processzor) specifikus regiszterek (MSRs Model-Specific Register) melyek csak az operációs rendszer kernel-je által hozzá férhető, s leginkább csak számukra hordoz információt. Más rendszer erőforrások Az előbb említetteken kívül vannak még más nem tárgyalt rendszer regiszterek, illetve adat struktúrák melyek az alábbi rendszer feladatokat látják el: • • • Operációs rendszeri utasítások (LGTD, SGDT .) Teljesítmény, működés megfigyelő rendszer elemek, MSR függő. (Preformance-monitoring counters) Belső cache és bufferek. L1 cache, TLB illetve az L2 cache kezelése (Internal cache and buffers) A processzor működési módjai Az Intel által kifejlesztett, szabványos 32 bites x86-os architektúra a három alábbi működési módot illetve egy kvázi módot támogat: • Védett mód (Protected mode): Ez a processzor eredeti működési
módja. Ebben az üzemmódban használható az összes utasítás illetve architektúrai lehetőség, újítás. Ezen üzemmódban használhatja ki programunk leghatékonyabban a processzort, így ez a mód javasolt minden program illetve operációs rendszer számára. • Valós (címzésű) mód (Real-address mode): Ez az működési mód ez eredeti Intel 8086 processzor környezetét adja nekünk. Természetesen a csak valós módot használó programok nem lettek hatékonyabbak, mint egy 8086-os processzoron, csak gyorsabbak hisz ma, akár 2.2 GHz-cel is működhet egy iP4 valós módban. Ez a mód tartalmaz annyi kiegészítést, hogy használhatóak a valós módban is érvényes 286, 386, 486, illetve Pentiumos utasítások. Ilyen lehet a 32 bites operandusú műveletek, pl: ADD EAX, EBX, stb Vagy a 486-os BSWAP. Továbbá a legfontosabb a 386+-tól ismert módon a védett módba váltás lehetősége • Rendszer menedzselő mód (SMM - System management mode): A rendszer
menedzselő mód (SMM) már egy szabványosított lehetőség a 32 bites Intel Architektúrájú processzoroktól, az Intel386TM SL-től kezdődően. Ez adja meg az operációs rendszereknek azt a lehetőséget, hogy hasznosan, aktívan alkalmazzák az energia gazdálkodást. Ez annyit tesz, ha adott ideig "nem csinálunk semmit" akkor, elmenti az aktuális regiszterek értékét, és egy olyan módba kapcsol ahol jelentősen kisebb az órajel s csak arra figyel, hogy jön - e a visszatérésre, a munkába való visszakapcsolásra a jel. Ebbe a módba (SMM-be) külső megszakítás jel által kerülhet a processzor, ez az SMI# és ez fog rendszer menedzselő megszakítást (system management interrupt) generálni. Erre a processzor egy elkülönített címzési területre lép, míg el nem menti az éppen futó taszk adatait. SMM-ből való visszatérés után az SMI bekövetkezése előtti állapot áll helyre • Virtuális 8086-os mód (Virtual-8086 mode): Ez nem egy
létező - különálló működési mód, hanem egy kvázi üzemmód. Ez egy valós programot egy védett - multitaszkos környezetben tud futtatni Ennek az a jelentősége, hogy a valós program számára az operációs rendszer egy virtuális, látszólagos valós módot szimulál. A valós program ebből a környezetből annyit lát, mintha tényleg csak az övé lenne az egész processzor, holott a védett operációs rendszer felügyelete alatt áll. Ezt a lehetőséget biztosítja rendszer szinten a processzor az operációs rendszernek. 2.2 ábra: A processzor működési módjai közötti átjárás A processzor bekapcsolás vagy reszet után valós módba kerül. Védett módba a PE flag egyre állításával kerülhetünk Ez a flag a CR0 regiszter 0. bitje Hasonló analógia alapján ezen bit 0-ra állításával kerülhetünk vissza valós módba (Lásd 2.2-es ábra) Mielőtt a tisztelt Olvasó rögtön rohanna ezen bitet 1-re állítani meg kell jegyezni, hogy ez nem ily
egyszerű. Ugyanis rengeteg körülményt kell megteremteni ahhoz, hogy átválthassunk védett módba, e körülmények nélkül gyönyörű reszetet vagy lefagyást kreálunk. A védett módba kapcsolás, illetve ennek példaprogramja egy másik fejezet témája. A következő kérdés, hogy virtuális 8086-os módba hogyan kerülhetünk. Nyílván védett módban kell lennünk, hogy virtuális 8086-osba kerülhessünk. Ennek megvalósítása a már sokat emlegetett EFLAGS regiszter VM flagjével oldható meg. (Hasonlóan, ha 1-be vált és adottak a feltételek, akkor lép virtuális 8086-ba illetve ha 0-ba, akkor onnan ki) (Lásd ábra.) A processzor megtervezésének szépsége az, hogy taszk váltáskor a TSS-ben az ELFAGS regiszter is benne van, így ha a másik taszknak virtuális 8086-os kvázi üzemmódban kell mennie akkor ez automatikusan megtörténik. (Amikor ilyen taszkra váltunk akkor az EFLAGS által bekapcsolódik, illetve amikor innen elváltunk akkor pedig
kikapcsolódik.) Megjegyzem, hogy az SMM-ből való visszatérés után nyilvánvaló, hogy olyan módba tér vissza mely az SMI bekövetkezése előtt volt. (RSM által tér vissza, amit az ábra is mutatja) Az EFLAGS regiszter felépítése A 32 bites EFLAGS regiszter egy csoport állapot flaget (jelző bitet), egy csoport vezérlő bitet, és egy csoport rendszer bitet tartalmaz. Az alábbi (22-es) ábra definiálja az egyes jelzőbitek helyét a regiszterben Az inicializációs folyamat után az EFLAGS regiszter értéke 00000002h. Az 1, 3, 5, 15, 22 és 31 közötti bitek fenntartottak, így azok megváltoztatása, programból történő használata nem javasolt. Az EFLAGS regiszter egyes flagjei közvetlen módon állíthatóak az alább ismertetett speciális utasításokkal. Nincs olyan utasítás melynek segítségével közvetlenül az egész regiszter tartalmát vizsgálhatnánk, vagy átállíthatnánk. Azonban az alábbi trükkel, megoldható a regiszter értékének
tetszőleges állítása. Az alábbi utasítások lehetővé teszik, hogy adott flageket a program rutinjának vermébe vagy az EAX regiszterbe írjunk, illetve fordítva, vagy a veremből illetve az EAX regiszterből írjuk az EFLAGS regiszterbe. Az utasítások a következőek: LAHF, SAHF, PUSHF, PUSHFD, POPF, és POPFD. Miután az EAX regiszter tartalmát a verembe vagy az EAX regiszterbe mentettük lehetőség nyílik, hogy a processzor bit manipuláló utasításaival állítsuk át a flageket (BT, BTS, BTR, és BTC utasítások). Amikor az egyik taszk futását megszakítjuk, akkor a processzor automatikusan elmenti az EFLAGS regiszter értékét a taszk állapot szegmensbe (TSS). Amikor vissza kívánunk térni a megszakított taszkhoz, akkor a processzor egyszerűen visszaírja az EFLAGS regiszterbe a TSS-ben elmentett értéket. Amikor egy megszakítás vagy kivétel meghívódik, a processzor automatikusan elmenti a rutin verem területén az EFLAGS regiszter értékét.
Amikor a megszakítás vagy kivétel lekezelése taszkváltással jár, akkor természetesen az aktuális EFLAGS regiszter értéke a TSS-ben lesz eltárolva. Lássuk az EFLAGS regiszter felépítésének ábráját: 2.3 ábra: Az EFLAGS regiszter felépítése A 32 bites x86-os architektúra fejlődésével mindig újabb és újabb flagek adódtak az először csak 16 bites FLAGS regiszterhez, majd az Intel386-tól 32 bitesnek definiált EFLAGS regiszterhez. A kompatibilitás megőrzése végett természetesen egy előző processzorcsaládban definiált flagek a következőben sem változnak meg. Fontos továbbá, hogy a fenntartottnak jelzett biteket ne használhatjuk programainkban, hisz ezek funkciója bármikor megváltozhat. • Bit 0: Átvitel flag (CF - Carry Flag): ez akkor 1, ha akármilyen aritmetikai művelet eredménye nem volt ábrázolható az adott adat struktúrán, pl: MOV AX, FFFFh ; ADD AX, 2. Így ekkor a CF flag egy a legfelső fölötti bitként szolgál.
(Csak akkor használt ha elég az egy bitnyi kiterjesztés) A CF értéke állítható a POPF, POPFD, IRET utasításokkal a megfelelő módon, illetve egyszerűbb a CLC (CF=0), vagy CMC (CF= NOT CF), vagy STC (CF=1). • Bit 1: nem használt, fenntartott • Bit 2: Párosságot jelző flag (PF - Parity Flag): Ha az utolsó aritmetikai művelet eredménye páros, akkor 1. • Bit 3: nem használt, fenntartott • Bit 4: Fél-átvitel flag (AF - Adjust Flag): értéke egy ha BCD aritmetikai művelet után a 3. bit felett átvitel illetve alulcsordulás volt • Bit 5: nem használt, fenntartott • Bit 6: Nulla flag (ZF - Zero Flag): Ha egy művelet eredmény nulla volt • Bit 7: Előjel flag (SF - Sign Flag): Előjeles, egész számok használata esetén: jelentése egyenértékű a legnagyobb helyiértékű bittel ami nem más, mint az előjel bit. Tehát az elvégzett művelet eredményének előjelét adja meg, ha az előjeles egész szám volt. • Bit 8: Csapda
flag (TF - Trap Flag): Engedélyezi a lépésenkénti végrehajtást. Ez úgy valósul meg, hogy minden utasítás után egy debug kivételt generál, így megfigyelhetjük mi történt a végrehajtott utasítás után. Ezt beállítani a POPF - PUSHF illetve IRET utasításokkal lehet. Ha egyre állítottuk ezen bitet, rögtön debug kivétel képződik. • Bit 9: Megszakítás flag (IF - Interrupt Flag): Itt lehet állítani a maszkolható megszakítások engedélyezését. (Mi az, hogy maszkolható megszakítás? A megszakítás fogalmát ismertnek tekintjük, így: a megszakítások két részre bonthatók a maszkolhatókra illetve a nem maszkolhatóakra. A nem maszkolható megszakítás (NMI) olyan eseményt jelez a processzornak ami nem tűr halasztást, pl. memória, vagy periféria hiba. A maszkolható megszakítás pedig hardver által kezdeményezett megszakítás Mivel ezek maszkolhatóak, egy megszakítás várakozási sorba kerül, s jelentőssége, prioritása
szerint lesz sorba helyezve. Így ha fontosabb, akkor az előbb beérkezett megszakítás előtt is végrehajtódhat Nos ezzel a bittel az utóbb említett maszkolható megszakítások engedélyezését lehet beállítani.) Nos általában ez a bit engedélyezett, csak akkor szoktuk letiltani, ha épp pl.: egy saját szoftver megszakítást - egy ilyen rutint futtatunk. Ezen rutint nem szakíthatják félbe egyéb maszkolható megszakítások, mert számos probléma lehetne belőle. Azt, hogy ez a bit módosítható -e az éppen futó taszk - program számára azt a alább tárgyalt IOPL, CPL, illetve VME flag (VME CR4-ben) együttes hatása dönti el. A módosítás a CLI (nem engedélyezi a maszkolható megszakításokat), STI (engedélyezi), POPF, POPFD vagy IRET által beállítandó (E)FLAG regiszter ezen adott bitjének értéke szerint. • Bit 10: Irány flag (DF - Direction Flag): A string (karakterlánc, bájtsorozat) műveletek (MOVS, CMPS, SCAS, LODS, és STOS) irányát
állítja. Ha DF = 1 akkor automatikusan csökken a cím érték (ez a magasabb címtől alacsonyabb felé haladás), ha DF = 0 akkor automatikus növekedés (alacsonyabb címtől a magasabb felé). Az STD (ez állítja DF = 1) illetve CLD (ez állítja DF = 0) parancsokkal illetve a már említett POPF, POPFD és IRET utasításokkal állítható be a bit értéke. • Bit 11: Túlcsordulás flag (OF - Overflow Flag): Ha a művelet eredménye túl nagy pozitív szám, vagy túl kicsi negatív akkor értéke = 1, egyébként 0. • Bit 12 - Bit 13: I/O privilégium szint mező (I/O privilege level field): Az éppen futó taszk vagy program I/O privilégium szintjét (IOPL) adja meg. A CPL (CPL - Current Privilege Level; aktuális privilégium szint), tehát a CPL-nek kisebb vagy egyenlőnek kell lennie az IOPL-lel, hogy a taszk vagy program hozzáférjen a I/O porthoz. (Kisebb vagy egyenlő CPL jelenti, hogy magasabb, a kernelhez közelebbi szinten van a taszk vagy program.) Ez
IOPL értékét csak a kernel (CPL = 0) tudja állítani a POPF vagy IRET utasítással. • Bit 14: Beágyazott taszk flag (NT - Nested task flag): Meghatározza a vagy megszakítás, vagy call által meghívott taszkok változását. Ha CALL vagy megszakítás, vagy kivétel miatt került meghívásra az új taszk akkor a processzor az NT flaget 1-re állítja, s elmenti a TSS előző taszk mutató mezőjébe (previous task link) az elhagyandó taszk TSS kiválasztóját. Ha az új taszkból IRET-tel lép ki a program és az NT flag = 1 akkor, a processzor az elmentett TSS kiválasztót tölti a TR-be (taszk regiszter), és azt futtatja. Ez addig folyatódik, míg a legfelső szintű taszkhoz nem érünk el ahol az NT = 0. Ezt lehet egy zárt láncra is alkalmazni, de a preemptiv operációs rendszerek általában nem hagyják szépen lassan a teljes programot lefutni, illetve nem várják meg az IRET-et, mert addig a többi taszk nem tudná végezni munkáját, hanem az
időosztás elve szerint mindegyik taszk a prioritásának megfelelően fut kicsit majd átvált egy másik taszkra. Azonban ha JMPvel ugrottunk a taszkra, akkor az NT flag értékét a processzor 0-ra állítja, ezáltal megszüntetve a láncot Az NT flag értékét a POF, POPFD vagy IRET utasítással állíthatjuk át, ugyanakkor ezt körültekintéssel tegyük, mert váratlan kivételt (unexcepted exception) okozhat az alkalmazásban. Alábbi ábránk az egymásba ágyazott taszkokra mutat példát: 2.4 ábra: Egymásba ágyazott végrehajtású taszkok • Bit 15: nem használt, fenntartott • Bit 16: Folytatás flag (RF - Resume Flag): Ha beállított, átmenetileg letiltja a lépésenkénti végrehajtásra szolgáló debug kivétel (#DE - debug exception) generálását. Ha 0, akkor minden utasítás után generálható debug kivétel. Az elsődleges funkciója az utasítás újra végrehajtása (folytatása) - pl hiba esetén - anélkül, hogy újabb #DE-t okozna a
végrehajtáshoz szükséges IRETD utasítás. • Bit 17: Virtuális 8086-os mód (VM - Virtual-8086 mode): Ennek egyre állításával léphetünk virtuális 8086-os módba ha a processzor már védett módban van és megfelelőek a körülmények. Ennek 0-ra állításával e módból a sima valósba visszalépés történik meg. • Bit 18: Illeszkedés ellenőrzés (AC - Alignment check): Ennek és a CR0-ban lévő AM flagnek az 1-re állításával kapcsolhatjuk be a memória referenciák ellenőrzését. Ennek és/vagy a CR0AM-nek a 0-ra állításával az illeszkedés ellenőrzés letiltás állítható be. Kivétel képződik ha egy word cím nem páros (osztható kettővel), vagy egy doubleword cím nem osztható néggyel. A kivétel csak a felhasználói programokat érinti (amelyeknek a privilégium szintje = 3). Az ilyen jellegű csoportosítás főként a multi-processzoros rendszerekben: két processzor közötti adat cserekor, vagy speciális pointer-táblázatok
kezelésekor nyújt könnyedséget. • Bit 19: Virtuális megszakítás (VIF - Virtual Interrupt Flag): A már tárgyalt IF flag virtuális mását tartalmazza. A VIF flag a következő VIP flaggel együtt használt A processzor a futtatásban akkor veszi csak figyelembe a VIF flaget, ha a CR4-ben a VME vagy a PVI flag egyre állított és az IOPL kisebb, mint 3. • Bit 20: Virtuális megszakítás-felfüggesztés (Virtual interrupt pending): A szoftver által beállított flag melynek jelentése, hogy egy megszakítás függőben van. Ha 0, akkor nincs függőben lévő megszakítás Ahogy előbb említettük, a VIF flaggel együtt használt, s ugyanúgy vonatkozik rá, hogy: az IOPL-nek kisebb vagy egyenlőnek kell lennie 3-mal, illetve a CR4-ben vagy a VME vagy PVI biteknek bekapcsoltnak kell lenniük. A VIP flaget a processzor mindig csak kiolvassa, de nem módosítja. • Bit 21: Azonosítás (ID - Identification): Az újabb processzorok rendelkeznek azzal a
lehetőséggel, hogy információt adjanak típusukról, gyártójukról, illetve az általuk támogatott speciális műveletekről. Ez a bit arra szolgál, hogy megtudjuk rendelkezik -e a processzor lekérdezhető információval. Ha értéke egy, akkor támogatja a CPUID utasítást, ellenkező esetben nem. • Bit 22 - Bit 31: nem használt, fenntartott Memória kezelő regiszterek A processzor négy memória kezelő regisztert (GDTR, LDTR, IDTR, és TR) alkalmaz, hogy megadja azoknak az adat struktúráknak (leíró táblázatoknak) a címét és méretét melyek a szegmentált memóriakezelésért felelősek. Speciális rendszer utasítások felelősek ezen regiszterekbe való írás illetve olvasásért. 2.5 ábra: A memória menedzselő regiszterek felépítése Globális leíró tábla regiszter A GDTR (GDTR - Global Descriptor Table Register; Globális leíró tábla regiszter) tartalmazza a GDT (globális leíró tábla) 32 bites lineáris kezdő címét, és egy 16
bites tábla limitet, avagy a GDT bájtban mért hosszát. A kezdő cím a lineáris 0-tól számított bájtban mért cím, míg a hossz bájtban mért hosszt adja meg. A kezdőcímet érdemes néggyel osztható címre helyezni (sőt lehet 4096-tal osztható címre is). A LGDT utasítás betölti az operandusban megadott 6 bájtot a GDTR regiszterbe. A fenti ábrán látható, hogy hogyan kell felépíteni a betöltendő 6 bájtot: a felső 32 bitre a lineáris cím, míg az alsó 16-ra pedig a limitnek kell kerülnie. A SGDT hasonló logika alapján a megadott címre menti el ezt a bizonyos 6 bájtot. Bekapcsolás vagy reszet után a GDTR 32 bites címe 0-ra illetve a limit 0FFFFh-ra van állítva. Ezt nekünk kell beállítani, ha védett módba akarunk lépni Lokális leíró tábla regiszter Amint az ábrából is láthatjuk más a felépítése a LDTR regiszternek (LDTR - Local Descriptor Table Register; Lokális leíró tábla regiszter). Két részből áll ezen regiszter
Az egyik a rendszer regiszter, amit mi magunk is beállíthatunk a másik, amihez nem férhetünk hozzá egy rejtett rész (angol terminológia szerint a hidden-part). A látható rész egy 16 bites szegmens kiválasztó, míg a rejtett rész tartalmaz egy 32 bites kezdő címet, 16 bites limitet, majd leíró attribútumokat az LDT számára. A rejtett résszel nem kell törődnünk, hisz úgy sem férünk hozzá Az LLDT és az SLDT utasításokkal tölthetjük be, illetve kérdezhetjük le az LDT-t leíró kiválasztó értékét. Tehát ezen utasítások használatakor csak a látható rész, a 16 bites szegmens kiválasztót kell megadnunk, illetve az SLDT-nél csak ezt kapjuk vissza. Hogy miért van a láthatatlan rész? A lényege az, hogy az LLDT utasítás után, ahogy betöltöttük az új szegmens kiválasztót a LDTR látható részébe, a processzor automatikusan feltölti az LDTR rejtett részét a GDT-ből. Részletezve: tegyük fel, hogy elkészítettük a rendszer
szegmens leírót (system-segment descriptor) az LDT számára, a szükséges megfontolások szerint. (Megadtunk mindent, amit egy ilyen leírónál kell, így az ott helyezkedik el minden információval a GDT-ben, s annak már egy eleme, leírója.) Most már csak hivatkozni kell rá, s betöltjük az LDTR-be. Ekkor mi csak a 16 bites kiválasztót adjuk meg, s a processzor a rejtett részt a kiválasztó ismerete által feltölti, avagy bemásolja a rejtett részbe a leírót egy az egybe. Erre azért van szükség, hogy minél gyorsabban meglegyen az LDT lineáris címe, ugyanis amikor az LDT egyik leírójára hivatkozunk gyorsan, kell elő állítani, hogy hol is kezdődik az LDT. Így nem kell mindig a GDT-hez nyúlni, hogy hol kezdődik az LDT, és csak azután az LDT egyik leíróját kiolvasni, s csak a végén a kívánt leíró által prezentált memória területet, szegmenst elérni. Ehelyett gyorsabban elérhetjük. De ne higgyük, hogy csak ez a regiszter áll két
részből, hasonlóan működik az összes szegmens regiszter. (CS, DS, stb) Amikor taszk váltás történik, az új taszk LDT kiválasztója automatikusan betöltődik a TSS-ből. (Hasonlóan a régi is elmentődik a régi TSS-ébe.) Ezáltal minden taszknak saját LDT-je lehet (Persze ne felejtsük el, ha LLDT utasítással írunk az LDTR-be akkor a régi elveszik.) Bekapcsoláskor vagy reszet után a processzor az LDTR szegmens kiválasztóját és 32 bites lineáris címét 0-ra állítja, míg a limitet 0FFFFh-ra a GDT-hez hasonlóan. Megszakítás leíró tábla regiszter Az ábráról is láthatjuk, hogy az IDTR (IDTR - Interrupt Descriptor Table Register; Megszakítás leíró tábla regiszter) az GDTR-hez hasonlítható. (Az előbb tárgyaltak szerint az IDT-re is elmondhatjuk, hogy nem szegmens, ugyanis nincs leíró, pontosabban kiválasztó mellyel hivatkozhatnánk rá.) Az IDTR a GDTR-hez hasonlóan egy 32 bites lineáris tábla kezdő címből illetve ezen tábla 16
biten tárolt méretét tartalmazó részből áll. (Minden a GDTR hasonlatára) Az LIDT illetve SIDT utasításokkal írhatunk bele, illetve olvashatjuk ki értékét. Itt is 6 bájtos tábla cím-limit adatot vár az LIDT illetve ad vissza az SIDT. Bekapcsoláskor illetve reszet után a lineáris kezdő cím 0, a limit - a tábla mérete bájtban pedig 0FFFFh (Megjegyezném, hogy valós módban - azért hogy a processzor megőrizze a kompatibilitást, s rugalmasabb legyen a felépítés - az IDTR regiszter használt méghozzá 32 bites lineáris címe 0, limitje a kiszámolható 256 * 4 bájt = 1024 bájt. (256 elem, 4 bájt egy megszakítás vektor, mely a megszakítást végző 20 (+1) bites rutin fizikai címe) 1024 bájt, az-az 3FFh.) Taszk regiszter Az ábra alapján a TR (TR - Task Register; Taszk regiszter) pedig az LDTR-rel hozható rokonságba. Hasonlóan egy 16 bites szegmens kiválasztóból áll a látható rész, míg a rejtett a 32 bites lineáris kezdő címből, 16
bit bájtbeli tábla méretből, illetve a szegmens leíró által tárolt attribútumokból. A TR mindig az éppen aktuális taszk TSS kiválasztóját tartalmazza. Ez a kiválasztó a GDT-ben egy rendszer szegmens leíróra mutat, (ahogy ezt az előzőekben láttuk) amit az LDTR-ben az elmondottak szerint, csak létre kell hozni, s ezek után hivatkozhatunk is rá. A jól megszokott logika alapján az LTR utasítással írhatunk a TR-be, illetve onnan az STR-rel olvashatjuk ki az ott tárolt kiválasztót. (Mivel csak kiválasztó van a látható részben, ezért csak azt kell lementeni, illetve a TR-be tölteni) Az LTR után a GDT-ből a kiválasztott leíró adatai a TR rejtett részébe töltődik automatikusan. Bekapcsolás után a szegmens kiválasztó a TR látható részében 0, illetve a rejtett részben a lineáris kezdő cím 0, és a limit 0FFFFh. Amikor taszk váltás történik a TR-be kerül az új taszk TSS kiválasztója, míg a régi taszk TSS kiválasztója az új
taszk TSS-ének előző taszk mutató (previous task link) mezőjébe kerül. (Fent láttuk, hogy a taszkok egymásbaágyazásakor ennek feltétele az EFLAGS regiszter NT flagjének 1 értéke) A TR-be történt kiválasztó írás után a fent elmondottak szerint megtörténik a rejtett rész feltöltése. Vezérlő regiszterek A vezérlő regiszterek (CR - Control Registers; Vezérlő regiszterek) CR0, CR1, CR2, CR3, és CR4 meghatározzák a processzor működését, működésének módját, illetve az éppen futó taszk viselkedését illetve a taszk lehetőségeit. 2.6 árba: Vezérlő regiszterek • CR0 - rendszer vezérlő flageket tartalmaz melyek vezérlik a processzor állapotát illetve működési módját. • CR1 - fenntartott illetve nem létezik • CR2 - laphiba (page-fault) kivétel bekövetkezése után azt a pontos lineáris címet tartalmazza, mely a laphibát kiváltotta. • CR3 - tartalmazza az lapcímtár (page directory) fizikai kezdőcímét
illetve két flag bitet (PCD és PWT). Ezt a kontrol regisztert gyakran emlegetjük PDBR-nek (Page Directory Base Register) is. A felső 20 bit tartalmazza az lapcímtár fizikai kezdőcímét. A kezdőcím 20, és nem 32 bites, ugyanis a lapcímtár kezdőcímét 4 KB-ra (4096 bájtra, lapkeret méretre) kell igazítani. Így 0 bájttól kezdődik a címzés, illetve ami a 20 bites címben 1 az a fizikai memóriában a 4096. bájtot jelenti A PCD és PWT flagek befolyásolják a lapcímtár illetve a processzor belső adat cache-elését. (A nem tárgyalt bitek, ahogy az ábrán látható 0 értéket tartalmaznak) • CR4 -számos flaget tartalmaz melyek a processzor architektúrai bővítéseit írják le. (Pl ha adott bit be van kapcsolva akkor az a processzor támogatja azt a fejlesztést.) Védett módban, hogy vezérlő regiszterből olvassunk vagy ebbe írjunk a MOV utasítás szolgál. A védelem értelmében ezen regiszterek kiolvasása bármely privilégium szinten
megengedett (privilégium szint 0-3), az írása pedig csak a 0s privilégium szinten. Tehát az applikációk - taszkok olvashatják ezen regisztereket, de írni nem tudják Pl ugyanis ki akarhatja olvasni az applikáció a CR0 regiszter adott bitjéből, hogy van -e FPU. A fenntartott biteket nem lehet átírni, mindig az előbb kiolvasott értékekkel rendelkeznek. A vezérlő regiszterek adott bitjeinek leírása: • CR0, bit 31: Lapozás (PG - Paging): ha bekapcsolt a lapozást (paging) engedélyezi, ha kikapcsolt tiltja. Ha kikapcsolt az összes lineáris cím fizikai memória címet jelent! A lapozást csak védett módban használhatjuk (CR0.PE = 1) Ha valós módban akarnánk használni általános védelmi hibát (#GE - general protection error) kapunk. • CR0, bit 30: Cache tiltása (CD - Cache Disable): amikor ez a flag (CD) illetve a következő NW flag kikapcsolt - 0 - akkor az egész fizikai memória mind belső és külső cache-elése engedélyezett. Az alábbi
táblázat foglalja össze röviden e két bit kombinációi által kialakítható cache-elést. • CR0, bit 29: Nem átíró (NW - Not Write-through): Ha a fenti CD illetve ez az NW flag 0, akkor write-back üzemmódban van a processzor (a Pentium®, illetve P6 család processzorai), vagy write-through üzemmódban (az Intel486™ processzor). Cache működési módok: CD NW 0 0 Cache-elés illetve írás / olvasási mód L1 L2 Így ad a cache-elés legnagyobb teljesítményt. - Az olvasási találatok (read hit) elérhetőek a cache-ben, az eredménytelen keresés (read miss) után pedig a memóriából Igen Igen Igen Igen - Az írási találat frissíti a cache-t Igen Igen - (Pentium® illetve P6 processzoroknál) az írás elhibázása után frissítődik a memória Igen - (P6 processzorok) Az írás elhibázása (write miss) cache vonal betöltést (cache line fill) okoz; az írás sikeressége (write hit) több processzor esetén az eddig megosztott cache
vonalakat kizárólagossá teszi az MTRR kontrolja alatt. - (Intel486™) Minden írás frissíti a memóriát is, az írás hiba nem okozza a cache vonalak betöltését (cache line fill) - Az Invalid (érvénytelen) cache beállítás megengedett az MESI értelmében. Igen - A külső snoop megengedett. Igen Igen Igen Igen 0 1 Hibás beállítás, általános védelmi hibát (general-protection error) ad vissza 0-s hibakóddal NA NA 1 0 A memória koherencia fenntartott. Igen Igen - Az olvasás sikeressége esetén a cache-ből olvasunk, az olvasás elhibázásakor nincs helyettesítés Igen Igen - Az írás elhibázásakor a cache frissítve van Igen - (P5 és P6) Csak megosztott cache vonalra történő írás, illetve írás elhibázás frissíti a fizikai memóriát - (Intel486™) Minden írás egyben frissíti a memóriát WT# - (P5) Az írás találat (write hit) a megosztott (shared) cache vonalakat kizárólagossá (excusive) teszi az WB/WT# kontrolja
által - Az Invalid (érvénytelen) cache beállítás megengedett az MESI értelmében. Igen - A külső snoop megengedett. Igen Igen Igen Igen 1 1 A memória koherencia nincs fenntartva, ez a bekapcsolás vagy reszet utáni állapot: Igen Igen - Olvasási találat után a cache-hez fordul, e hiányában nincs helyettesítés - Írási találat frissíti a cache-t. Igen Igen Igen Igen - Írási találat hiányában a memóriához fordul 1.1 táblázat: Cache működési módok Magyarázat: cache-elési terminológa Az Intel Architektúrájú processzorok a Pentiummal kezdődően a MESI (modified, exclusive, share, invalid) cache protokollt használják, hogy fenntartsák a koherenciát a belső illetve más processzorok cache-eivel. Az alábbi események fordulnak elő a működés során: • Cache line fill: amikor a processzor érzékeli, hogy a memóriából olvasunk egy operandust akkor a memória azon részét beolvassa a cache-be (L1, L2, vagy
mindkettőbe). Ezt úgy teszi, hogy egy a 32 bájtos (256 bites) cache vonalat felhasználva beolvassa az adott 32 bájtot, 4 órajel ciklussal. (Ugyanis a rendszersín 64 bites) Ezt nevezzük cache line fill-nek. • Cache/read hit: Ha abból a memória területből olvasunk, ami már cache-elve van, akkor az olvasáskor a processzor már a cache-ből olvashatja, s nem kell a memóriára várni. Write hit: Amikor a processzor a memória egyik cache-elhető területébe kíván írni akkor először megvizsgálja a cache-t létezik -e a cache-ben az a memória rész. Ha létezik a valós megfelelő cache vonal akkor, ide a cache-be lehet írni ahelyett, hogy a memóriába kellene. (Persze ez a nem write through, WT#-ra is igaz, hisz az csak átír.) • • Write miss: Ha nincs annak a memória területnek cache vonal foglalva, akkor cache line fill történik, így lefoglalódik az írás számára a cache vonal. Lássuk a különböző processzorok cache-einek
karakterisztikáját: Cache vagy buffer Karakterisztika L1 Utasítás cache - P6 vagy P5 processzorok: 8 vagy 16 KBájtos, 4 utas asszociatív, 32 bájtos cache line (régi Pentiumok 2 utas t. asszociatív) - Intel486™: 8 vagy 16 KBájt 4 utas asszociatív, 16 Bájt cache line, az utasítás illetve adat cache e 8 v. 16 KBájtban van együtt L1 Adat cache - a P6 családba tartozó processzorok: 8 vagy 16 KBájt, 2 utas asszociatív, 32 bájtos cache line szélesség. - Pentium processzor: 8 vagy 16 KBájt, 4 utas asszociatív 32 bájt cache line, régebbi Pentiumnak: 2 utas asszoc. - Intel486™: lásd L1 utasítás cache L2 cache - P6 processzorok: 256, 512 KBájt vagy 1 MBájt 4 utas asszociatív, 32 bájtos cache line-nal (1 MB L2 cache = super cache) - Pentium: rendszer - alaplap függő, ált: 256 - 512 KBájt, 4 utas asszociatív, 32 bájt cache line méret - Intel486™: rendszer függő (ált. 256 KBájt) 1.2 táblázat: 32 bites, x86-os processzorok cache-einek
jellemzői • CR0, bit 18: Illesztés vizsgálat (AM - Allignment Mask): erről már beszéltünk az EFLAGS AC flagjánál. Ha AM = 1, akkor automatikus illesztés vizsgálat történik, egyébként nem. Ennek az automatikus illesztés vizsgáltnak a feltétele, hogy a már említett AC flag is egyenlő legyen 1-gyel, a CPL = 3 (felhasználói programok), illetve a védett, vagy virtual8086-os módban legyen. • CR0, bit 16: Írásvédelem (WP - Write protect): Letiltja a kernel szintű rutinoknak, hogy írhassanak a felhasználói szintű read-only lapokba, ha WP = 1. Ha WP = 0 akkor a kernel írhat a felhasználói read-only lapokba. • CR0, bit 5: Numerikus hiba (NE - Numeric Error): Ha bekapcsolt engedélyezi az belső - natív mechanizmust, mely jelzi az FPU hibákat, ha kikapcsolt akkor a PC-stílusú FPU hibajelzés lesz a használt. • CR0, bit 4: Kibővítés flag (ET - Extension Type): A P5 illetve P6 processzorokban ez fenntartott bit értéke 1. Az Intel386™
illetve Intel486™ processzorokban arra szolgál, hogy jelezze (ha értéke egy), támogatja -e a Intel 387 DX utasításokat. • CR0, bit 3: Taszk kapcsolás bit (TS - Task Switched): Ha bekapcsolt, elősegíti elmenteni az FPU állapotát taszk váltáskor, és addig nem enged az FPU-hoz férni amíg el nem mentettük a tartalmát. Ha a mentés ideje alatt akar az új taszk hozzáférni, akkor az eszköz nem hozzáférhető (#NM - device-not-avaible) kivétel generálódik. (Az FPU állapotának lementése kicsit összetett három biten alapul: EM, MP, TS. Bővebben a szakirodalomban.) • CR0, bit 2: Emuláció (EM - Emulation): Ha bekapcsolt jelzi, hogy nincs a processzornak se belső se külső koprocesszorra. Ha ilyenkor (EM=1) FPU utasítást akarunk végrehajtani, akkor az eszköz nem hozzáférhető (#NM) kivételt kapunk. Ha kikapcsolt jelen van az FPU (Ha nincs FPU jelen, akkor emulációval lehet helyettesíteni, ekkor az összefüggő EM, MP, TS flageket megfelelően
kell beállítani.) • CR0, bit 1: Koprocesszor felügyeletét szolgáló bit (MP - Monitor Coprocessor): Kontrolálja a WAIT utasítás (vagy FWAIT) hatását az FPU-ra. (TS és EM flagekkel együtt) • CR0, bit 0: Védett mód engedélyezése (PE - Protection Enable): A védett módba kapcsolhatunk, ha PE = 1 (megfelelő előkészületek után). Amennyiben PE = 0 akkor valós címzésű módot (~valós módot) használunk. (Ahhoz, hogy még a lapozást is használjuk, a PE = 1-re állítása után még PG-t is 1-re kell állítani.) • CR3, bit 4: Lap szintű cache tiltása (PCD - Page-level Cache Disable): Az adott lapcímtárak cacheelését szabályozza. Ha bekapcsolt, tiltott a cache-elés, ha kikapcsolt engedélyezett Ez csak a processzoron lévő cache-re van hatással (L1, és ha van L2). Ezen bitnek nyílván csak akkor van jelentősége, ha a lapozás használt (PE = 1 és PG = 1) és a CD (cache disable) flag a CR0-ban nem bekapcsolt. • CR3, bit 3: Lap
szintű átlátszó átírtás (PWT - Page-level Writes Transparent): Beállítja, hogy writethrough vagy write-back cache-elést használjon az adott lapcímtárra. (~A write-through csak a cache-en keresztül ír, olvas, míg a write-back csak akkor nyúl a memóriához, ha szükséges.) • CR4, bit 0: Virtuális 8086-os mód engedélyezése (VME - Virtual 8086 Mode Extension): Ha bekapcsolt engedélyezi virtual8086-os módban a megszakítás- és kivétel- kezelésre vonatkozó kiterjesztést. Ez a bővítmény nem használható, ha ezen bit 0. Ez a kiterjesztés meggyorsíthatja a 8086-os program futását, ugyanis nem a virtual8086-os monitort kell a megszakítások kezeléséért meghívni, hanem maga a 8086-os program (inkább környezet) kezeli le megszakításait. Egyszerűen átirányítjuk hozzá a megszakításokat A VME bittel a már tárgyalt VIF (Virtual Interrupt Flag) bit is összefüggésben van, együttesen a megbízhatóbb működést segítik elő a multitaszkos
környezetben. • CR4, bit 1: Védett módú virtuális megszakítások (PVI - Protected-Mode Virtual Interrupts): Ha bekapcsolt engedélyezi védett módban a VIF (Virtual Interrupt Flag) működését. Ha kikapcsolt tiltja • CR4, bit 2: Time-stamp számláló olvasás tiltása (TSD - Time Stamp Disable): Ha bekapcsolt az RDTSC utasítás csak kernel privilégium szinten hajtható végre, ha kikapcsolt, akkor bármely privilégium szinten futtatható. • CR4, bit 3: Hibakeresés kiterjesztés bit (DE - Debugging Extension): A DR4 és DR5 debug regiszterre hivatkozik egy nem meghatározott utasítás (#UD - undefinied opcode) kivétel esetén, ha ez a bit bekapcsolt. Ha kikapcsolt nem hivatkozik rá a szoftver kompatibilitás megtartása végett. • CR4, bit 4: Lapméret kiterjesztésének bitje (PSE - Page Size Extension): Engedélyezi a 4 MB-os lapok használatát, ha bekapcsolt. Ha PSE flag = 0, akkor csak a 4 KBájtos lapokat használhatjuk (Működésének feltétele,
hogy a lapozás működőképes legyen.) • CR4, bit 5: Fizikai címkiterjesztés bitje (PAE - Physical Address Extension): Ez a P6 családtól használható csak, előző processzorokban fenntartott. Ha bekapcsolt akkor, engedélyezett a 36 bites fizikai címzés (így 64 GB fizikai memória címezhető), ha kikapcsolt akkor a 32 bites alap címzést használjuk. (4 GB fizikai memória címezhető). • CR4, bit 6: Géptípus ellenőrzés engedélyezése (MCE - Machine-Check Enable): Bekapcsolt állapota esetén engedélyezi a gép-vizsgálati kivétel (machine-check exception) lefutását. Ha kikapcsolt, akkor nem • CR4, bit 7: Globális lap engedélyezése (PGE - Page Global Enable): (P6+) Ha ez a bit bekapcsolt engedélyezi a globális lap lehetőséget, mely annyit tesz, hogy a legtöbbet használt illetve megosztott lapokat globálissá, mindenki által elérhetővé tehetjük, a lapcímtár, vagy laptáblázat adott bejegyzésének 8. bitjének, a global bit
bekapcsolásával. Amennyiben kikapcsolt a PGE bit ez a lehetőség nem használható (Megjegyzendő, hogy az így globálisnak deklarált lapok lapbejegyzései (lapcímtár vagy laptár bejegyzései) taszk váltás, illetve a CR3 regiszter felülírásakor nem törlődnek a TLB-kből, határozatlan ideig tárolódnak. Ezek érvénytelenítésére a PGE kikapcsolása után vagy a INVLPG utasítás segítségével lehetséges.) • CR4, bit 8: Teljesítmény figyelő számlálók engedélyezése (PCE - Preformance-Monitoring Counter Enable): Ha bekapcsolt ez a bit, akkor engedélyezi az RDPMC utasítás bármely privilégium szinten történő futtatását, ha kikapcsolt, akkor csak a kernel futtathatja. A memóriakezelés áttekintése A 32 bites x86 architektúrájú processzorok memória-kezelésének lehetőségei két részre oszthatóak: szegmentálás és lapozás. • A szegmentálás (segmention) elősegíti elkülöníteni az egyedi kód, adat és verem modulokat,
részeket, így lehetőség van arra, hogy több program (avagy taszk) fusson egy processzoron anélkül, hogy egymással érintkezniük kellene vagy lehetne. A szegmentálást nem kell külön bekapcsolni, ezt mindig használjuk védett módban, következéskép nincs státusz bit, mellyel le lehetne tiltani a szegmentálást. Csupán programozási módszerrel lehet elérni, hogy elnyomjuk a szegmentálásos modell működését. • A lapozás (paging) azt a lehetőséget biztosítja számunkra, hogy úgy hozzuk létre programunk, operációs rendszerünk környezetét, hogy a fizikai memória szűkössége esetén csak annyi információt tároljunk a fizikai memóriában, amennyi szükséges az adott program, rendszer futásához. Így a program azon memória-részei, ún. lapjai, amelyek szükségesek a futáshoz, a fizikai memóriában maradnak, a fölösleges, illetve az átmenetileg nem szükségesek pedig egy átmeneti tárolóra (pl. a háttértárolóra) kerülnek Ha
időközben az eltárolt lapok szükségessé válnának, a processzor támogatása révén meglehetősen gyorsasan visszalapozhatóak a fizikai memóriába. Ezt a memória-kezelést virtuális memória-kezelésnek is nevezzük, hisz több memóriát kezelhetünk, mint amennyi fizikailag jelen van gépünkben. A lapozást továbbá a különböző taszkok elkülönítésére is használjuk. A lapozás nem automatikusan kapcsolódik be védett módban, ha szükségünk van rá, bekapcsolhatjuk megfelelő előkészületek után. 3.1 ábra: Szegmentálás és lapozás Amint az ábra is mutatja a szegmentálás lehetőséget nyújt a processzor címezhető területének (lineáris címtartomány - linear address space) kisebb védett részekre, szegmensekre való osztásához. A szegmensek használhatóak kód, adat, verem vagy rendszer adatstruktúrák tárolására (úgymint a már tárgyalt TSS és LDT). Ha több program (taszk) fut a processzoron lehetőség van arra, hogy minden
program a saját szegmens készletével dolgozzon. A processzor szigorúan ellenőrzi a szegmensek elhelyezkedését, méretét, nehogy egyik program a másik szegmensébe írhasson. Továbbá a szegmenseket elkülöníthetjük típusuk szerint. Adott típusú szegmensen csak adott művelet, parancs végezhető. Pl kódszegmenst csak olvasni lehet írni nem Minden szegmens a processzor lineáris címtartományában helyezkedik el. Ahhoz, hogy egy adott szegmensben egy adott bájtot elérjünk logikai címet (ezt far-pointer-nek, távoli mutatónak is nevezik) kell használnunk. A logikai cím a fenti ábrán látható módon két részből a szegmens kiválasztóból és az ofszetből áll. A szegmens kiválasztó (pontosabban a leíró, amiről már szót ejtettünk) egy egyedi azonosító a szegmens számára. A szegmens kiválasztó végül is egy index a leíró táblázat (pl. a GDT) egy elemére a kívánt szegmens leíróra Így a szegmensleíró meghatározza a szegmens
méretét, a hozzáférési jogokat, a szegmens privilégium szintjét, a típusát és az első kezdő bájtot a lineáris címtartományon (ezt a szegmens kezdőcímének nevezzük). Az ofszet határozza meg a szegmensen belül az elérni kívánt bájt helyét. (Az ofszet simán hozzáadódik az előbb elmondott szegmens kezdőcímhez, így alkotva lineáris címet a processzor lineáris címtartományában.) Ha a lapozás nem használt akkor a lineáris cím közvetlenül fizikai címet jelent a processzor számára (a lineáris cím konverzió nélkül fordítódik fizikaivá). A fizikai címtartomány szélességét, nagyságát a processzor címsín szélessége határozza meg. (Amekkora a legnagyobb megcímezhető bájt => címsínek száma Ezt iPentium-ig 232-en, illetve iP6 családtól már 236-on.) Részint a multitaszking miatt van szükség több memóriára, mint amennyi fizikailag elhelyezkedik a gépünkben, így a lineáris címtartományt, a memóriát úgymond
"virtualizáljuk". (Fent már szóltunk erről) A processzor virtuális memória-kezelésnek csak egy részét, a logikai kezelés feltételeit valósítja meg a lapozásos memóriakezelésen keresztül (A többit az operációs rendszernek kell elkészíteni). A lapozás "virtuális memória" környezetet biztosít, ahol a nagy lineáris címtartomány bizonyos nagyságú fizikai memóriával (RAM, ROM) és háttértárolóval van megoldva. Az operációs rendszer fenntart egy lap címtárat (pagedirectory) és csomó lap táblázatot (page-table), amik egybefoglalják a lapokat (pages) Amikor egy program (vagy taszk) hozzá akar férni egy címhez a lineáris címtartományban, akkor a processzor a lapcímtárat és lap táblázatot használja, hogy átfordítsa a lineáris címet a fizikaivá és teljesíthesse a kívánt műveletet írást, vagy olvasást - a kívánt memória címen. Ha az elérni kívánt címet tartalmazó lap nincs éppen bent a fizikai
memóriában, akkor a processzor megszakítja a program (taszk) futását egy page-fault kivétel generálásával. Ekkor az operációs rendszer lekezeli a laphiba (pagefault) kivételt úgy, hogy beolvassa a fizikai memóriába a háttértárolóról a kívánt lapot Ezek után már a program folytathatja a futását és elérheti a kívánt címet. Akkor van a lapozásos memória-kezelés, a virtuális memória-kezelés helyesen megvalósítva az operációs rendszerben, ha az adott programnak (és a felhasználónak) teljesen észrevehetetlen annak létezése. 16 bites (pl valós) programokat is be lehet lapozni (anélkül, hogy ők tudhatnának róla, hisz a 16 bites processzorokban még nincs is implementálva ez a lehetőség) akkor, ha virtuális-8086-os módban futnak. Ez még nagyobb biztonságot ad, mind az operációs rendszernek és a programnak. Szegmensek használata A 32 bites x86-os processzor architektúra által támogatott szegmentálásos mechanizmus eredménye
képen sok fajta rendszer építhető fel. Kezdve a sík memória-modelltől (Basic Flat Model), mely csak minimális védelmet nyújt, egészen a sok, multiszegmens (Multi Segment) modellig, mely meglehetősen összetett, robosztus alkalmazások és operációs rendszerek memória modelljére használatos nagy megbízhatósága által. Az alábbiakban a lehetséges szegmentált memória modelleket tekintjük működés, felhasználhatóság, illetve egyéb szempontok alapján. A sík modell Ez a legegyszerűbb memória modell, mely az operációs rendszer és programok számára egy nem szegmentált, folytonos címtartományt biztosít. Ezért e modellnek legtalálóbb neve a "nem szegmentált modell" lehet Ez a modell nagymértékben (szinte teljesen) elrejti a szegmentálást a rendszerprogramozók elől, így szinte megkerülhető a szegmentálás. Ez természetesen csak a szegmentálás "eltompítása", nem kikapcsolása! Ahhoz, hogy ezt a memória modellt
használjuk legalább két szegmens leírót kell létrehoznunk: egyet, mely a kódszegmensre és egyet, mely az adatszegmensre mutat. (Lásd az alábbi ábrát) Ahogy mindkét szegmens az egész lineáris címtartományra van leképezve, úgy mindkét szegmens leírónak azonosan 0 a kezdő címe, és szegmens határuk (a szegmens bájtban mért hossza) is azonosan 4 GBájt. Annak ellenére, hogy a szegmens határt 4 GBájtra állítjuk, ha olyan címet címezünk meg, ahol nincs fizikailag memória, nem kapunk általános védelmi-hibát (#GP). A ROM (EPROM) általában a fizikai címtartomány tetején helyezkedik el, mert a processzor reszet után a működését az FFFF FFF0h címen kezdi. A RAM (DRAM) a címtartomány alsó részén helyezkedik el, mert reszet után a DS 0-ra van állítva. (Az utolsó adatok nyílván a valós címzésre értendőek, de ezekről pontosabban, bővebben beszélünk a processzor inicializálása témakörben.) 3.2 ábra: Sík modell Védett sík
modell A védett sík modell hasonló a nem szegmentált modellhez annyi különbséggel, hogy a szegmens határ csak azon címtartományokat foglalja magába, ahol fizikailag is létezik memória. (Lásd az alábbi ábrát) Általános védelmi hibát (#GP - general-protection error) generál a processzor amennyiben nem létező memória címtartományhoz kívánunk hozzáférni. Ez a modell minimális hardver védelmet ad bizonyos program hibák ellen 3.3 ábra: Védett sík modell Ezt a modellt sokkal összetettebb módon is használhatjuk, pl. ha a lapozást (bővebben: 36-os alfejezet) párosítjuk ezen védett sík modellel. Így lehetővé válik, pl a felhasználói kód és a supervisor (kernel) kód elválasztása Négy szegmenst kell definiálni: kód- és adatszegmenst a felhasználói privilégium szintre (3-as privilégium szint, a legalacsonyabb jogokkal) és úgyszintén kód- illetve adatszegmenst a supervisor privilégium szintre (kernel, az operációs rendszer
magja, 0-s, legprivilegizáltabb privilégium szint). Általában ezek a szegmensek átlapolódnak egymáson a 0-s címtől kezdődően a lineáris címtartományban. Így ez a sima szegmentálásos modell egyszerű lapozással párosítva megvédheti az operációs rendszert a felhasználói programoktól úgy, hogy minden taszknak külön lapozó egységet (lapcímtárat) hoz létre (Mivel a TSS-ben a CR3-at átadjuk, így minden taszk saját lapcímtárral rendelkezhet.) Ezáltal nem csak az operációs rendszer kernel-jét védjük meg a felhasználói programoktól, hanem a taszkokat is megvédjük egymástól, így az egyik taszk összeomlása nem vezethet más taszkok illetve az operációs rendszer összeomlásához. Ezt a memória modellt (a védett sík modellt illetve a lapozást) sok fejlett operációs rendszer használja. Multiszegmens modell A multiszegmens modell, mint az alábbi ábra is mutatja teljesen kihasználja a szegmentálás lehetőségeit megvalósítva a
teljes hardveres védelmét a kód-, illetve adatstruktúrák és a programok, illetve taszkok számára. Itt minden programnak (vagy taszknak) saját szegmensleíró táblázata van a saját szegmenseivel. A szegmensek vagy kizárólagosan csak az őt birtokló program által, vagy programok (taszkok) között megosztva érhető el. Az összes szegmenshez, illetve a futtató környezethez (~operációs rendszer) való hozzáférést a processzor kontrollálja. 3.4 ábra: Multiszegmens modell A hozzáférés vizsgálat (access check) nem csak a szegmensen kívül történő illegális műveleteket (pl. írás a szegmensen kívül, a szegmens határt átlépve) hanem a szegmensen belüli nem támogatott műveleteket is letiltja. Pl a kódszegmensek csak olvasható szegmensek, így letiltottuk (maga a processzor) az ezekbe történő írást, ha írni kívánnánk a kódszegmensbe kivétel generálódik. A hozzáférési jogok megállapítása és létrehozása úgyszintén bizonyos
védelmi szinteket biztosít a különböző szintű programoknak, taszkoknak, pl. megvédi az operációs rendszert az illetéktelen alkalmazás hozzáférésétől. A lapozás és a szegmentálás Lapozást bármelyik fent említett szegmens modellel együtt használhatjuk. A processzor által létrehozott lapozó mechanizmus a lineáris címtartományt lapkora osztja (ezekbe helyezhetjük el szegmenseinket). Ezt jól mutatja be a 3.1-es ábra Majd ezek a lineáris címtartománybeli lapok a fizikai memóriába lesznek betérképezve (mappolva) A lapozásos mechanizmus rengeteg lap szintű védelmi lehetőséget hordoz magában, amit vagy a szegmentálás szegmens védelmével, avagy a helyett használhatunk. Pl két szintű supervisor kódot is létrehozhatunk a másodikat is megvédve a felhasználói programoktól. A fizikai címtartomány Védett módban a 32 bites x86-os processzorok 4 GBájtnyi fizikai címtartományt biztosítanak illetve ennyi érhető el. (Ez 232-en
bájt.) Ez a legnagyobb cím, ami a processzor címbuszán megcímezhető Ez egy sima, egybefüggő (fizikai szinten nem szegmentált) címtartomány egészen a 0 kezdő címtől, az FFFFFFFFh címig. Ez a fizikai címtartomány állhat csak olvasható, olvasható / írható memóriából, illetve memóriába ágyazott I/O-ból is. Az Intel P6 családtól kezdődően a processzorok támogatják a fizikai cím kiterjesztést a 236-on bájtra avagy 64 GBájtra. Ez a processzor 36 bites címsínének köszönhető, így a legnagyobb címezhető bájt címe: FFFFFFFFFh Ezt a szolgáltatást a CR4-ben található "fizikai címzés kiterjeszése" (PAE - Phisical Address Extension) flag (CR4, 5. bit) egyre állításával állíthatjuk be. A logikai és a lineáris címek Védett módban, a rendszer szintű műveleteknél a processzor két szintű címfordítást használ, hogy megkapja a fizikai címet: logikai címfordítás és lineáris címtartomány lapozása. Még a
legkisebb mértékű szegmentálás alkalmazásakor is a processzor által elérhető címtartomány minden bájtja logikai címmel érhető el. A logikai cím áll egy 16 bites szegmens kiválasztóból (szelektor) és egy 32 bites ofszetből, amint az alábbi ábra is mutatja. A szegmens kiválasztó megadja azt a szegmenst, amelyikben a keresett bájt található, és az ofszet pedig a szegmens kezdő címéhez képest a relatív elhelyezkedését, a címét. A processzor minden logikai címet lineáris címmé alakit. A lineáris cím egy 32 bites cím a processzor lineáris címtartományában. Ahogy a fizikai címtartomány is, úgy a lineáris címtartomány is egybefüggő (nem szegmentált), 232 bájt cím tartománnyal, kezdve 0-tól egészen az FFFFFFFFh-ig. A lineáris címtartomány (nem úgy, mint a logikai) tartalmazza az összes szegmenst illetve rendszer táblázatot (leíró táblák, stb.), amit a processzor definiált A logikai címből lineáris címre történő
címfordítás esetén a processzor az alábbiakat teszi: 1. A szegmens kiválasztó által megadott index (a kiválasztó alsó 3 bitjét kivéve az egész maga az index) segítségével a processzor kiolvassa a GDT vagy LDT-ben elhelyezkedő kívánt leírót (a szegmens leíró tartalmát). Ez csak akkor történik meg, ha új kiválasztót (szelektort) töltünk egy szegmens regiszterbe Ekkor töltődik a szegmens regiszter rejtett részébe a leíró információi. 2. Megvizsgálja a szegmens leírót, hogy meggyőződjön a hozzáférési jogokról, a szegmens méretéről hogy a kívánt cím a szegmensen belül esik -e, illetve a fent említett jogok alapján elérhető -e. 3. A szegmens kezdő címéhez (ami a szegmens leíróban található) adja az ofszetet így képezvén a lineáris címet. 3.5 ábra: Címfordítás: logikai címből, lineáris címmé Amennyiben a lapozás nem használt a processzor a lineáris címet közvetlenül fizikai címként használja (avagy
a processzor címbuszán a "lineáris cím megy ki"). Amennyiben használt a lapozó egység, akkor a lineáris címből egy második címfordítás szükséges a fizikai cím kiszámításához. Szegmens kiválasztók A szegmens kiválasztó egy 16 bites azonosító minden szegmens számára. (36-os ábra) A szegmens regiszterben megadandó szegmens kiválasztó nem közvetlenül a szegmensre mutat (ahogy valós címzésű módban), hanem egy szegmens leíróra, amelyik definiálja a szegmenst. A szegmens kiválasztó az alábbi részekből áll: • Index: (a 3. bittől a 15-ig) Ez az index rész választja ki a szegmens leírót a GDT vagy LDT-ből Mivel 13 biten tároljuk az indexet így 213-on, 8192 db indexünk pontosabban leírónk lehet. A processzor az index értékét 8-cal szorozza (mivel ennyi bájtból áll egy leíró hossza) és hozzáadja a GDT vagy LDT kezdőcíméhez, ami a GDTR vagy LDTR regiszterben található. (A nyolccal való szorzást nem kell
fizikailag a processzornak elvégeznie, hiszen hárommal balra van eltolva az index értéke, ami nyolccal való szorzást jelent. Ugyanis a 02 biteken más, nem az index bitei vannak Így csak az alsó 3 bitet kell kinullázni és hozzá is lehet adni a GDT illetve LDT kezdőcíméhez.) • Tábla kijelölő (TI - Table Indicator) flag (2. bit): Kiválasztja, mely leíró táblázatot kívánjuk használni: - ha üres - nulla ezen bit, akkor a GDT-ből választódik ki a leíró az index által, - ha bekapcsolt - 1 ezen bit, akkor pedig az aktuális LDT-re értendő az index. 3.6 ábra: Szegmens kiválasztó • Igényelt privilégium szint (RPL - Requested Privilege Level) (a 0. és 1 bitek): Meghatározza a kiválasztók privilégium szintjét. Már láttuk, hogy a privilégium szint 0 és 3 között lehet, ahol a 0 a legnagyobb privilégiummal rendelkező szint (legprivilegizáltabb). Az RPL és CPL-ről bővebben, a védelmi szintek tárgyalásakor beszélünk. Az első
leíró a GDT-ben nem használt a processzor által. Az a szegmens kiválasztó, mely ezen 0 leíróra mutat a GDTben (0 index, TI = 0) úgynevezett "null szegmens kiválasztóként" szokták használni (null referencia) A processzor nem generál kivételt, ha a szegmens regiszterbe null kiválasztót töltünk (kivéve a CS és SS-t). Abban az esetben viszont már generál kivételt, ha null kiválasztóval feltöltött szegmens regiszterrel akarunk a memóriához hozzáférni. Null kiválasztót használhatunk a nem használt szegmens regiszterek feltöltésére, a processzor is reszet után ezzel tölti fel a szegmens regisztereket. A CS vagy SS regiszterbe null kiválasztó töltése általános-védelmi hibát (#GP) eredményez. A szegmens kiválasztók elérhetőek a felhasználói programok számára úgy, mint egy pointer része, de értéküket reprezentáló leíróikat csak a link editorok vagy link betöltök - a kernel része - módosíthatják. (A felhasználói
szintű programok nem.) Szegmens regiszterek Hogy csökkentsük a címfordításra elhasznált időt és a kód bonyolultságát a 32 bites x86-os architektúrájú processzor 6 szegmens kiválasztó regisztert tartalmaz. (37-es ábra) Mindegyik szegmens regiszternek speciális feladata van Így legalább az alábbi 3 regiszter használt mindegyik program által: kódszegmens (CS), adatszegmens (DS) és veremszegmens (SS). Ezen regisztereknek a rájuk jellemző kiválasztókat kell tartalmazniuk, melyeknek érvényesnek kell lenniük. A processzor további három adatszegmens regisztert támogat, melyekkel további szegmensek választhatóak ki az aktuális program vagy taszk számára, ezek: ES, FS, GS. Ha egy program egy szegmenst el akar érni, akkor először is az egyik szegmens regiszterbe valós kiválasztót, a kívánt szegmens kiválasztóját kell töltenie. Így annak ellenére, hogy több ezer (2 * 8192) szegmenst definiálhatunk egyszerre lévén, hogy csak 6 szegmens
regiszter van csak 6-ot használhatunk. Ha az adott haton kívül továbbiakat kell elérnünk akkor a szegmens regiszterekben a kiválasztókat váltogatva tehetjük meg. 3.7 ábra: Szegmens regiszterek Minden szegmens regiszternek van egy "látható" és egy "rejtett" része (erről már szót ejtettünk). A rejtett szegmens regiszter részt néha "leíró cache-nek" (descriptor cache) vagy "árnyék regiszternek" (shadow register) is nevezik. Amikor egy szegmens kiválasztót a szegmens regiszter "látható" részébe töltünk (ezek a fent elmondott CS, . GS regiszterek), a processzor automatikusan betölti a szegmens regiszter "rejtett részébe" a szegmens kezdő címét, a szegmens méretét (szegmens határ) és a szegmenshez a hozzáférési jogokat. Ez az információ eltárolás teszi lehetővé a processzor számára, hogy plusz órajel ciklus nélkül történhessen meg a memória hozzáféréskor a cím
transzformáció. (Gondoljunk bele, hogy az LDT-n keresztüli címzéshez a cache nélkül + 3 órajel ciklus kellene) Multiprocesszoros rendszerekben, amikor ugyanahhoz a leíró táblához kívánnak hozzáférni, az operációs rendszernek kell módosítania, újratöltenie a szegmens regisztert, amikor a leíró tábla módosul. Ha ez nem valósul meg, akkor a szegmens regiszter rejtett része nem frissül fel, így a processzor a leíró cache-ben (a szegmens regiszter rejtett részében) a régi adatokkal dolgozik, annak ellenére, hogy a memóriában, a leíró táblázatban már új adatok vannak. Két fajta utasítás típus létezik, hogy a szegmens regiszterbe kiválasztót adjunk meg: 1. Direkt utasítás, úgy mint a: MOV, POP, LDS, LES, LSS, LGS és LFG utasítások Ezek explicit módod férnek a szegmens regiszterekhez. 2. Implicit módon az alábbi távoli mutatóval rendelkező (far pointer-es) utasítások használhatóak: CALL, JMP és RET utasítások illetve az
IRET, INTn, INO, INT3 utasítások. Ezek az utasítások a CS és néha más regiszterek értékét is megváltoztatják működésük elemeként. A MOV utasítással a szegmens kiválasztók (a szelektor látható) részét általános regiszterben is eltárolhatjuk. Szegmens leírók A szegmens leíró egy adat struktúra (rekord) a GDT vagy LDT-ben, mely információkat tartalmaz a szegmens elhelyezkedéséről, méretéről, hozzáférési jogairól és állapotáról. A szegmens leírók tipikusan a fordítók, linker-ek, betöltők, operációs rendszerek hozzák létre, s nem maguk a felhasználói programok. Az alábbi ábra illusztrálja egy leíró általános felépítését, mely igaz minden típusú leíróra: 3.8 ábra: Szegmens leíró • Szegmens határ mező (Segment Base Address): Megadja a szegmens méretét. A processzor a szegmens határát két részből állítja össze egy db 20 bites címmé, amint a fenti ábra is mutatja. A processzor két féle képen
értelmezheti a szegmens határt, a G (lépésmérték) flag állapotától függően: • • Ha a G flag üres, a megadott szegmens határ bájtban értendő az 1 bájt - 1 MBájt intervallumban. Ha a G flag bekapcsolt a szegmens méret 4 KBájtban értendő a 4 KBájt - 4 GBájt intervallumban. Az előbb definiált nagyságú szegmens határt a processzor két féle képen értelmezi attól függően, hogy a szegmens felfelé növekvő (expand-up) vagy lefelé növekvő (expand-down) címzést alkalmazó szegmens. (Alább részletezzük.) A felfelé növekvő szegmenseknél az ofszet a logikai címben 0 és a szegmens határ között lehet. Ha az ofszet nagyobb, mint a szegmens határ általános-védelmi hiba (#GP) generálódik A lefelé növekvő szegmensek esetén az ofszet a szegmens határtól FFFFFFFFh-ig vagy FFFFh-ig mehet a B flagtől függően (D/B az ábrán). Ha az ofszet kisebb, mint a szegmens határ úgyszintén általános-védelmi hiba (#GP) generálódik.
Amennyiben a szegmens határt utólag csökkentjük a lefelé növekvő szegmensek esetén új memóriaterület a szegmens alján (!) foglalódik le. Az 32 bites x86-os architektúra szerint a verem mindig lefelé nő, így ez a mechanizmus könnyen használható a verem számára. • Kezdőcím mező (Base Address Fields): Meghatározza a szegmens első - nulladik - bájtjának címét a lineáris címtartományban a 4 GBájtos határon belül. A kezdőcím három részből áll - lásd az ábrát fent -, ami 32 bitet tesz ki A szegmensek kezdőcímét 16 bájtos határra lehet - illik - állítani. Noha nem kötelező a 16 bites illesztés, annak ellenére javallott hiszen így érhető el a programok maximális teljesítménye a kód és adat 16 bájtos határra történő illesztésével. • Típus mező (Type Field): Megadja a szegmens vagy a kapu (kapu (gate) - a különböző privilégium, illetve módok közötti átjárásra használt) típusát és meghatározza a
hozzáférés módját (írás / olvasás) továbbá a szegmens növekedésének módja is itt beállított (expand-down / expand-up). Ezen bit értelmezése függ az S bittől, ugyanis különböző típusok léteznek a kód-, adatszegmens leírókra (S = 1 esetén), és különböző típusok - ahogy később láthatjuk - a rendszer leírók esetén ha S = 0. • A leíró típusa flag (S - Descriptor Type): Megadja hogy a szegmens leíró rendszer szegmens leíró -e (ha az S flag üres), vagy felhasználói szegmens leíró -e (ha az S flag bekapcsolt). • Leíró privilégium szint mező (DPL - Descriptor Privilege Level): Meghatározza egy szegmens privilégium szintjét. A privilégium szint - a már tárgyaltak szerint - 0 és 3 között lehet, ahol a 0 a legprivilegizáltabb szint. A DPL-t a szegmens hozzáférésének kontrollálására használjuk • A szegmens betöltött flag (P - Segment-Present): Jelöli, hogy az adott szegmens jelen van -e a memóriában (ha
igen, értéke bekapcsolt), vagy sem (ekkor értéke üres). Ha olyan leíróra mutató kiválasztót töltünk a szegmens regiszterbe, melynél ezen P flag üres tehát nincs a szegmens a memóriában - a processzor "nincs jelen a szegmens" (#NP - segment-not-present exception) kivételt generál. (Az operációs rendszer lekezeli ezt a kivételt, behúzza a memóriába a szegmenst, és a parancs, ami a hibát kiváltotta most már újra hibátlanul lefuthat. Legalább is nem történik újra #NP) Ez egy plusz lehetőség a virtuális memória-kezelésre a lapozással együtt. Az alábbi ábra mutatja, hogy hogyan épül fel a szegmens leíró, amikor a megnevezett szegmens nincs jelen a memóriában. Így a leíró bizonyos részei - ha nincs a szegmens a memóriában - az operációs rendszer által szabadon használhatóak más célra. (Általában az alább felhasználhatónak jelzett bitek a szegmens háttértárolón való elhelyezkedését adják meg. Így az
operációs rendszer is gyorsan be tudja olvasni a memóriába, és futhat tovább a program.) 3.9 ábra: Szegmens leíró, amikor a "szegmens jelen van" flag értéke 0 • Alap utasítás és cím méret, illetve alap verem mutató méret és/vagy felső határ flag (D / B Default operation size / Default stack pointer size and / or Upper bound): Különböző utasításokat hajt végre attól függően, hogy a szegmens leíró egy futtatható kódszegmens, vagy (lefelé terjeszkedő) adatszegmens, vagy egy veremszegmens. (Ezt a flaget mindig 1-re állíthatjuk 32 bites kód- és adatszegmensek esetén, míg 16 bitesek esetén pedig 0-ra.) • Végrehajtható kódszegmens: A D flag megadja a szegmensben található alap hosszt az effektív cím és utasítás operandusának hosszára. Ha a flag bekapcsolt 32 bites cím vagy 8 bites operandust vár a processzor, ha üres akkor 16 bites cím vagy 8 bites operandust feltételez a processzor. Használható a 66h
utasítás prefixum, hogy olyan operandus hosszt adhassunk meg, ami az alaptól eltér illetve a 67h-s prefixum, hogy az alaptól eltérő hosszú címet adhassunk meg. • Verem szegmens, pontosabban az az adatszegmens, melyre az SS mutat: A flaget ezen esetben B (mint big - nagy) flagnek nevezzük, mely meghatározza a verem mutató hosszát a verem műveletek esetén (úgy mint: push, pop, és call). Ha a flag bekapcsolt 32 bites verem mutató a használt, pontosabban az ESP regiszter, amennyiben a flag üres, 16 bites verem mutató a használt az-az az ESP alsó 16 bitje az SP regiszter. Ha a veremszegmens egy lefelé terjeszkedő (expand-down) adatszegmens (részletesen alább), akkor a B flag továbbá meghatározza a veremszegmens felső határát. • Lefelé terjeszkedő adatszegmens (Expand-down data segment): A fent megnevezett B flag meghatározza a szegmens határt. Ha a B flag bekapcsolt a felső határ FFFFFFFFh (232-1 = 4 GBájt), amennyiben üres, a felső határ:
FFFFh (216-1 = 64 KBájt) • Lépésmérték - "szemcsésség" flag (G - Granualarity): Meghatározza a szegmens határ (méret) szám értelmezését. Ha a G flag üres, akkor a szegmens határ bájtban értendő, míg ha a G flag bekapcsolt, a szegmens határ 4 KBájtos egységekben értendő. (Ez csak a szegmens határra értendő, s soha sem a kezdőcímre, mert azt mindig bájtban adjuk meg.) Ha a G flag beállított a legfelső 12 bitet a processzor soha sem veszi figyelembe az ofszet ellenőrzésekor. Így a G flag bekapcsoltsága esetén az ofszetnek 0 és 4095 között kell lennie. • Rendelkezésre álló és fenntartott bitek (Available and reserved bits): A 20. bit a második dupla word-ben felhasználható az operációs rendszerek által, míg a 21 bit ugyanitt fenntartott, és értéke mindig 0. Kód- és adatszegmens leíró típusok Ha az S (leíró típus) flag bekapcsolt a szegmens leíróban, akkor a szegmens vagy adat- vagy kódszegmens. A típus
mező (3.8-as ábrán látható) legfelső bitje (a második dupla word 11 bitje a leíróban) adja meg, hogy a leíró adat- ha ez a bit üres - vagy kódszegmenst (ha bekapcsolt) ír le • Ha adatszegmenst ír ad meg a leíró, akkor a három alsó bit (8, 9, 10-es bitek) megadja: hogy hozzáfértek e (A - Accessed), az írás megengedett -e (W - write enable), és a feltöltési irányt (E - expansion-direction). Az alábbi táblázat összefoglalja a lehetséges bit kombinációk által reprezentálható lehetőségeket. (Az adatszegmensek mindenképen olvashatóak az írást a már említett E bittel engedélyezhetjük.) Típus mező Decimálisan 10 9 8 E W A 11 Leíró típus Leírás 0 0 0 0 0 Adat Csak olvasható 1 0 0 0 1 Adat Csak olvasható, hozzáfért 2 0 0 1 0 Adat Olvasható/írható 3 0 0 1 1 Adat Olvasható/írható, hozzáfért 4 0 1 0 0 Adat Csak olvasható, lefelé terjeszkedő 5 0 1 0 1 Adat Csak olvasható,
lefelé terjeszkedő, hozzáfért 6 0 1 1 0 Adat Olvasható/írható, lefelé terjeszkedő 7 0 1 1 1 Adat Olvasható/írható, lefelé terjeszkedő, hozzáfért C R A 8 1 0 0 0 Kód Csak végrehajtható 9 1 0 0 1 Kód Csak végrehajtható hozzáfért 10 1 0 1 0 Kód Végrehajtható/olvasható 11 1 0 1 1 Kód Végrehajtható/olvasható, hozzáfért 12 1 1 0 0 Kód Csak végrehajtható, lefelé terjeszkedő 13 1 1 0 1 Kód Csak végrehajtható, lefelé terjeszkedő, hozzáfért 14 1 1 1 0 Kód Végrehajtható/olvasható, lefelé terjeszkedő 15 1 1 1 1 Kód Végrehajtható/olvasható, lefelé terjeszkedő, hozzáfért 3.1 táblázat: Kód- és adatszegmens típusok • A veremszegmensek olyan adatszegmensként vannak definiálva, mely írható és olvasható adatszegmens. Ha az SS regiszterbe olyan kiválasztót töltünk, mely nem írható adatszegmenst definiál, általános védelmi hibát (#GP -
general-protection error) generál a processzor. Ha a veremszegmens méretének dinamikusan kell változnia, akkor definiálhatjuk lefelé bővülő (expand-down) adatszegmensként (az E bit bekapcsolásával). Így, ha dinamikusan változtatjuk a veremszegmens méretét, akkor az alul bővül, az-az a plusz hely a veremszegmens alján adódik hozzá. Ha a veremszegmenst statikusan kívánjuk kezelni, akkor lehet mind lefelé és mind felfelé bővülő típusú. Az A (A - Accessed - hozzáfért) bit jelzi, hogy a szegmenst használták -e azóta, mióta az operációs rendszer utoljára kinullázta ezt a bitet. A processzor akkor állítja egyre ezt a bitet, ha a szegmens regiszterbe a szegmenst leíróra mutató kiválasztót töltjük. Ez a bit addig marad egy amíg explicit (szándékos, közvetlen) módon ki nem nullázzuk. Ezt a bitet felhasználhatjuk (pontosabban az operációs rendszer felhasználhatja) mind virtuális memóriakezelésre és mind debugging-ra
(nyomkövetésre). • Kódszegmens esetén a típus alsó három bite (8, 9, 10. bitek a leíró második dupla word-jében) megadja: hogy a kódszegmenshez elérték -e (A - accessed), az implicit olvasás megengedett -e (R - read-enable), és hogy illeszkedő -e a szegmens (C - conforming). A kódszegmens minden esetben legalább futtatható, vagy lehet futtatható és olvasható is, ez utóbbi függ az R bit állapotától (ha bekapcsolt, akkor olvasható is). Akkor lehet szükség a futtatható és olvasható kódszegmensre, ha konstansokat vagy egyéb statikus adattagokat tartalmaz a kód. (Így a kódszegmens a valósmódtól eltérően nem írható csak olvasható) Ekkor a kódszegmensben tárolt adatokat legegyszerűbben úgy érhetjük el ha adatszegmens regiszterbe (DS, ES, FS, GS regiszterek) töltjük a kiválasztót, és így olvassuk ki a kívánt adatot. (Továbbá elérhető CS-t átíró prefixumot használó utasítással is.) A kódszegmens lehet illeszkedő vagy
nem illeszkedő (conforming vagy nonconforming). Ha futtatni akarunk egy magasabb privilégium szinten lévő (alacsonyabb privilégium számú) illeszkedő kódszegmenst, akkor a futtatás megengedett, hogy a jelenlegi privilégum szinten folytatódjon. Amennyiben nem illeszkedő más privilégium szinten álló kódszegmenst akarunk futtatni, a processzor általános védelmi hibát (#GP) generál. Nem illeszkedő kódszegmensek esetén csak call vagy taszk kapu alkalmazásával futtatható a más privilégium szinten álló kódszegmens. A rendszer utility-ket melyeknek nincs szükségük a memória védelemre, illetve bizonyos kivétel kezelő metódusokat (pl. osztás vagy túlcsordulás hiba) illeszkedő kódszegmensbe tölthetjük Az olyan utility-ket melyeknek szükségük a védett memóriakezelés minden megszorítására, csakis nem illeszkedő szegmensbe tölthetjük. Megjegyezném: hogy magasabb privilégium szintről (nagyobb privilégium számú) nem futtathatunk
alacsonyabb szinten elhelyezkedő kódszegmenst akár illeszkedő akár nem illeszkedő az. Minden ilyen call vagy jmp paranccsal kiadott próbálkozás általános védelmi hibát (#GP) okoz. Minden adatszegmens, amelyik nem illeszkedő nem érhető el alacsonyabb privilégum szintről egyetlen rutin vagy program számára se (ez akkor van, ha a kód nagyobb számú privilégium szinten fut). A kódszegmensektől eltérően az adatszegmens elérhető privilegizáltabb (számmal alacsonyabb privilégium szintű) helyről a rutinok - programok számára minden különösebb kapu vagy "speciális furfang" nélkül. A processzor frissítheti a Típus mezőt (a leíró típusa), ha a szegmenshez hozzányúltak (A - accessed) még akkor is, ha csak olvassák. Ha a leíró táblázat ROM-ban van, akkor a processzornak le kell állítani az írás kísérletet az adatbuszon még az írás ciklus alatt. Továbbá szükséges a READY# jelet a processzornak jelezni
(visszaállítani egybe), mert egyébként ROM-ba íráskor az írás ciklus nem fejeződik be. Az Intel386-os processzorok mindig 1-re kapcsolják a hozzáfért (A - accessed) bitet, amikor használják az adott szegmenst. Az Intel486, Pentium illetve P6 család processzorai, csak akkor kapcsolják 1-re ezt a bitet, ha még nincs bekapcsolva. Így ezen processzorok esetén a ROM-ban elhelyezett leírótáblázat frissítése elkerülhető, ha minden leírónál az A bitet egyre állítjuk. Rendszerszegmens leíró típusok Amikor az S bit (3.8-as ábra) üres akkor, akkor a szegmens leíró rendszerszegmenst definiál A processzor az alábbirendszer szegmenseket definiálja: • • • • • • Helyi leíró táblázat (LDA - Local Descriptor Table) szegmens leíró Taszk állapot szegmens (TSS - Task-State Segment) leíró Call-kapu (Call-gate) leíró Megszakítás kapu (Interrupt-gate) leíró Csapda kapu (Trap-gate) leíró Taszk-kapu (Task gate) leíró Típus mező
Decimálisan 11 10 9 8 Leírás 0 0 0 0 0 Fenntartott 1 0 0 0 1 16 bites taszk állapot szegmens (TSS) (Felhasználható) 2 0 0 1 0 Lokális leíró táblázat (LDT) 3 0 0 1 1 16 bites taszk állapot szegmens (TSS) (Foglalt) 4 0 1 0 0 16 bites call-kapu 5 0 1 0 1 Taszk kapu 6 0 1 1 0 16 bites megszakítás kapu 7 0 1 1 1 16 bites csapda kapu 8 1 0 0 0 Fenntartott 9 1 0 0 1 32 bites taszk állapot szegmens (TSS) (Felhasználható) 10 1 0 1 0 Fenntartott 11 1 0 1 1 32 bites taszk állapot szegmens (TSS) (Foglalt) 12 1 1 0 0 32 bites call-kapu 13 1 1 0 1 Fenntartott 14 1 1 1 0 32 bites megszakítás kapu 15 1 1 1 1 32 bites csapda kapu 3.2 táblázat: Rendszerszegmens és kapu leíró típusok A rendszer leírók két csoportba sorolhatóak: rendszerszegmens leírók és kapu leírók. A rendszerszegmens leírók rendszerszegmensre (LDT vagy TSS szegmensre) mutatnak. A kapu leírók
tényleg fizikailag is a kapu szerepét töltik be, ugyanis vagy egy mutatót (pointert) tartalmaznak a kódszegmens belépési pontjára (procedure entry point) call, megszakítás, vagy csapda kapuk esetén. Egyéb esetben - taszk kapu - a kapu leíró TSS szegmens kiválasztót tartalmaz. A fenti táblázat jól összefoglalja a típus mezőben kialakítható bit kombinációk által hordozott rendszerszegmens leírók típusát. Ezekről bővebben most és a későbbiekben is tárgyalunk Szegmens leíró táblák A szegmens leíró táblázat a szegmens leírók tömbjének tekinthető (a szegmenst leírót tekinthetjük egy "változatos rekordnak", és így a leíró táblázatot egy rekordokból álló tömb) lásd az alábbi ábrát. A leíró táblázat választható nagyságú maximálisan 8192 db 8 bájtos leírót tartalmazhat. Két fajta leíró táblázat létezik: • A globális leíró táblázat (GDT - Global Descriptor Table) és • a lokális
leíró táblázat(ok) (LDT - Local Descriptor Tables) 3.10 ábra: Globális és lokális leíró táblák Védett módban mindig kell léteznie egy GDT-nek, amelyeket a program(ok), taszk(ok) használhatnak. Lehetőség van egy vagy több LDT létrehozására is, de nem szükséges létrehozni. Például létrehozhatunk minden egyes taszknak külön saját LDT, vagy akár az összes taszk is egyet használhat, de lehetőség van csak az adott taszkok közötti LDT megosztásra is. (A legjobb a teljesen elkülönített LDT minden taszk számára és a későbbiekben meglátjuk, hogyan lehet igazán biztonságos módon memóriát taszkok között megosztani.) A GDT, mind ahogy már megállapítottuk nem szegmens (mármint a szó szerinti értelmében, ugyanis nincs szegmens leírója) hanem egy adatstruktúra a lineáris címtartományban. Ahogy az előzőekben áttekintettük a GDTR regiszterbe kell a lineáris kezdőcímét és a méretét tölteni a megadott módon. (A GDT
kezdőcímét érdemes 8 bájtos határra igazítani.) Ahogy a kezdőcímet úgy a GDT méretét is bájtban kell megadnunk Ahogy a szegmens mechanizmusnál is, az utolsó használható bájtot a kezdőcím plusz méret adja meg. (Nyílván a 0-s határ megadása csak egy bajt használatát engedné meg, de nincs is értelme, hisz egy leíró 8 bájtos és a 0. leíró nem használt) Tehát a határ (a GDT méret) megadása mindig az alábbi képlet szerint illik, hogy történjen: 8*N-1. Ahol N a leírók száma (Legalább kettőt kell megadni.) A null leíróról már beszéltünk, hogy használható a szegmens regiszterek (kiv CS, SS) kinullázására anélkül, hogy a regiszterbe töltéskor #GP-t kapnánk. Akkor ha a null referenciát szegmens tartalmazó regiszterrel akarunk memóriát elérni viszont #GP-t kapunk. A LDT egy LDT típusú rendszerszegmens leíró által meghatározott szegmens és erre mutató kiválasztóval kiválasztható. A GDT-nek tartalmaznia ezt a
rendszerszegmens leírót, hisz csak így kiválasztható (Amennyiben több LDT-vel akarunk dolgozni, akkor mindannyiuknak a leíróját kell a GDT-nek tartalmaznia - nincs megkötés mely GDT részben -.) Mivel az LDT-t is szegmens kiválasztóval érjük el, hogy lerövidítsük az a már fent említett címfordítási időt az LDTR regiszter tartalmazza a kiválasztót - ezzel választottuk ki az aktuális LDT - illetve (rejtett módon) tartalmazza az LDT lineáris kezdő címét, méretét, és hozzáférési jogait. Amikor a GDTR regisztert (az SGDT utasítással) kiírjuk a memóriába egy 48 bites pszeudó leíró tárolódik el. 3.11 ábra: Pszeudó leíró formátum Ez azért jön létre, hogy felhasználói privilégium szinten (= 3) elkerüljük az illesztési hibát (alignment check) így ez páros word címen helyezkedik el. (a cím 4-gyel osztva 2-t ad) Így a processzornak illesztett dupla word után kell tárolnia ezt az illesztett word-öt. A felhasználói
programok általában nem tartalmaznak pszeudó leírót de így elkerülhető (lenne) az illesztési hiba. A hasonló illesztés használható akkor, amikor az IDTR regisztert írjuk ki az SIDT paranccsal. Amikor az LDTR vagy TR regisztert kiírjuk a memóriába (az SLTR illetve STR parancsokkal) a pszeudó leíró dupla-word címen található meg (a cím 4-gyel osztott maradéka 0). Lapozás Amikor védett módban dolgozunk a 32 bites, x86-os architektúrájú processzorok megengedik, hogy a lineáris címtartományt direkt módon nagy méretû fizikai memóriába (pl. 1-4 GB RAM-ig) képezzük (ez a szegmentálás, lásd előző fejezet), vagy indirekt módon a lapozást használva kisebb méretû fizikai memóriát és háttértárolót használjunk memória igényünk kielégítésére. Az utóbbi memória leképezési módszert, a lapozást, virtuális memóriakezelésnek is nevezzük. Amikor a lapozást használjuk, a processzor a lineáris címtartományt fix méretû (ált. 4
KBájtosra) lapokra osztja, melyeket a fizikai memóriába vagy/és a háttértárolóra képezhetjük le (mappolhatjuk). Amikor egy védett program vagy taszk hozzá akar férni egy logikai címhez a memóriában, akkor a processzor a logikai címet lineáris címmé fordítja - transzformálja - majd a lapozó mechanizmust használva a lineáris címet fizikai címmé fordítja. Így a szegmentálástól eltérő módon kettős címfordítás történik. Ha nincs a fizikai memóriában az a lap amelyikre a lineáris cím hivatkozik, akkor a processzor laphiba kivételt (#PF - page-fault exception-t) generál. A laphiba kivételt lekezelő kernel szintû - eljárás utasítja az operációs rendszert a kért lap betöltésére, a háttértárolóról a fizikai memóriába (Ez a lap betöltés járhat azzal, hogy először a fizikai memóriában helyet kell készíteni a betöltendő lapnak, így először fizikai lapok kerülnek ki a háttértárolóra, és ennek a helyére kerül a
kívánt lap.) Amikor a lap betöltődött a fizikai memóriába a kivételt lekezelő rutin után a taszk futása azon az utasításon folytatódik mely a kivételt kiváltotta. Így az az utasítás már elérheti a kívánt logikai címet. A processzor táblázatokban tárolja el azon információkat, mely alapján a lineáris címet fizikaivá fordítja, illetve melyből kiderül, hogy laphibát kell generálni mert a háttértárolón van a kívánt lap. Ezeket a táblázatokat lapcímtárnak (page directory) és lap táblázatnak (page table) nevezzük. A lapcímtár (page directory - PD) elemei a lapcímtár bejegyzések (page directory entry - PDE), melyek lap táblázatokra (page table) mutatnak. A lap táblázat elemei (page table entry - PTE) pedig a fizikai memória lapokra (pages) mutatnak (Egy lap táblázatot a lapcímtár bejegyzés segítségével, s egy fizikai lapot a lap táblázat bejegyzése által szolgáltatott kezdőcímmel lehet kiválasztani.) A lapozás
abban is különbözik a szegmentálástól, hogy fix méretû lapokat, memória-tartományokat használ. Ha csak szegmentált memória modellt használunk, akkor egy kód vagy adatstruktúránk minden egyes része, szegmense biztosan a fizikai memóriában van. A lapozásnál ettől eltérően lehet, hogy adatstruktúránk egy része a fizikai más része a háttértárolóból képzett memóriában, a virtuális memóriában van. Persze a két szélső eset is gyakran előfordul, hol egészen vagy a fizikai memóriában vagy a háttértárolón vannak a nevezett lapok. Hogy minimalizáljuk a címfordításhoz szükséges busz ciklusok számát, a legtöbbet használt lapcímtár és lap táblázat bejegyzések, indexek a processzor TLB-nek nevezett részében vannak cache-elve. (TBLs - Translation Lookaside Buffers - "fordítási segédpuffer" Ezután csak TLB-k.) A TLB-k kiszolgálják szinte az összes olvasási kérelmet az aktuális lapcímtárra és lap táblázatra
anélkül, hogy a processzornak külön a memóriában kellene olvasnia, ezzel plusz busz ciklust okozva. Csak akkor történik plusz busz ciklus a címfordítás során, ha egyetlen TLB bejegyzés sem tartalmazza a kívánt lap táblázat bejegyzést, ez tipikusan akkor történik, ha a kívánt lapot hosszú ideje nem kezeltük. Lapozási lehetőségek A lapozást az alábbi három flag vezérli: • PG (paging - lapozás) flag, 31. bit a CR0-ban (Intel386™-tól létezik) • PSE (page size extension - lap méret kiterjesztés) flag, 4. bit a CR4-ben (a Pentium® illetve a Pentium Pro-tól elérhető) • PAE (page address extension - lap cím kiterjesztés) flag, 5. bit a CR4-ben (a Pentium Pro-tól elérhető) A PG flag engedélyezi (PG=1) vagy tiltja (PG=0) a lapozó mechanizmust. Az operációs rendszer ha használja, akkor általában a processzor inicializálása során kacsolja be. A PG flaget be kell kapcsolni, ha virtuális memóriakezelést akarunk használni,
vagy az operációs rendszer több, mint egy viruális-8086-os taszkot kíván futtatni. A PSE flag engedélyezi (PSE=1 esetén) a nagy lapok használatát: 4 MBájtos vagy 2 MBájtos lapok (az utóbbi PAE=1 esetén használható). Ha PSE üres, akkor minden lap 4 KBájt nagyságú (A 4 illetve 2 MBájtos lapokat alább bővebben tárgyalunk.) A PAE flag engedélyezi (PAE=1 esetén) a 36 bites fizikai címzést. Ez a lehetőség, a fizikai cím kiterjesztés csak a lapozásnál használható. Ez mechanizmus a lapcímtárakra illetve a lap táblázatokra épít, így érhetőek el a P6 családban (a Pentium Pro-val) bevezetett 32 bit feletti memória címek. Laptáblázatok és lapcímtárak A processzor az alábbi négy adat struktúrát használja a lineáris címből fizikaivá történő címfordítás esetén, amennyiben a lapozás engedélyezett: • Lapcímtár: Egy tömb, mely 32 bites bejegyzésekből, a lapcímtár bejegyzésekből áll. (PDEs - page-directory entries)
Ez az adat struktúra egy db 4 KBájtos lapban foglal helyet, így 1024 bejegyzése lehet maximálisan. (4096 Bájt / 4 Bájt = 1024). Így a lapcímtár index 0 - 1023 között kell, hogy legyen • Lap táblázat: Egy tömb, mely 32 bites bejegyzésekből, a lap táblázat bejegyzésekből áll. (PTEs - page-table entries) Ez az adat struktúra egy db 4 KBájtos lapban foglal helyet, így 1024 bejegyzése lehet maximálisan. (4096 Bájt / 4 Bájt = 1024). Így a laptábla kiválasztó 0 - 1023 között kell, hogy legyen (Ha 2, vagy 4 MBájtos lapokkal dolgozunk, akkor a lap táblázat nem használt.) • Lap (ez maga a lap, vagy nevezik lap keretnek is): A fenti flagek beállításától függően reprezentálhat: 4 KBájtos, 4 MBájtos, vagy 2 MBájtos összefüggő címtartományt. • Lapcímtár mutató tábla (Page-Directory-Pointer Table): Ez egy olyan tömb, mely 64 bites bejegyzésekből áll. Minden egyes bejegyzés - ahogy később látni is fogjuk - lapcímtárra
mutat. Ez az adat struktúra csak akkor használt ha, a PAE=1. Ezek a táblázatok biztosítanak hozzáférést, vagy a 4 KBájtos vagy a 4 MBájtos lapokhoz a sima 32 bites címzéskor, és a (36 bites) fizikai cím kiterjesztéskor a 4 KBájtos illetve a 2 MBájtos lapokhoz. Az alábbi táblázat bemutatja a lap, illetve a fizikai cím méretét melyet a fenti flagek különböző beállításából kaphatunk. Továbbá van egy másik bit is a PS (Page Size - lap méret) ami a lapcímtár minden egyes bejezésében megtalálható, megadja, hogy a lap táblázat bejegyzése vajon 4 KBájtos (ha PS=0) vagy 4 MBájt / 2 MBájtos lapra mutatnak -e (PSE vagy PAE = 1, illetve PS=1). A PS bitről a későbbiekben bővebben szólunk. Lássuk ezen bitek által kialakítható kombinációkat, az alábbi táblázatban: PG flag (CR0) PAE flag (CR4) PSE flag (CR4) PS flag Lap méret Fizikai cím mérete (lt. bejegyzés) 0 X X X - Lapozás letiltva 1 0 0 X 4 KBájtos 32 bites 1
0 1 0 4 KBájtos 32 bites 1 0 1 1 4 MBájtos 32 bites 1 1 X 0 4 KBájtos 36 bites 1 1 X 1 2 MBájtos 36 bites Lap méretek és fizikai cím méretek Lineáris címfordítás, 4 KBájtos lapok esetén Az alábbi ábra mutatja a lapcímtár és a lap táblázat hierarchiáját, amikor a lineáris címtartomány 4 KBájtos lapokba van osztva. A lapcímtár fizikai kezdőcímét a CR3 (PDBR) regiszter tartalmazza a már elmondott módon A lapcímtár adott bejegyzései egy lap táblázatra mutatnak. A lap táblázat adott bejegyzése az adott fizikai memória lapra mutat Ez a lapozásos mechanizmus 220 lapot használ (1024 lapcímtár bejegyzés * 1024 lap táblázat bejegyzés = 220 lap). 220 lap pedig 220 * 4096 Bájt = 4 GBájt = 232 Bájt nagyságú lineáris címtartományt tud lefedni. Lássuk az ehhez tartozó ábrát: Lineáris címfordítás (4 KBájtos lapok) Ahhoz, hogy kiválaszthassuk a megfelelő lapcímtárat illetve lap táblázatot, a lineáris
cím az alábbi három részre oszlik (lásd a fenti ábrát): • Lapcímtár bejegyzés - A lineáris 32 bites cím: 22-től 31-ig vett bitei (10 bit). Ez egy ofszet (index) a lapcímtár egyik bejegyzésére, a kiválasztott bejegyzés adja meg a fizikai kezdőcímét a lap táblázatnak. • Lap táblázat bejegyzés - A lineáris 32 bites cím: 12-től 21-ig vett bitei (10 bit). Ez egy ofszet (index) a kiválasztott lap táblázat egyik bejegyzésére, a kiválasztott bejegyzés adja meg a fizikai kezdőcímét az elérni kívánt lapnak (page). • Lap ofszet - A lineáris 32 bites cím: alsó 12 bite (0-tól 11-ig vett bitek). Ez adja meg egy lapon belül az elérni kívánt bájt ofszetjét. A memória kezelő programok használhatnak csak egy lap címtárat minden taszk számára, vagy kaphat minden taszk külön lapcímtárat, vagy ezt a két lehetőséget kombinálni is engedi a processzor. Lineáris címfordítás, 4 MBájtos lapok esetén Az alábbi ábra mutatja,
hogy hogyan lehet a lineáris címtartományt 4 MBájtos lapokkal lefedni illetve azokra osztani. A lapcímtár bejegyzései közvetlenül, lap táblázatok nélkül 4 MBájtos lapokra mutatnak a fizikai memóriában. Mivel a lapcímtárnak 1024 bejegyzése lehet, és 4 MBájt méretûek a lapok így le lehet fedni az egész 4 GBájt lineáris címtartományt. (1024 bejegyzés * 4 MBájt = 4 GBájt) Lássuk az ehhez tartozó ábrát: Lineáris címfordítás (4 MBájtos lapok) 4 MBájtos lapot úgy hoz létre a memória menedzser, hogy a PSE bitet a CR4 regiszterben bekapcsolja (ezáltal engedélyezi a nagy lapokat), és lapcímtár adott bejegyzésében jelzi a PS bit bekapcsolásával, hogy ez a bejegyzés nagy, 4 MBájtos lapra mutat (A következő ábra bemutatja a lapcímtár illetve lap táblázat bejegyzéseinek felépítését, lásd alább.) Ezáltal, ha a PSE és PS bitek is bekapcsoltak a lineáris cím az alábbi két részre bomlik a 2 címfordítás előtt: •
Lapcímtár bejegyzés - A lineáris 32 bites cím: 22-től 31-ig vett bitei (10 bit). Ez egy ofszet (index) a lapcímtár egyik bejegyzésére, a kiválasztott bejegyzés adja meg a fizikai kezdőcímét az elérni kívánt 4 MBájtos lapnak. • Lap ofszet - A lineáris 32 bites cím: alsó 22 bite (0-tól 21-ig vett bitek). Ez adja meg egy lapon belül az elérni kívánt bájt ofszetjét. A 4 KBájtos és 4 MBájtos lapok keverése Amikor a CR4 regiszterben a PSE bit bekapcsolt, a 4 MBájtos lapok illetve a 4 KBájtos lapok lap táblázatai is elérhetőek ugyanabból a lapcímtárból a bejegyzés PS bit állapotától függően. Ha a PSE bit kikapcsolt, akkor csak a 4 KBájtos lapok lap táblázatai érhetőek el a lapcímtárból a PS bit állapotától függetlenül. (Ennek ellenére a későbbi kompatibilitás megőrzése végett érdemes ilyenkor a PS bitet kikapcsoltan hagyni.) Tipikus esete a 4 KBájtos és 4 MBájtos lapok keverésének amikor az operációs rendszer
kernel-jét nagy méretû lapba helyezzük, hogy csökkentsük a címfordításra fordított időt, így növeljük a rendszer teljesítményét. (A kis lapok esetén nem biztos, hogy mindig a TLB-ben van a lapcímtár illetve lap táblázat címe, így sok lehet a plusz busz ciklus. Míg egy vagy két nagy lap címfordítási ideje abszolút elenyésző.) Megjegyzendő továbbá, hogy a processzor a 4 MBájtos és a 4 KBájtos lap bejegyzéseknek elkülönített TLB-t tart fenn. Így ha a nagyobb és sokat használt kódot 4 KBájtos lapok helyett 4 MBájtosba helyezzük ezáltal a többi 4 KBájtos lap számára helyet szabadítunk fel a TLB-ben. (Így több 4 KBájtos lap bejegyzést tárolhatunk el a TLB-ben.) Ezáltal tovább növelhető a rendszer teljesítménye (Így a nagy 4 MBájtos lapok használata két módon gyorsíthatja a rendszert.) A lapcímtár kezdőcíme Az aktuális lapcímtár fizikai kezdőcíme a CR3 regiszterben (ezt page directory base register-nek vagy
PDBR-nek is hívják) van eltárolva. Ha a lapozás használva lesz, akkor a PDBR-t a processzor inicializálásakor kell beállítani, mielőtt még engedélyeznénk a lapozást. A PDBR-t megváltoztathatjuk explicit módon úgy, hogy a CR3 regiszterbe a MOV paranccsal új értéket töltünk, vagy implicit módon a taszk váltás során a TSS-ben más CR3 értéket adunk meg. (A TSS-ről még a taszk kezelésnél bővebben szót ejtünk.) Nincs "betöltött" (present) flag a PDBR-ben a lapcímtár számára. (Az-az: nincs olyan bit mely jelezhetné, hogy a lapcímtár maga, a fizikai memóriában van -e. Ezáltal nem generálódhat kivétel se ha nincs a fizikai memóriában) Ennek ellenére a lapcímtárnak nem kell jelen lennie a fizikai memóriában (~ úgymond ki lehet lapozva a háttértárolóra), amíg hozzá tartozó taszk fel van függesztve (a háttérben van). Viszont az operációs rendszernek meg kell győződnie, hogy taszkra váltás előtt annak a taszknak a
TSS-ében tárolt PDBR (CR3) által mutatott lapcímtár a fizikai memóriában van e, s ha igen, csak akkor válthat rá. Továbbá amíg a taszk aktív, a lapcímtárnak úgyszintén a memóriában kell lennie. Lapcímtár és lap táblázat bejegyzések Az alábbi ábrák bemutatják a lapcímtár és a lap táblázat bejegyzéseinek felépítését, amikor 4 KBájtos lapokat és sima 32 bites fizikai címzést használunk. A lapcímtár bejegyzés felépítése (4 KBájtos lapok esetén) A lap táblázat bejegyzés felépítése (4 KBájtos lapok esetén) A lapcímtár bejegyzés felépítése (4 MBájtos lapok esetén; 32 bites címzés) A fenti ábrákban lerajzolt flagek és mezők szerepe és jelentése: • Lap kezdő cím: • 4 KBájtos lap esetén a lap táblázat bejegyzés: 12. bitétől a 31-ig (a felső 20 bit) Megadja a fizikai kezdőcímét a 4 KBájtos lap első bájtjának. Ez a fizikai cím felső 20 bite, így a lapnak 4 KBájtos határra kell, hogy
igazítva legyen (a cím 4096-tal osztva 0-t ad). • 4 KBájtos lap esetén lap táblázat kezdő címe a lapcímtár bejegyzésben: Megadja a fizikai kezdőcímét a lap táblázat első bájtjának. Ez is az előzőhöz hasonló módon a fizikai cím felső 20 bitjét adja, így 4 KBájtos határra kell, hogy igazítva legyen a lap táblázat is. (A lapcímtár minden egyes lap táblájának 4 KBájtra igazítva kell lenniük.) • 4 MBájtos lap esetén a lapcímtár bejegyzésben: Megadja a 4 MBájtos lap első bájtjának fizikai kezdőcímét. Csak a felső 10 bit (22-től 31-ig vett bitek) használt, így a 12-től 21-ig vett bitek fenntartottak Pentium II-től ezeket a biteket 0-ra kell állítani. (Azért csak a felső 10 bit használt, mert 1024 db 4 MBájtos lap lehet, és ezt 10 biten lehet letárolni, továbbá a 4 MBájtos igazítás miatt. à) Itt a fizikai kezdőcímét a lapnak, a 4 MBájtos lapnak, a felső 10 bit adja, így 4 MBájtos határra kell igazítani a
lapot. • Betöltött, "jelen van" flag (P - Present), a bejegyzések 0. bite: Megadja, hogy a lap, vagy a lap táblázat, amire az adott bejegyzés mutat, éppen a fizikai memóriában van e. Ha ez flag bekapcsolt a bejegyzésben, akkor amire mutat (lap illetve lap táblázat) a fizikai memóriában van, és a kívánt bájt elérése megtörténik a címfordítás után. Ha ezen bit kikapcsolt akkor a lap vagy a lap táblázat nincs a memóriában, és ha a processzor ehhez a nem betöltött laphoz akar hozzáférni, akkor laphiba (#PF) kivételt generál. A processzor soha nem állítja át ezen bit értékét, csak az operációs rendszer állíthatja be attól függően, hogy kilapozta háttértárra, vagy onnan töltötte be. Ha a processzor laphiba kivételt generál akkor az operációs rendszernek az alábbiakat kell végrehajtania: 1. Be kell töltenie az elérni kívánt lapot a háttértárolóról a fizikai memóriába 2. Be kell írnia a lap
kezdőcímét a lapcímtárba vagy a lap táblázatba (attól függően, hogy lapot, vagy lap táblázatot töltött be) és be kell kapcsolnia a P (present - betöltött) flaget. Továbbá ugyanekkor nyílik lehetőség a piszkos (D) és hozzáfért (A) flagek (lásd alább) beállítására is. 3. Érvénytelenítni az aktuális lap táblázat bejegyzést a TLB-ben 4. Visszatér a laphibát lekezelő eljárásból (#PF handler), hogy folytassa a megszakított taszk futását (Azon az utasításon folytatódik újra, ami a kivételt okozta.) • Írás / Olvasás flag (R/W - Read / Write), a bejegyzések 1. bite: Meghatározza az írás és olvasási jogokat egy lapon vagy lapok csoportján (ha egy lap táblázatra mutató lapcímtár bejegyzésben adjuk meg). Ha a flag üres, akkor a lap (vagy lapok) csak olvasható(ak), ha bekapcsolt akkor a lap (illetve lapok) olvashatóak és írhatóak is. Ez a flag a bejegyzések U/S illetve a CR0-ban található WP flaggel együtt
alakítják ki hatásukat. • Felhasználó / felügyelői (U/S - User / Supervisor) flag, a bejegyzések 2. bite: Meghatározza az felhasználói illetve felügyelői jogokat egy lapon vagy lapok csoportján (ha egy lap táblázatra mutató lapcímtár bejegyzésben adjuk meg). Ha a flag kikapcsolt, akkor a lap (vagy lapok) a felügyelői (supervisor) privilégium szinthez tartoznak, ha bekapcsolt, akkor pedig a felhasználói (user) privilégium szinthez. Hatását ez a flag a már említett R/W és CR0-beli WP flagekkel együttesen fejti ki Ezekről bővebben a lap szintû védelemnél, a következő fejezetben lesz szó. • Lap szintû átírás (PWT - Page-level write-through) flag, a bejegyzések 3. bite: Kontrollálja, hogy átíró (write-through) vagy visszaíró (write-back) cache-elő eljárást alkalmazzon a processzor egy lapra, vagy egy lap táblázatra. Ha a PWT flag bekapcsolt, akkor engedélyezi az átíró cache-elést a bejegyzéshez tartozó lapra vagy lap
táblázatra. Ha ezen flag kikapcsolt, akkor visszaíró cache-elés az engedélyezett, a bejegyzéshez tartozó lapra vagy lap táblázatra. A processzor figyelmen kívül hagyja ezen flag állapotát, ha a CR0 regiszterbeli CD (cache disable) flag bekapcsolt. • Lap szintû cache-elés tiltása (PCD - Page-level cache disable) flag, a bejegyzések 4. bite: Kontrollálja a cache-elést lapokon vagy a lap táblázatokon. Ha a PCD flag bekapcsolt, akkor a bejegyzéshez tartozó lap, vagy lap táblázat cache-elése letiltott. Ha ez a flag kikapcsolt, akkor a lap vagy laptábla cacheelhető Ez a flag valósítja meg azon lapok cache-elésének letiltását, melyek vagy a memóriába ágyazott I/O port-okat (kapukat) tartalmaznak, vagy a cache-elés nem hozna azon lapok elérésében jelentősebb teljesítménynövekedést. A processzor figyelmen kívül hagyja ezen flag állapotát, ha a CR0 regiszterbeli CD (cache disable) flag bekapcsolt. • Hozzáfért (A - Accessed) flag, a
bejegyzések 5. bite: Jelzi, hogy a lapot vagy lap táblázatot használták e (olvastak e belőle, vagy írtak e bele). A memória kezelő programok tipikusan törlik ezt a flaget azután, hogy a lapot vagy lap táblázatot a fizikai memóriába töltötték. A processzor pedig bekapcsolja ezt a flaget, mihelyt először hozzányúlnak a laphoz vagy lap táblázathoz. (ha írni vagy olvasni akarjuk) Ez a flag úgynevezett "ragadós" ("sticky") flag, az-az a processzor csak egyszer kapcsolja be (ha bekapcsolt, akkor újra nem kapcsolja be), s soha nem kapcsolja ki implicit módon. Csak szoftver kapcsolhatja ki explicit módon. Ez a flag, illetve az alábbi piszkos (D - dirty) flag együttesen használhatóak a memória menedzselő szoftverek számára a lapok, illetve lap táblázatok kilapozására (a háttértárolóra), illetve onnan belapozására a fizikai memóriába. • Piszkos (D - Dirty) flag, a bejegyzések 6. bite: Ha bekapcsolt, akkor jelöli, hogy a
lapba írtak. (Ez a flag nem használt - 0-ra állított - azon lapcímtárak bejegyzésében, melyek lap táblázatokra mutatnak.) A memória kezelő programok ezt a flaget tipikusan azután nullázzák ki, miután a fizikai memóriába töltötték a flaghez tartozó lapot (a flag bejegyzéséhez tartozó lapot). A processzor bekapcsolja ezt a flaget az első írás hozzáféréskor Ez a flag is úgynevezett "ragadós" ("sticky") flag, az-az a processzor csak egyszer kapcsolja be (ha bekapcsolt, akkor újra nem kapcsolja be), s soha nem kapcsolja ki implicit módon. Csak szoftver kapcsolhatja ki explicit módon Ez a flag illetve fenti hozzáfért (A) flag együttesen használhatóak a memória menedzselő szoftverek számára a lapok illetve lap táblázatok kilapozására (a háttértárolóra), illetve onnan belapozására a fizikai memóriába. • Lap méret (PS - Page size) flag, lapcímtár bejegyzés 7. bit: Megadja a lap méretét. Ez a flag csak a
lapcímtár bejegyzéseiben használt Ha ez a flag kikapcsolt, akkor a lap mérete 4 KBájtban értendő és a lapcímtár bejegyzése egy lap táblázatra mutat. Amikor ez a flag bekapcsolt és normál 32 bites címzést használunk, akkor 4 MBájt a lap mérete (illetve ha a fizikai cím kiterjesztés (PAE) a bekapcsolt, akkor 2 MBájt a lap méret) és ilyenkor egy lapra mutat a lap táblázat bejegyzése. Ha a lapcímtár bejegyzése egy lap táblázatra mutat, akkor az összes lap táblázat által mutatott lap biztosan 4 KBájtos lesz. • Globális (G - Global) flag, a bejegyzések 8. bite: (A Pentium Pro processzortól használható - tehát a P6 családtól -, ezelőtti processzoroknál fenntartott.) Ha bekapcsolt, jelzi a globális lapok (global page) használatának lehetőségét. Amikor egy lapot globálisnak jelölünk meg és a CR4 regiszter PGE (PGE - page global enable; globális lap engedélyezése) flagje bekapcsolt, akkor taszk váltáskor vagy a CR3 regiszterbe
új érték töltése esetén a lap táblázat vagy lapcímtár bejegyzése a globális lapra a TLB-ben nem lesz érvénytelenre állítva. Ez a flag elősegíti, hogy a sokat használt oldalak (pl operációs rendszer kernel-jének lapjai) bejegyzései szinte mindig a TLB-ben legyenek, s a CR3 (PDBR) regiszter megváltozásakor ne kerüljenek ki onnan. Csak szoftver állíthatja ezt a flaget: ki- vagy bekapcsolt állapotura Azon lapcímtár bejegyzésekre, melyek lap táblázatra mutatnak, ez a flag mellőzve lesz és a globális karakterisztikát lap táblázat bejegyzéseiből veszi. • Fenntartott (reserved) és a szoftverek számára használható (AVL - avaible-to-software) bitek: A lap táblázat bejegyzésben a 7. bit fenntartott és 0-ra állítható, azon lapcímtár bejegyzésben, mely lap táblázatra mutat, a 6. bit a fenntartott és 0-ra állítható A 4 MBájtos lapok lapcímtár bejegyzésének 12-től 21-ig vett bitei fenntartottak és Pentium II-től 0-ra kell
állítani ezeket. Minden lap - lap táblázat - lapcímtár bejegyzés 9, 10, 11 bitei felhasználhatóak a rendszer szoftverek vagy éppen az operációs rendszer által. Amikor a PSE és PAE flagek bekapcsoltak a CR4 regiszterben, a processzor laphiba (#PF) kivételt generál, amennyiben a fenntartott bitek nem 0ra vannak állítva. Nem betöltött lapcímtár illetve lap táblázat bejegyzések Amennyiben a lap táblázat vagy lapcímtár P (Present - jelen van, "betöltött") flagje kikapcsolt, akkor az adott bejegyzés 1-től 31-ig vett bitei (magát a P legalsó bitet kivéve, az összes) az operációs rendszer által felhasználható. Általában ez a felhasználható része a bejegyzésnek információt tartalmaz az operációs rendszer részére, hogy hol van eltárolva (pontosabban, hogy hol tárolta el) a kilapozott lapot a háttértárolón. Ekkor ez a felépítése a bejegyzésnek: A lapcímtár vagy lap táblázat bejegyzés felépítése amikor nincs
betöltve a lap Fordítási "segédpuffer" (TLBs) A processzor a leggyakrabban használt lapcímtár és lap táblázat bejegyzéseket a processzoron lévő cache-ben az úgynevezett "fordítási segédpufferben" (TLBs - Translation Lookaside Buffers; ezután csak TLB-k) tárolja. A P6 processzor család tagjai és Pentium processzoroknak külön TLB-i vannak adat és utasítás cache-re. Továbbá a P6 processzor család tagjai elkülönített TLB-ket tartanak fent a 4 KBájtos és 4 MBájtos méretű lapokra. A CPUID utasítást lehet arra használni, hogy lekérdezzük a TLB-k méretét a P6 illetve Pentium processzorokban. (Pontosan le lehet kérdezni, hogy milyen adat és milyen utasítás TLB van, hány bejegyzést tud tárolni a 4 KBájt illetve 4 MBájtos lapokra, sőt P6-tól a L2 cache méretét is lekérdezhetjük.) A legtöbb lap címfordítás a TLB-ken keresztül történik. Csak akkor nyúl a fizikai memóriában található lapcímtár illetve lap
táblázatokhoz a processzor, ha nem találja a TLB-kben a lapra (vagy lap táblázatra) mutató bejegyzést (azt a bejegyzést, ami a címfordításhoz szükséges). A TLB-k nem hozzáférhető felhasználó programok illetve taszkok számára (0-tól eltérő privilégium szintű kód nem érheti el), így nem tudják érvényteleníteni a TLB-ket. Csak az operációs rendszer, a 0 privilégium szinten tudja érvényteleníteni a TLB-ket, illetve a kiválasztott TLB bejegyzéseket. Valahányszor megváltozik (még akkor is ha betöltött (P) flag = 0) a lapcímtár vagy a lap táblázat adott bejegyzése, akkor az operációs rendszernek érvénytelenítenie kell a megváltozott bejegyzéshez tartozó TLB bejegyzést, és az a következő hozzáféréskor (mármint a megváltozott bejegyzéshez való hozzáféréskor) lesz frissítve. Az összes (nem globális) TLB-k automatikusan érvénytelenítődnek valahányszor a CR3 regiszterbe értéket töltünk. A CR3 regiszterbe az
alábbi két módon tölthetünk új értéket: • Explicit módon, a MOV utasítást használva, pl: MOV CR3, EAX Ahol az EAX regiszter tartalmazza a megfelelő lapcímtár kezdőcímet. • Vagy implicit módon taszk váltás során, amikor automatikusan megváltozik a CR3 regiszter tartalma. (Az új taszk TSS-éből veszi a CR3 regiszter új értékét.) Az INVLPG utasítás szolgál arra, hogy adott lap táblázat bejegyzést érvénytelenítsünk a TLB-ben. Általában ez az utasítás csak egy TLB bejegyzés érvénytelenítésére szolgál, de bizonyos esetekben, érvénytelenítheti nem csak a kiválasztott bejegyzést, sőt érvénytelenítheti akár az összes TLB-t. Ez az utasítás nem veszi figyelembe a lapcímtár illetve lap táblazat bejegyzéseiben a G (globális) flag állapotát, így azok is érvényteleníthetőek. (Lásd alább) A Pentium Pro processzortól kezdődően a PGE flag a CR4 regiszterben (PGE - globális lapok használatának engedélyezése) és
a G (globális) flag a lapcímtár és lap táblázat bejegyzéseiben (8. bit) használható arra, hogy a legtöbbet használt lapok bejegyzései ne legyenek automatikusan érvénytelenítve a TLB-kben taszk váltás vagy a CR3 explicit megváltoztatása során. Amikor egy globális lap lapcímtár vagy lap táblázat bejegyzését a TLB-be tölti a processzor, akkor az meg nem határozható ideig marad ott. (Ezáltal esetlegesen foglalva a bejegyzés helyet a TLBben, amikor már nincs is rá szükség, vagy a bejegyzés jellemzőinek megváltozása miatt még fölöslegesen, hibásan érvényes.) Az egyetlen lehetőség, hogy érvénytelenítsük a globális lap bejegyzéseket a TLB-kben, hogy a PGE flaget vagy kikapcsoljuk és érvénytelenítjük a TLB-ket (akár explicit akár implicit módon), vagy használjuk az INVLPG utasítást a kívánt lapcímtár illetve lap táblázat bejegyzésekre (persze egyesével) a TLB-kben. Fizikai címkiterjesztés A fizikai címkiterjesztés
(PAE - Phisical Address Extension) flag a CR4 regiszterben engedélyezi a 32 és 36 közötti címsínek használatát. (Ez csak a Pentium Pro-tól használható) Így a processzor plusz 4 címsínnel rendelkezik, hogy hozzáilleszthetőek legyenek a plusz cím bitek. Ez a lehetőség csak akkor használható, ha a lapozás engedélyezett (ha mind a CR0-beli PG és a CR4-beli PAE flag bekapcsolt). Amikor a fizikai címkiterjesztés (PAE) engedélyezett, akkor a processzor két lapméretet engedélyez: a 4 KBájtosat, illetve a 2 MBájtosat. Ugyanúgy ahogy a normál 32 bites címzésnél is használhatóak ugyanazok a lap táblázatok a különböző méretű lapok esetén (úgymint: a lapcímtár mutathat 2 MBájtos lapra, vagy a 4 KBájtos lapokat tartalmazó lap táblázatra). Ahhoz, hogy használható legyen a 36 bites fizikai címzés az alábbi módosításokat kellett elvégezni a lapozó adatstruktúrákban: • A lapozó táblázatok (lapcímtár és lap táblázat)
bejegyzései 64 bit hosszúságúra lettek növelve, hogy elférjenek a hosszabb 36 bites címek. Így minden 4 KBájtos lapcímtár és lap táblázat csak feleannyi, az-az 512 bejegyzést tartalmazhat. (32 bites bejegyzésekből 1024, és most a 64 bites bejegyzésekből pedig 512 darabot.) • Egy új táblát, az úgynevezett lapcímtár mutató táblát (PDPT - Page-Directory-Pointer Table) adtak a lineáris címfordítási hierarchiához. Ennek a táblának 4, 64 bites bejegyzése van Ez a tábla a lapcímtár alatt helyezkedik el a hierarchiában, így ez - mint nevéből is adódik - 4 különböző lapcímtárra mutat (ha PAE flag bekapcsolt). • A 20 bites lapcímtár kezdőcím a CR3-ban (PDBR-ben) egy nagyobb, 27 bites lapcímtár mutató tábla kezdőcímre lett lecserélve. (Így a CR3 regisztert, pontosabban annak ezen részét már PDPTR regiszternek nevezzük.) Így a PDPTR regiszter (az a 27 bit) a lapcímtár mutató tábla fizikai kezdőcímének felső 27
bitét adja, emiatt a kezdőcímet 32 bájtos határra kell igazítani. • A lineáris címfordítás protokollja úgy változott meg, hogy engedi a 32 bites lineáris címet a nagyobb, - P6 család processzorainál 36 bit - méretű fizikai címre leképezni. A CR3 regiszter felépítése, ha a PAE flag, az-az a fizikai címkiterjesztés engedélyezett a CR4 regiszterben: A CR3 regiszter felépítése, amikor a fizikai címkiterjesztés engedélyezett Lineáris címfordítás fizikai címkiterjesztés és 4 KBájtos lapok esetén Az alábbi ábra bemutatja a lapcímtár mutató táblázat (PDPT - Page-Directory-Pointer Table), a lapcímtár (PD - PageDirectory) és a lap táblázat (PT - Page-Table) hierarchiát, amikor 4 KBájtos lapokkal fedjük le a lineáris címtartományt, a fizikai címkiterjesztés esetén (PAE bekapcsolt). Ez a lapozási struktúra is 220-on lapot használ, és lefedi mind a 232-en bájt az-az 4 GBájt lineáris címtartományt. Lineáris
címfordítás, amikor a fizikai címkiterjesztés engedélyezett (4 KBájtos lapok) Ahhoz, hogy kiválaszthassuk a megfelelő bejegyzéseket, a lineáris cím az alábbi három részre oszlik: • Lapcímtár mutató táblázat bejegyzés (PDPTE) - A lineáris 32 bites cím: 30-tól 31-ig vett bitei (a felső két bit). Ez a két bit egy ofszetet szolgáltat, kiválasztja a 4 bejegyzés közül az egyiket a PDP táblában. A kiválasztott bejegyzésben található kezdőcím szolgáltatja a lapcímtár fizikai kezdőcímét. • Lapcímtár bejegyzés (PDE) - A lineáris 32 bites cím: 21-től 29-ig vett bitei (9 bit). Ez egy ofszet (index) a lapcímtár egyik bejegyzésére, a kiválasztott bejegyzés adja meg a fizikai kezdőcímét a lap táblázatnak (a lapcímtár ezen bejegyzés által kiválasztott lap táblázatát adja meg). (Ne legyen megtévesztő, csak 9 bites az ofszet, hisz a 64 bites bejegyzések miatt csak 512 bejegyzés közül kell választani. És 29-en pont
512) • Lap táblázat bejegyzés (PTE) - A lineáris 32 bites cím: 12-től 20-ig vett bitei (9 bit). Ez egy ofszet (index) a kiválasztott lap táblázat egyik bejegyzésére, a kiválasztott bejegyzés adja meg a fizikai kezdőcímét az elérni kívánt lapnak. (Itt is csak 9 bites az ofszet, hisz 512 bejegyzés van) • Lap ofszet - A lineáris 32 bites cím: alsó 12 bite (0-tól 11-ig vett bitek). Ez adja meg egy lapon belül az elérni kívánt bájt ofszetjét. Lineáris címfordítás fizikai címkiterjesztés és 2 MBájtos lapok esetén Az alábbi ábra bemutatja, hogy hogyan lehet a lapcímtár mutató táblázattal, illetve a lapcímtárral lefedni a - 32 bites - lineáris címtartományt 2 MBájtos lapokkal. Ez a lapozási struktúra 2048 lapot használ (4 * 512), és lefedi az egész 4 GBájt lineáris címtartományt. 2 MBájtos lapokat úgy használhatunk, ha a CR4 regiszterben a PSE (Page Size Extension - lap méret kiterjesztés) flag és a lapcímtár
bejegyzés PS flagje is bekapcsolt. (Feltéve, hogy a PAE eleve bekapcsolt) Amikor így használjuk a vezérlő flageket (CR4) és a bejegyzés PS flagjét (tehát egyaránt bekapcsoltak), akkor a lineáris cím az alábbi három részre oszlik (lásd alább az ábrát): • Lapcímtár mutató táblázat bejegyzés (PDPTE) - A lineáris 32 bites cím: 30-tól 31-ig vett bitei (a felső két bit). Ez a két bit egy ofszetet szolgáltat, kiválasztja a 4 bejegyzés közül az egyiket a PDP táblában. A kiválasztott bejegyzésben található kezdőcím szolgáltatja a lapcímtár fizikai kezdőcímét. • Lapcímtár bejegyzés (PDE) - A lineáris 32 bites cím: 21-től 29-ig vett bitei (9 bit). Ez egy ofszet (index) a lapcímtár egyik bejegyzésére, a kiválasztott bejegyzés adja meg a fizikai kezdőcímét az elérni kívánt 2 MBájtos lapnak. • Lap ofszet - A lineáris 32 bites cím: alsó 21 bite (0-tól 20-ig vett bitek). Ez adja meg egy lapon belül az elérni
kívánt bájt ofszetjét. A címfordítást 2 MBájtos lapok esetén: Lineáris címfordítás, amikor a fizikai címkiterjesztés engedélyezett (2 MBájtos lapok) A 36 bites fizikai címtartomány elérése a kiterjesztett lap táblázat struktúrával Az előző két részben leírt lap táblázat struktúrák megengedik egy időben egészen 4 GBájtnyi fizikai címterület használatát a 64 GBájtból. (Tehát, egyszerre csak 4 GBájt használható a 64 Gbájtból (P6+)) A fizikai memória további 4 GBájtos részleteit az alábbi két trükkel érhetjük el. • A CR3, pontosabban a PDPTR regisztert kell átírnunk, hogy más lapcímtár mutató táblázatra mutasson, mely más lapcímtárakkal illetve lap táblázatokkal rendelkezik, így más fizikai memória területet fedhet le. • Vagy a lapcímtár mutató táblázat bejegyzéseit is megváltoztathatjuk, hogy más lapcímtárakra illetve az más lap táblázatokra mutasson így fedve le más fizikai memória
területet. A kettő lehetőség között jelentős különbség van, s véleményem szerint az első a könnyebben kezelhető megoldás a multitaszkos operációsrendszerek számára. A lapcímtár és lap táblázat bejegyzései kiterjesztett fizikai címzés esetén Az alábbi ábra mutatja a lapcímtár mutató táblázat, a lapcímtár és lap táblázat bejegyzéseinek felépítését, amikor a (36 bites) fizikai címkiterjesztés engedélyezett és 4 KBájtos lapokat használunk. Az összes bejegyzésben használt flag tulajdonsága és funkciója megegyezik a "Lapcímtár és lap táblázat bejegyzéseinek felépítése" részben elmondottakkal, az alábbi jelentős kivételekkel: • Egy új táblázat, a lapcímtár mutató táblázat található a lapozási hierarchiában. • A tábla bejegyzések mérete 64 bitre növekedtek a 32-ről. • Az előző pont által, a maximális bejegyzések száma egy lapcímtárban illetve egy lap táblázatban a felére,
512-re csökkent. • Továbbá a bejegyzésekben a fizikai kezdőcím 24 bitre növekedett (a 20-ról). Az ábra, az új felépítésű bejegyzésekről: A lapcímtár mutató táblázat, a lapcímtár és a lap táblázat bejegyzéseinek felépítése 4 KBájtos lapok és fizikai címkiterjesztés engedélyezése esetén A fizikai kezdőcím egy bejegyzésben meghatározza az alábbiakat, a bejegyzés típusától függően: • Lapcímtár mutató táblázat bejegyzés (PDPTE) - a 4 KBájtos lapcímtár első bájtjának fizikai címe. • Lapcímtár bejegyzés (PDE) - a 4 KBájtos lap táblázat, vagy a 2 MBájtos lap első bájtjának fizikai címe. • Lap táblázat bejegyzés -a 4 KBájtos lap első bájtjának fizikai címe. Minden táblázat bejegyzéseiben (kivéve azon lapcímtár bejegyzést, mely 2 MBájtos lapra mutat) a lap kezdőcíme a 36 bites fizikai cím felső 24 bitét adja, így a lapokat 4 KBájtos határra kell igazítani. Amikor a lapcímtár
2 MBájtos lapra mutat, a lap kezdőcíme a 36 bites fizikai cím felső 15 bitét adja, így a 2 MBájtos lapokat 2 MBájtos határra kell igazítani. Így más a lapcímtár bejegyzés felépítése 2 MBájtos lapokra, lássuk az ábrát: A lapcímtár mutató táblázat és a lapcímtár bejegyzéseinek felépítése 2 MBájtos lapok és fizikai címkiterjesztés engedélyezése esetén A lapcímtár mutató táblázat bejegyzéseiben (mind a 4-ben !) a betöltött (P - Present; 0. bit ) flagnek bekapcsoltnak kell lennie ha PAE (fizikai címkiterjesztés) és PG flagek (paging - lapozás) bekapcsoltak. Amennyiben nem mind a 4 lapcímtár mutató táblázat bejegyzésben bekapcsolt a P flag akkor, általános védelmi hiba kivételt generál (#GP) a processzor. A lapcímtár bejegyzés 7. bite, a PS (lap méret) flag állapota dönti el, hogy a bejegyzés 2 MBájtos lapra, vagy 4 KBájtos lap táblázatra mutat -e. Ha ez a flag kikapcsolt, akkor lap táblázatra, ellenkező
esetben a 2 MBájtos lapra mutat. Ez a flag engedélyezi ugyanazon lap táblázat rendszerben a 4 KBájtos és 2 MBájtos lapok keverését Az hozzáfért (A - access) és piszkos (D - dirty) flagek azon táblák bejegyzésit hozzák létre, melyek a lapokra mutatnak. A 9-től 11-ig vett bitek a tábla bejegyzésekben (fizikai címkiterjesztés esetén) a rendszer szoftverek számára felhasználhatóak. (Ahogy egy előző részben tárgyaltuk, ha a betöltött (P) flag kikacsolt az egész bejegyzés felhasználható az operációs rendszer számára, csak itt a fizikai címkiterjesztés miatt nem 31 hanem már 63 bit használható fel.) A ábrákon fenntartottnak, 0-nak jelölt értékeket az operációs rendszernek 0-ra kell állítania, és nem szabad módosítania. Ugyanis a PSE és/vagy PAE (CR4) flagek engedélyezése esetén a processzor laphiba kivételt (#PF) generál, ha a lapcímtár vagy lap táblázat bejegyzéseiben a fenntartott bitek nem 0-ra állítottak. Továbbá ha
a lapcímtár mutató táblázatban a fenntartott bitek nem nullára állítottak, akkor általános védelmi hiba kivételt (#GP) generál a processzor. A szegmensek lapokba foglalása A 32 bites, x86-os architektúra szegmentáló és a lapozásos mechanizmusa széles variációját nyújtja a memóriakezelésnek. Amikor a szegmentálás és a lapozás kombinálva van, a szegmensek számos módon foglalhatóak lapokba. Hogy egy sík (nem szegmentált) címzési modellt definiáljunk, pl az összes kód, adat és verem modul leképezhető egy vagy több nagy szegmensbe (akár 4 GBájtig), melyek azonos lineáris címtartományt osztanak meg (lásd ábra). Itt a szegmensek lényegileg láthatatlanok mind az applikációk, s mind az operációs rendszer számára Ha a lapozás használt, akkor a lapozó mechanizmus leképezhet egy teljes lineáris címtartományt (az egy szegmensbeli teljes lineáris címtartományt) a virtuális memóriába. Vagy akár, minden program, taszk
rendelkezhet saját, nagy lineáris címtartománnyal (saját szegmensében), amely leképezhető a virtuális memóriába saját lapcímtár illetve lap táblázatai által. A szegmensek mérete lehet kisebb, mint a lapméret. Ha a lapméretnél kisebb szegmenst helyezünk olyan lapba, mely nincs megosztva más szegmenssel, akkor a fennmaradó memória terület elveszik. Pl ha csak egy kis adat struktúrát, úgymint egy 1 bájtos rövideke szemafort helyezünk egyedül egy lapba, akkor lefoglalja maga az egész 4 KBájtot. Ha több - sok rövidke adatstruktúrát kell tárolnunk, akkor érdemesebb egy, több szegmenset magába foglaló lapba helyezni, elkerülve ezzel a memóriaveszteséget. A 32 bites, x86-os architektúrájú processzorok nem követelnek meg összefüggést a lap és szegmens méret között. Egy lap tartalmazhat a benne lévő szegmens vége után egy másik szegmens kezdetét. Hasonlóan a szegmens vége elhelyezkedhet egy másik lap kezdetén. A memória kezelő
szoftverek lehetnek egyszerűbbek, és hatékonyabbak azáltal, hogy valamilyen igazítást végeznek a lap és szegmens méretére vonatkozóan. Pl azon szegmenst mely elfér kb egy lapban azt inkább egy lapban helyezi el, mint kettőben amikor több információt kellene tárolni a lapozás során (két lap, több tárolt információ, bonyolultabb jog és hozzáférés vizsgálat illetve regisztráció). A másik előnye a lapozás és szegmentálás kombinálásának, hogy egyszerűsödik a memória kezelő szoftver feladata azáltal, hogy minden szegmens saját lap táblázatot kap, és így a lap táblázatokhoz tartozó egy lapcímtár bejegyzés által kézben tartható az egész szegmens jog illetve hozzáférés, és egyéb jellemzők viszonya. (Tehát egy lapcímtár bejegyzéssel akár megállapíthatjuk, hogy írtak -e az egész szegmensbe, vagy ezen egy bejegyzéssel a háttértárolóra kilapozhatjuk az egész szegmenset, stb.) Ezen megvalósítás sematikus ábrája:
Minden szegmenshez külön lap táblázatot biztosít a memória szervező szoftver A védelmi rendszer Védett módban, a 32 bites, x86 architektúrájú processzorok védelmi mechanizmust nyújtanak, mely mind a szegmens és mind a lapozás szinten működnek. Ez a védelmi mechanizmus teszi lehetővé, hogy korlátozzuk a hozzáférési jogot bizonyos szegmensekhez vagy lapokhoz a privilégium szinttől függően (négy privilégium szint definiált a szegmensek, és kettő a lapok számára). Például, az operációsrendszer nagy biztonságot igénylő adat- és kódszegmenseit megvédhetjük azáltal, hogy egy privilegizáltabb (számmal alacsonyabb privilégium szintű) szegmensbe helyezzük, mint az általa futtatott applikációkat. A processzor védelmi mechanizmusa meghatározott módon meg fogja akadályozni az illetéktelen alkalmazás kódrészletének az operációsrendszer kód- vagy adatterületeinek elérésében. A szegmens és lap védelem használható a
szoftver fejlesztés minden állapotában úgy, mint a fejlesztés közben a hibák felderítésére és úgy, mint a végtermékbe beágyazott tulajdonság, hogy minél erőteljesebb, megbízhatóbb operációsrendszert, utility programot vagy felhasználói programot fejlesszünk. Amikor a védelmi mechanizmus használt, minden memória mutatót megvizsgál a processzor, hogy megfelel -e a kívánt védelmi követelményeknek. Minden vizsgálat a fizikai memória ciklus megkezdése előtt zajlik le, hogy hiba esetén a kivétel időben generálódhasson. A védelmi vizsgálatok párhuzamosan zajlanak a címfordítással, így nem lép fel teljesítmény csökkenés ezek használatával. A védelmi vizsgálatok az alábbi részekre bonthatóak: • Határ (limit) vizsgálat • Típus vizsgálat • Privilégium szint vizsgálat • A címezhető tartomány korlátozása • A rutinok belépési pontjainak korlátozása • A használható utasítások korlátozása
Minden védelemsértés után kivétel generálódik. A szegmens és lap védelem engedélyezése és tiltása A CR0 regiszterben a PE flag bekapcsolásával a processzor védett módba kapcsol, ami a szegmens szintű védelmet automatikusan bekapcsolja. Amikor már védett módba léptünk, nincs vezérlő bit a szegmens szintű védelem közvetlen állítására, le- vagy bekapcsolására. A szegmens szintű privilégium szint védelem azonban letiltható pontosabban kikerülhető - úgy, hogy minden szegmens szelektornak, és leírónak 0-s (legprivilegizáltabb) privilégium szintet adunk. Ez a megoldás kikerüli az privilégium szintek közötti védelmet lévén, hogy csak a legprivilegizáltabb szinten helyezkednek el szegmensek, de továbbra is érvényes a többi védelem úgy, mint a határ, vagy típus vizsgálat. A lap szintű védelem a lapozás bekapcsolásával (a CR0-beli PG flag bekapcsolásával) automatikusan engedélyeződik. Itt sincs lehetőség a lapozás
bekapcsolása után annak védelmi mechanizmusának letiltására. Azonban az alábbi megoldással kikerülhető a lapozással együtt járó védelem: • Töröljük ki a CR0-beli WP flaget, • és kapcsoljuk be minden lapcímtár és lap táblázat olvasható/írható (R/W) és felhasználói/felügyelői (U/S) flagjét. Ez a beállítás minden lapot írhatóvá és felhasználói lappá tesz, így tulajdonképpen letiltottuk (kikerültük) a lap szintű védelmet. Szegmens és lap szintű védelemhez felhasznált mezők és flagek A processzor védelmi mechanizmusa az alábbi rendszerflageket és mezőket használja a szegmensek és lapok hozzáférésének meghatározásához: • Leíró típus (S - Descriptor type) flag (A 12. bit a szegmens leíró második doubleword-jében) Ez a bit meghatározza, hogy a szegmens leíró rendszer szegmens vagy kód- illetve adatszegmens leíró -e. • Típus mező (A 8-tól 11-ig vett bitek a szegmens leíró második
doubleword-jében.) Meghatározza a (kód / adat, vagy rendszer) szegmens típusát. • Határ (limit) mező (A 0-tól 15-ig vett bitek a szegmens leíró első doubleword-jében és a 16-tól 19-ig vett bitek a második doubleword-ben.) Meghatározza a szegmens méretét, illetve adatszegmens esetén a G és E flaggel együtt értelmezett. • G (Granularity - Lépésmérték, "szemcsézettség") flag (A 23. bit a szegmens leíró második doubleword-jében) Meghatározza a szegmens méretét, illetve adatszegmens esetén az E flaggel együtt értelmezett. (Egy korábbi részben bővebben tárgyaltuk.) • E (Expansion-direction - a terjeszkedés iránya) flag (A 10. bit az adatszegmens leíró második doublewordjében) Meghatározza a szegmens méretét, a határ mező és a G flaggel együtt (A szegmens leírók részben bővebben tárgyaltuk.) • Leíró privilégium szint mező (DPL) (A 13 és 14-es bitek a szegmens leíró második doubleword-jében.)
Meghatározza egy szegmens privilégium szintjét. • Igényelt privilégium szint mező (RPL) (A 0. és 1 bitek bármely szegmens kiválasztóban) Meghatározza az igényelt privilégium szintet egy szegmens kiválasztó számára. • Aktuális privilégium szint mező (CPL) (A 0. és 1 bitek a CS szegmens regiszterben) Megadja az éppen futtatott program vagy rutin privilégium szintjét. Az aktuális privilégium szint kifejezés (CPL) mindig a CS-ben elhelyezkedő ezen két bitre értelmezett. • Felhasználói/felügyelői flag (U/S) (A 2. bit a lapcímtárban vagy a lap táblázatban egy bejegyzésében) Meghatározza a lap típusát vagy felhasználói, vagy felügyelői lap. • Olvasható/Írható flag (R/W) (Az 1. bit a lapcímtár vagy a lap táblázat egy bejegyzésében) Meghatározza a lapon megengedett műveletet: olvasás, vagy olvasás és írás. Az alábbi ábra bemutatja a különböző mezők, flagek elhelyezkedését a kód-, adat-, és
rendszerszegmens leíróban. Az ábra az RPL (vagy CPL) elhelyezkedését mutatja meg a szegmens kiválasztóban (vagy a CS regiszterben). A ábrák megmutatják az U/S illetve R/W flagek elhelyezkedését a lapcímtár illetve lap táblázat egyes bejegyzéseiben. A védelem kialakításához használt leíró mezők Meglehetősen sokféle védelmi módot valósíthatunk meg ezen rendszerflagekkel, illetve mezőkkel. Amikor az operációsrendszer létrehoz egy leírót, akkor feltölti a szükséges értékekkel a megfelelő flageket, illetve mezőket. ezáltal alakítva ki az operációsrendszerre jellemző sajátos védelmi módot. A felhasználói programok nem férnek hozzá ezen flagekhez és mezőkhöz (pontosabban nem szabad hozzáférniük). Az alábbi szakaszok leírják, hogy a processzor hogyan használja ezeket a flageket és mezőket a különböző védelmi mechanizmusok megvalósítására. Határ ellenőrzés A szegmens leíró határ (limit) mezője
megakadályozza, hogy a programok vagy rutinok a számukra megengedett szegmensen kívül címezzenek. A konkrét értéke a határ mezőnek függ a G (Granualarity - Lépésmérték; "szemcsésség") flagtől (lásd az ábrát). Adatszegmensek esetén a határ függ még az E (Expansion direction - a terjeszkedés irányát jelző flag) és a B (a verem mutató méretét és/vagy a felső határt megadó) flagtől. Az E flag egy bitet jelent az adatszegmensek esetében a szegmens típus mezőben. Amikor a G flag kikapcsolt (bájt felbontás), akkor a tényleges határt a szegmens leíróban található 20 bites határ mező adja. Itt a határ 0 és FFFFFh (1 MBájt) között lehet Amikor a G flag bekapcsolt (4 KBájtos lapok szerinti felbontás), a processzor beszorozza a határ mező értékét 212-ennel (4 KBájttal). Ebben az esetben a tényleges határ FFFh (4 KBájt) és FFFFFFFFh (4 GBájt) között mozog. Megjegyzendő, ha a G flag bekapcsolt, akkor a szegmens ofszet
alsó 12 (cím) bite nincs ellenőrizve; pl. ha a szegmens határ 0, akkor a helyes ofszet 0 és FFFh között van Bármely szegmens, kivéve a lefelé terjedő típusúakat, a határ az az utolsó cím, melynek címzése még megengedett a szegmensben, bájtban mérve eggyel kisebb ez a legnagyobb cím, mint a szegmens mérete (a 0-tól való címzés miatt). A processzor általános védelmi hiba kivételt (#GP) generál minden esetben, ha az alábbi módon kívánunk egy címet a szegmensben elérni (mert ennek már egy része a szegmensen kívül esik): • Egy olyan bájt (8 bit) elérése, melynek nagyobb az ofszetje, mint a határ • Egy olyan word (16 bit) elérése, melynek nagyobb az ofszetje, mint a (határ - 1) • Egy olyan doubleword (32 bit) elérése, melynek nagyobb az ofszetje, mint a (határ-3) • Egy olyan quadword (64 bit) elérése, melynek nagyobb az ofszetje, mint a (határ-7) A lefelé bővülő (lefelé terjeszkedő) adatszegmensek számára is
hasonló a szegmens határ lényege, de másképp értelmezett. Itt a tényleges határ megadja azt az utolsó címet amelyhez már nem férhetünk hozzá a szegmensben; a használható ofszetek a (tényleges határ + 1) érték és az FFFFFFFFh között mozognak, ha a B flag bekapcsolt. Ellenkező esetben (B=0) a helyes ofszet a (tényleges határ + 1) és FFFFh között mozog. Ebből következően a lefelé bővülő szegmensnek akkor maximális a mérete, ha a szegmens határ 0. A határ vizsgálat elkapja a program hibákat úgy, mint a kód túlfutását vagy a hibás mutató számításból eredő hibás címzést. Ezen hibák detektálása és jelzése a hiba bekövetkezésekor történik, így könnyebb a hiba okának kiderítése Határ vizsgálat nélkül ezen hibák átírnának más kód- vagy adatszegmenseket, mely még nagyobb hibához vezetne a program vagy operációsrendszer későbbi működésében. A szegmens határ vizsgáltán kívül a processzor megvizsgálja a
leírótáblák határait is. A GDTR és IDTR regiszter tartalmaz egy 16 bites határ értéket, melyet használva a processzor megakadályozza, hogy a program a leírótáblán kívüli szegmens leírót választhasson ki. Két okból lehet veszélyes a saját leírótáblán kívüli érték kiválasztása, mert: • • • lehet, hogy így más éppen e tábla utáni leírótábla értékét választja ki, s így rossz helyen ír illetve olvas, vagy ha a leíró tábla után adat vagy kódszegmens van, akkor a hibásan kiválasztott szegmens leíró nem létező címre mutathat és/vagy hibás jogosultságok erednek a tévesen értelmezett adatból ) Az LDTR és a TR regiszter egy 32 bites szegmens határ értéket tartalmaz, melyet az LDT GDT-ben elhelyezett leírója tárol (minden taszknak más LDT-je lehet; ez a TSS-ben, a CR3 által megadott), s a TR regiszterben található kiválasztó által indexelt leírót (a TSS-re mutató leírót) hasonlóan a GDT tartalmazza. A
processzor hasonlóan arra használja itt is a szegmens határt, hogy megakadályozza a szegmensen kívüli címzést, a leírótáblán kívüli leíró kiválasztást. (Bővebben a "Szegmens leíró táblák"-nál) Típusellenőrzés A szegmens leírók az alábbi két féle módon tartalmaznak típus információkat: • Az S (leíró típus) flag és • A típus mező A processzor arra használja ezt az információt, hogy észlelje azokat a programhibákat melyek a helytelen vagy adott környezetben hibás szegmens vagy kapu használatból erednek. Az S flag jelzi, hogy a leíró rendszer típusú vagy kód illetve adat típusú. A típus mező további négy bitet tartalmaz arra, hogy különböző kód-, adat-, vagy rendszerszegmens típusokat definiáljunk. Az első táblázat megadja a kód- és adatszegmens leíró típusokat, s a második táblázat a rendszerszegmensek leíróinak típusait definiálja. A processzor megvizsgálja a típus információt
mindannyiszor, amikor szegmens kiválasztókkal és leírókkal dolgozunk. Az alábbi felsorolás leírja ezen típus vizsgálatok tipikus eseteit a teljesség igénye nélkül: • Amikor szegmens kiválasztót töltünk a szegmens regiszterbe. Bizonyos szegmens regiszterek csak bizonyos típusú leírókat tartalmazhatnak. Pl: • A CS regiszterbe csak kódszegmens leírója tölthető • A kódszegmensek nem olvashatóak, illetve a rendszerszegmens leírók nem tölthetőek adatszegmens regiszterbe (DS, ES, FS, és GS) • Csak írható adatszegmenst definiáló szegmensleíró tölthető az SS regiszterbe • Amikor egy szegmens kiválasztót töltünk az LDTR vagy TR regiszterbe. • Az LDTR regiszterbe csak LDT definiáló szegmensleíró tölthető • A TR regiszterbe csak TSS definiáló szegmensleíró tölthető • Amikor utasítások nyúlnak azon szegmens regiszterekhez, melyek már a megfelelő szegmens kiválasztót tartalmazzák. Bizonyos
szegmensekben, bizonyos utasítások csak megadott előredefiniált módon használhatóak, pl: • Nincs olyan utasítás, mely írhatna egy futtatható szegmensbe • Nincs utasítás, mely írhatna a nem-írható adatszegmensbe • Nincs utasítás, mely olvashatná azon futatható szegmenst ahol az olvasható flag nem bekapcsolt • Amikor egy utasítás operandusa tartalmaz egy szegmens kiválasztót. Bizonyos utasítások csak különleges módon érhetnek el egy szegmenst vagy kaput, pl.: • Egy távoli hívás (far CALL) vagy a távoli ugrás (far JMP) utasítás csak illeszkedő kódszegmens, vagy nem illeszkedő kódszegmens, vagy call-kapu, vagy taszk kapu, vagy TSS szegmens leírójához férhet hozzá. • Az LLDT utasítás csak LDT-t reprezentáló leíróval hívható meg. • Az LTR utasítás csak TSS-t reprezentáló leíróval hívható meg. • A LAR utasításnak egy szegmens vagy kapu leírót kell reprezentálnia egy LDT, vagy TSS, vagy
call-kapu, vagy taszk kapu, adatszegmens számára. • Az LSL utasításnak egy szegmens leírót kell reprezentálnia egy LDT, vagy TSS, vagy kód- vagy adatszegmens számára. • Az IDT (Megszakítás leíró tábla - Interrupt Descriptor Table) bejegyzései csak megszakítás, csapda (trap), vagy taszk kapu típusú bejegyzéseket tartalmazhat. • Bizonyos belső utasítások alatt. Pl: • Egy távoli híváskor vagy távoli ugráskor (mely a far CALL vagy far JMP utasítással lett végrehajtva), a processzor megvizsgálja a vezérlés átadásának típusát (mely a hívás vagy ugrás más kódszegmensre, vagy hívás kapun keresztül, vagy taszk váltáskor következik be). A vezérlésátadás típusát azáltal ellenőzi, hogy megnézi a CALL vagy JMP parancs operandusában megadott szegmens (vagy kapu) leíró típusát. Ha a leíró típusa kódszegmens, vagy call-kapu, akkor hívás vagy ugrás következik be a másik kódszegmensre. Ha a leíró típusa TSS
vagy taszk kapu, akkor taszk váltás következik be. • Egy hívás, vagy ugrás a call-kapun keresztül (vagy egy megszakítás illetve kivétel lekezelő rutin hívása csapda vagy megszakítás kapun keresztül) esetben a processzor automatikusan megvizsgálja azt a szegmens leírót, melyet a kapu fog szolgáltatni a kódszegmensnek. • Egy hívás vagy ugrás egy új taszkra a taszk kapun keresztül (vagy egy megszakítás illetve kivétel lekezelő rutin hívása az új taszkra a taszk kapun keresztül) esetben a processzor automatikusan megvizsgálja azt a szegmens leírót, melyet a taszk kapu fog szolgáltatni a TSS gyanánt. • Hívás vagy ugrás egy új taszkra annak TSS-ére való direkt hivatkozásával, a processzor automatikusan megvizsgálja azt a szegmens leírót, melyet a CALL vagy JMP utasítás operandusa tartalmaz, hogy az vajon tényleg TSS típusú -e. • Egy beágazott taszkból való visszatérés során (az IRET utasítás által), a processzor
automatikusan megvizsgálja az előző taszkra mutató linket az aktuális TSS-ben, hogy az vajon tényleg TSS-re mutat -e. Null szegmens kiválasztó vizsgálata Kísérlet arra, hogy null szegmens kiválasztót töltsünk (lásd a "Szegmens kiválasztók" fejezetet) a CS vagy SS regiszterbe általános védelmi hibát (#GP) von maga után. Null szegmens kiválasztót tölthetünk a DS, ES, FS, vagy GS regiszterekbe, de bármely próbálkozás arra, hogy ezen null kiválasztót tartalmazó szegmensregiszteren keresztül érjünk el egy szegmenst általános védelmi hibát generál (#GP) a processzor. A nem használt adatszegmens regiszterek null szegmens kiválasztóval történő feltöltése egy hasznos megoldás arra, hogy megfigyeljük a nem használt szegmensekhez való hozzáférést és/vagy, hogy megakadályozzuk a nem kívánatos adatszegmenshez való hozzáférést. Privilégiumszintek A processzor szegmens védelmi mechanizmusa 4 privilégium szintet
különböztet meg, 0-tól, 3-ig számozva. A legnagyobb számmal jelzett privilégium szint a legkevesebb jogot jelenti. Az alábbi ábra bemutatja, hogy hogyan értelmezhetőek ezek a privilégium szintek, védelmi gyűrűként. A középső gyűrű (fenntartott a legprivilegizáltabb kód, adat és verem részére) használt a kritikus szoftver úgy, mint az operációsrendszer kernelének szegmenseinek tárolására. A külső gyűrűk kevésbé kritikus szoftver elhelyezésére használhatóak (Az olyan operációsrendszerek melyek nem kívánják kihasználni a teljes hardveres védelmet elég 2 privilégium szintet használniuk a 4-ből. Erre a javasolt privilégium szintek a 0-s a kernelnek és a 3-as az egyéb illetve felhasználói programoknak. A mai elterjedt operációs rendszerek legritkább esetben használják ki mind a 4 privilégium szintet: Windows 98, Windows NT két privilégium szint, OS/2 és Linux három (0,1,3).) A processzor arra használja a privilégium
szinteket, hogy megakadályozza a programot vagy taszkot abban, hogy nála számmal alacsonyabb privilégium szinten (az-az privilegizáltabb szinten) dolgozzon. Ez alól kivételt képez az előre megtervezett, kontrollált körülmények közötti alacsonyabb privilégium szintű szegmensek elérése (tehát csak bizonyos módon, kapukon keresztül érhetőek el az aktuálisnál privilegizáltabb szegmensek). Amikor a processzor privilégium szint sértést észlel általános védelmi hiba kivételt (#GP) generál. A privilégium szint ellenőrzésére a kód- és adatszegmensek között a processzor az alábbi három típusú privilégium szintet különbözteti meg: • Aktuális privilégium szint (Current privilege level - CPL, ezután csak: CPL). A CPL az éppen futtatott program vagy taszk privilégium szintjét jelenti. Ez a CS és SS regiszter 0 és 1 bitei tárolják Általában a privilégium szint megegyezik annak a kódszegmensnek a privilégium szintjével, melyről
az utasítások éppen végrehajtódnak. A processzor akkor változtatja meg a CPL-t, amikor a végrehajtás egy másik privilégium szintű kódszegmensre kerül. A CPL-t különböző módon kezeli a processzor illeszkedő, illetve nem illeszkedő kódszegmensek esetén. Illeszkedő (conforming) kódszegmens bármely privilégium szintről elérhető, mely egyenlő vagy számmal nagyobb (kevésbé privilegizált) privilégium szintű, mint az illeszkedő kódszegmens DPL-je. Szintén a CPL nem változik, ha processzor az aktuális CPL-től eltérő privilégium szintű illeszkedő kódszegmenst hív meg. • Leíró privilégium szint (Descriptor privilege level - DPL, ezután csak: DPL). A DPL egy szegmens vagy kapu privilégium szintjét adja meg. Ez a szegmens vagy kapu leíró DPL mezőjében van tárolva Amikor az éppen futó kódszegmens megpróbál hozzáférni egy szegmenshez vagy kapuhoz akkor annak DPL-jét összehasonlítja a CPL-lel és az RPL-lel a processzor
(részletesebben alább). A DPL különböző módon értelmezett a leíró típustól függően: • Adatszegmens: A DPL megmutatja azt a számmal jelzett legnagyobb privilégium szintet, amiről a szegmens elérése még megengedett (tehát ezen számmal kisebb egyenlőnek kell lennie a CPL-nek). Pl: A DPL egy adatszegmens privilégium szintjét jelenti, melynek értéke legyen most 1. Így ehhez a szegmenshez alapértelmezés szerint csak a 0, vagy 1-es privilégium szinten futó programok (CPL = 0, 1) férhetnek hozzá. • Nem illeszkedő kódszegmensek használata call-kapu nélkül: A DPL megadja azt a privilégium szintet, mellyel a programnak vagy taszknak rendelkeznie kell ahhoz, hogy elérhesse ezt a szegmenst. Pl: ha a DPLje a nem illeszkedő kódszegmensnek 0, akkor csak a 0 CPL-en futó programok vagy taszkok érhetik el ezt a szegmenst. • Call-kapu (Call gate): A DPL megmutatja azt a számmal jelzett legnagyobb privilégium szintet, amivel az éppen futó
program vagy taszk még elérheti a call-kaput. (Hasonló a hozzáférési szabály az adatszegmensnél elmondottakhoz.) • Illeszkedő és nem illeszkedő kódszegmens elérése call-kapun keresztül: A DPL megadja azt a számmal jelzett legkisebb privilégium szintet, melynél a program vagy taszknak még megengedett a szegmens elérése. Pl: ha egy illeszkedő kódszegmens DPL-je 2, akkor a CPL 0, vagy 1 szinten futó programok nem érhetik el. • TSS: A DPL megmutatja azt a számmal jelzett legnagyobb privilégium szintet, amivel az éppen futó program vagy taszk még elérheti a taszk kaput. (Hasonló a hozzáférési szabály az adatszegmensnél elmondottakhoz) • Igényelt privilégium szint (Requested privilege level - RPL, ezután csak: RPL). Az RPL egy felülbíráló privilégium szint, mely a szegmens kiválasztókhoz rendelt. Ez a szegmens kiválasztók 0 és 1-es bitein helyezkednek el. A processzor ellenőrzi az RPL-t a CPL-lel együtt, hogy megállapítsa,
engedélyezett -e a szegmenshez való hozzáférés. Még akkor is, ha szegmenst elérni kívánó programnak vagy taszknak meg van az elegendő jogosultsága a szegmens eléréséhez, a processzor akkor is letilthatja az elérést, ha az RPL nem megfelelő nagyságú privilégium ad (determinál). Ez az, ha a szegmens kiválasztó RPL-je számmal jelezve nagyobb, mint a CPL, akkor az RPL felülbírálja a CPL-t és oda-vissza. Az RPL használható arra, hogy biztosak lehessünk abban, hogy a privilegizált kód nem fér hozzá a felhasználói program nevében a kívánt szegmenshez; csak akkor, ha magának a programnak (az igénylőnek) is meg van arra a szegmensre a megfelelő jogosultsága. (Tehát egy kevésbé privilegizált program nem férhet hozzá egy privilegizáltabb, pl az operációs rendszer kernel-ének segítségével a tőle eltiltott szegmensekhez alapesetben.) (Lásd a "A hívó hozzáférési jogainak vizsgálata (az ARPL utasítás)" részt az RPL
részletesebb illetve tipikus használatának leírása végett.) A privilégium szintek ellenőrzése a szegmens leíróra mutató szegmens kiválasztó (szegmens) regiszterbe töltésekor történik. Az adatszegmensek vizsgálata különbözik a kódszegmensek közötti végrehajtás átadási vizsgálattól, így az alábbi két külön részben tárgyaljuk. A privilégium szint vizsgálat adatszegmensek hozzáférése esetén Ahhoz, hogy hozzáférjünk az adatszegmenshez, a szegmens kiválasztót az adatszegmens (DS, ES, FG, vagy GS), vagy a veremszegmens regiszterbe kell tölteni (SS). (A szegmens regisztereknek a MOV, POP, LDS, LES, LFS, LGS, és LSS utasításokkal adhatunk értéket.) Mielőtt a processzor beletöltené az értéket a szegmens regiszterbe privilégium vizsgálatot készít, (lásd ábra) azáltal, hogy összehasonlítja az éppen futó program vagy taszk privilégium szintjét (a CPL-t), a szegmens kiválasztó által szolgáltatott RPL-t, és a szegmens
leíró által szolgáltatott DPL-t (ez az a szegmens leíró, melyet a szegmens kiválasztó indexelt meg). A processzor a szegmens regiszterbe tölti a szegmens kiválasztót, ha a DPL számmal kifejezve nagyobb vagy egyenlő, mint a CPL és az RPL. Ellenkező esetben általános védelmi hiba generálódik (#GP) és a szegmens kiválasztó nem töltődik a regiszterbe. Privilégium vizsgálat adat éléréskor Az alábbi ábra négy rutint (mindegyik más kódszegmensben, rendre: A, B, C, és D) mutat be, s mindegyik más privilégium szinten helyezkedik el. Mind a négy rutin ugyanahhoz az adatszegmenshez kíván hozzáférni • Az A kódszegmensben található rutin el tudja érni az E-vel jelölt adatszegmenst az E1 szegmens kiválasztóval, mert az A kódszegmens CPL-je, és az E1 szegmens kiválasztó RPL-je egyenlő az E adatszegmens DPL-jével. • A B kódszegmensben található rutin el tudja érni az E-vel jelölt adatszegmenst az E2 szegmens kiválasztóval,
mert az A kódszegmens CPL-je, és az E2 szegmens kiválasztó RPL-je egyformán számmal jelezve kisebb (privilegizáltabb), mint az E adatszegmens DPL-je. A B kódszegmensbeli rutin szintén elérheti az E adatszegmenst az E1-gyel jelölt szegmens kiválasztóval. • A C kódszegmensben található rutin nem tudja elérni az E-vel jelölt adatszegmenst az E3 szegmens kiválasztóval (szaggatott vonal az ábrán), mert az C kódszegmens CPL-je, és az E3 szegmens kiválasztó RPLje szám szerint nagyobb (kevésbé privilegizált), mint a E adatszegmens DPL-je. Még abban az esetben se érheti el a példánkbeli C rutin az E adatszegmenst ha az E1 vagy E2 szegmens kiválasztókat használná ugyanis igaz, hogy az RPL megfelelő értékű lenne, de még mindig nem férhetne a szegmenshez, hisz a CPL-je nem eléggé privilegizált (számmal: túl nagy a CPL értéke az eléréshez). Példa adatszegmens elérésére, különböző privilégium szintekről • A D kódszegmensben
található rutin elérhetné az E adatszegmenst hiszen a D kódszegmens CPL-je szám szerint kisebb, mint az adatszegmens DPL-je. Viszont az E3 szegmens kiválasztó RPL-je (az E3 szegmens kiválasztót használja a D kódszegmensbeli rutin az E adatszegmens elérésére példánkban), tehát az RPL szám szerint nagyobb, mint az adatszegmens DPL-je, így mégsem érheti el a kívánt szegmenst. Ha a D kódszegmensbeli rutin az E1 vagy E2 szegmens kiválasztókat használná a hozzáférés engedélyezett lenne. Amint azt az előző példa bemutatta, a címezhető tartomány is úgy változik ahogy a program vagy taszk CPL-je változik. Amikor a CPL 0, akkor bármely privilégium szint adatszegmensei elérhetőek, ha CPL 1, akkor az 1 és 3 közötti privilégium szintek adatszegmensei érhetőek el, s ha a CPL 3, akkor csak a 3-as privilégium szinthez tarozó adatszegmensek érhetőek el. A szegmens kiválasztó RPL-je mindig felülírhatja a program vagy taszk privilégium szint
szerint címezhető tartományát. Ha az RPL-eket helyesen választjuk meg, számos hibát előzhetünk meg a véletlen (vagy szándékos) olyan szegmens kiválasztók használatából eredően, amikor kevésbé privilegizált kóddal akarunk privilegizáltabb adatszegmenshez hozzáférni. Fontos megjegyezni, hogy az adatszegmens RPL-je a szoftver hatásköre alá tartozik, így nagy figyelemmel kell eljárni. Pl, egy felhasználói program 3-as CPL-en futva is beállíthatja az adatszegmens kiválasztó RPL-jét 0-ra Így az RPL 0-ra állításával, csak a CPL vizsgálata történik meg. Lévén, hogy az RPL vizsgálata figyelmen kívül hagyódik szándékos védelem kikerülés történik, direkt hozzáférés történik az adatszegmenshez a privilégium szint vizsgálat megsértésével. Hogy megakadályozzuk ezen privilégium szint vizsgálat megsértéseket, a program vagy rutin megvizsgálhatja a hozzáférést, valahányszor adatszegmens kiválasztót kap egy másik rutintól
vagy programtól. (Bővebben a "A hívó hozzáférési jogainak vizsgálata (az ARPL utasítás)" részben.) Adat elérése a kódszegmensben Bizonyos esetekben szükséges lehet, hogy a kódszegmensben elhelyezett adat struktúrákhoz hozzáférhessünk. Az alábbi lehetőségek vannak az adat kódszegmensbeli elérésére: • Töltsünk egy adatszegmens regiszterbe egy nem illeszkedő, olvasható kódszegmenst definiáló szegmens leírót. • Töltsünk egy adatszegmens regiszterbe egy illeszkedő, olvasható kódszegmenst definiáló szegmens leírót. • Használjunk egy kódszegmenst felülbíráló prefixumot (CS), hogy olvashassuk az olvashatóra állított kódszegmenst a már CS regiszterbe töltött szegmens kiválasztóval. Az első mód hasonló az adatszegmensekhez való hozzáférésnél elmondottakhoz. A második mód mindig használható, hisz az illeszkedő kódszegmens privilégium szintje ténylegesen megegyezik a CPL-lel, a DPL-jére való
tekintet nélkül. A harmadik mód is hasonlóan mindig használható, hisz a CS regiszter által kiválasztott (kód)szegmens DPL-je megegyezik a CPL-lel (lévén, hogy mindig ugyanaz ez a két szám). Privilégium szint vizsgálat az SS regiszterbe töltéskor A privilégium szint vizsgálat történik az SS regiszterbe történő veremszegmens leíró töltésekor is. Itt minden a veremszegmenshez csatlakozó privilégium szintnek meg kell egyeznie a CPL-lel; tehát a CPL-nek, a veremszegmens kiválasztó RPL-jének és a veremszegmens leíró DPL-jének is azonosaknak kell lenniük. Ha az RPL és a DPL nem egyenlő a CPL-lel, akkor általános védelmi hiba (#GP) generálódik. Privilégium szint vizsgálat a kódszegmensek közötti program vezérlés átadásakor Ahhoz, hogy egyik kódszegmensből a másikra irányítsuk a vezérlést, a cél kódszegmens szegmens kiválasztóját a kódszegmens regiszterbe, a CS-be kell tölteni. Ennek a regiszterbe töltési folyamat
részeként a processzor megvizsgálja a cél kódszegmensének szegmens leíróját határ, típus, és privilégium szerint. Ha ezek a vizsgálatok sikeresen fejeződtek be, akkor a CS regiszterbe betöltött a kívánt szegmens kiválasztó, a programvezérlés átadódik az új kódszegmensre és a program futtatása az új kódszegmens EIP által mutatott első utasításán kezdődik. A program vezérlésének átadása a JMP, CALL, RET, INT n utasításokkal történik, és az IRET utasítással a megszakítás és kivétel kezelésnél szükséges vezérlés átadás történik. A kivételek, megszakítások, és az IRET utasítás speciális eseteit itt nem részletezzük. Ezen fejezet csak a JMP, CALL és RET utasítások szerepére szorítkozik A JMP vagy CALL utasítások más kódszegmenseket hívhatnak az alábbi négy módon: A parancs operandusa tartalmazza a cél kódszegmens szegmens kiválasztóját A parancs operandusa egy call-kapu leíróra mutat, mely tartalmazza
a cél kódszegmens szegmens kiválasztóját. A parancs operandusa egy TSS-re mutat, mely tartalmazza a cél kódszegmens szegmens kiválasztóját. A parancs operandusa egy taszk kapura mutat, mely egy TSS-re mutat, és ezen TSS tartalmazza a kívánt kódszegmens szegmens kiválasztóját. Az alábbi részek leírják az első két vezérlés átadási módot. Közvetlen hívás vagy ugrás kódszegmensre A közeli JMP, CALL és RET utasítások az aktuális kódszegmensben irányítják át a vezérlést, így privilégium szint vizsgálat nem történik. A távoli JMP, CALL, és RET utasítások az aktuálisból egy másik kódszegmensbe irányítják a program vezérlését, így a processzor privilégium szint vizsgálatot végez. Ha call-kapu nélkül irányítjuk át a vezérlést az aktuális szegmensről egy másikra, akkor a processzor négy féle privilégium információt vizsgál meg: Privilégium szint vizsgálat kapu nélküli vezérlés átadáskor A CPL
vizsgálata. (Itt a CPL az éppen futó, annak a kódszegmensnek a privilégium szintje, mely a hívást kezdeményezi. Ezen kódszegmens adott rutinja tartalmazza a hívást vagy ugrást) A DPL a cél szegmens, azon szegmens leírója által szolgáltatott privilégium szint, mely tartalmazza a meghívott rutint. Az RPL a cél kódszegmens, szegmens kiválasztójában megadott privilégium szint. (A szegmens regiszter megfelelő bitei.) Az illeszkedő (C - conforming) flag a cél kódszegmens szegmens leírójában található, mely meghatározza, hogy illeszkedő (a C flag bekapcsolva) vagy nem illeszkedő (C flag kikapcsolva) a kódszegmens. (A "Kód- és adatszegmens leíró típusok" részben ezen flaggel részletesen foglalkoztunk.) A CPL, RPL és DPL privilégium szintek összehasonlítása és értelmezése függ attól, hogy az illeszkedő (C) flag bekapcsolt -e. Az alábbi részeket ettől függően tárgyaljuk Nem illeszkedő kódszegmensekhez való
hozzáférés Amikor nem illeszkedő kódszegmensekhez férünk hozzá, a hívó kódszegmens CPL-jének egyenlőnek kell lennie a cél kódszegmens DPL-jével, különben a processzor általános védelmi hibát (#GP) generál. Például, az alábbi ábrában a C kódszegmens egy nem illeszkedő kódszegmens. Így az A kódszegmensben lévő rutin meghívhatja a C kódszegmensbeli rutint a C1 szegmens kiválasztót használva, ugyanis azonos privilégium szinten vannak (Az A kódszegmens CPL-je egyenlő a C kódszegmens DPL-jével). Viszont a B kódszegmensbeli rutin nem hívhatja meg a C kódszegmensbeli rutint, se a C1, se a C2 szegmens kiválasztóval, ugyanis a két kódszegmens eltérő privilégium szinten van. Példa illeszkedő és nem illeszkedő kódszegmensek elérése különböző privilégium szintekről A nem illeszkedő kódszegmensre mutató szegmens kiválasztó RPL-jének korlátozott szerepe van a privilégium szint vizsgálatban. Az RPL-nek szám szerint
kisebbnek vagy egyenlőnek kell lennie (privilegizáltabbnak), mint a hívó kódszegmens CPL-jének, hogy a vezérlés átirányítás megtörténhessen. Amint a fenti ábra mutatja a C1, C2 szegmens kiválasztók RPL-je lehet 0, 1, vagy 2 de semmiképpen sem lehet 3. Amikor a nem illeszkedő kódszegmens szegmens kiválasztója a CS regiszterbe töltődik, a privilégium szintet tartalmazó mező nem változik meg, marad az eredeti CPL (a hívó rutin CPL-je). Ez igaz még akkor is, ha a szegmens kiválasztó RPL-je különbözik a CPL-től. Illeszkedő kódszegmensek elérése Amikor illeszkedő kódszegmenst kívánunk elérni, a hívó rutint tartalmazó kódszegmens CPL-je szám szerint nagyobb, vagy egyenlő (kevésbé privilegizált) lehet, mint a cél kódszegmens DPL-je. Ha a CPL kisebb, mint a DPL a processzor általános védelmi hibát generál. (A cél (hívott) kódszegmens szegmens kiválasztójának RPL-je nem vizsgált illeszkedő kódszegmens esetében.) A fenti
ábrában a D, egy illeszkedő kódszegmenst jelöl. Mind az A és B kódszegmensbeli rutin elérheti a D kódszegmens kívánt rutinját (akár a D1, vagy D2 szegmens kiválasztóval, külön-külön), ugyanis mind az A illetve B kódszegmensnek nagyobb vagy egyenlő a CPL-je, mint a meghívott illeszkedő kódszegmens DPL-je. Illeszkedő kódszegmensek esetén, a DPL azt a legalacsonyabb (számú) privilégium szintet jelenti, melyről a hívó még elérheti a rutint a kívánt kódszegmensben. Megjegyzendő, hogy a D1 és D2 szegmens kiválasztók megegyeznek az RPL-jeiket kivéve. Annál fogva, hogy az RPLek nincsenek vizsgálva illeszkedő kódszegmens esetében, valójában a két szegmens kiválasztó felcserélhető Amikor a program vezérlés egy illeszkedő kódszegmensbe van irányítva, a CPL nem változik meg még akkor sem, ha a hívott kódszegmens DPL-je kisebb, mint a CPL. Ez az egyetlen olyan eset melyben eltérhet a CPL az aktuális kódszegmens DPL-jétől. Mivel a
CPL nem változik meg, nem történik verem váltás (ugyanis minden CPL váltáskor annak a privilégium szintnek megfelelő vermet kell használni). Illeszkedő szegmenseket, olyan kód modulokban használunk, mint például a matematikai könyvtárak, egyes kivétel kezelő rutinok, melyek támogatják ugyan az alkalmazást, de nincsen szükségük a védett rendszer-szolgáltatásokra. Ezen modulok részei ugyan az operációs rendszernek vagy futtatónak, ugyanakkor futtathatóak szám szerint nagyobb privilégium szintről (kevésbé privilegizált szintről), mint maga a rutin (pl. a kernel) privilégium szintje A CPL-t a hívó kódszegmens privilégium szintjén tartva amikor az illeszkedő kódszegmensre váltunk, elkerülhető hogy a felhasználói program (a hívó program) hozzáférhessen a nem illeszkedő kódszegmensekhez. (Tehát, ha az illeszkedő kódszegmensre való ugráskor a CPL a hívott illeszkedő kódszegmens CPL-jét venné át, akkor lehetősége lenne a
felhasználói programnak a nem illeszkedő kódszegmensek elérésére is, hiszen már egy privilegizáltabb CPL-re jutott az illeszkedő kódszegmensre váltás révén. Így semmiképpen sem érhető el jogtalanul a privilegizáltabb, védett adat (mely nem illeszkedő kódszegmensben van).) A legtöbb kódszegmens nem illeszkedő kódszegmens. Ezen szegmensek esetén csak akkor történhet meg a vezérlés átadás, ha a kódszegmens azonos privilégium szintű. Ellenkező esetben az egyetlen hozzáférési lehetőség csak a callkapun keresztül történhet Ezt a következő fejezetek írják le Kapu leírók Ahhoz, hogy a különböző privilégium szinteken elhelyezkedő kódszegmenseket irányított, felügyelt módon érhessük el, a processzor speciális leírókat, úgynevezett kapu leírókat nyújt. Négy féle kapu leírót különböztetünk meg: • Call-kapu (Call gate) • Csapda kapu (Trap gate) • Megszakítás kapu (Interrupt gate) • Taszk kapu
(Task gate) A taszk kapukat a taszk váltás során használjuk. A csapda és megszakítás kapuk egy speciális fajtája a call-kapunak, melyeket a kivétel és megszakítás kezelő rutinok használnak. Ezeket itt részletesebben nem tárgyaljuk Ez a fejezet a call-kapukat írja le. Call-kapuk A call-kapuk megkönnyítik a programoknak az irányított vezérlésátadást különböző privilégium szintek között. A callkapukat jellegzetesen csak az operációs rendszerekben használják, melyek a privilégium szint védelmet valósítják meg segítségükkel. A call-kapuk továbbá használatosak a 16 bites és 32 bites kódszegmensek közötti irányított vezérlés átadásra. Az alábbi ábra bemutatja a call-kapu leíró felépítését. A call-kapu leíró elhelyezhető a GDT vagy az LDT-ben, de nem helyezhető el az IDT-ben, a megszakítás leíró táblában. A call-kapu hat különféle szerepet lát el: • Meghatározza a kódszegmenst, melyet elkívánunk érni
• Definiál egy belépési pontot (entry point) a kódszegmensben az elérni kívánt rutinra • Meghatározza a privilégium szintet a hívó számára ahhoz, hogy elérhesse a rutint • Ha verem váltás történik, meghatározza a vermek között átmásolandó lehetséges paraméterek számát • Definiálja a cél veremre lementendő értékek méretét: a 16 bites kapuk 16 bitet, míg a 32 bites kapuk 32 bitet mentenek le • Meghatározza, hogy a call-kapu érvényes -e Call-kapu leíró A szegmens kiválasztó mező a call-kapu leíróban meghatározza az elérni kívánt kódszegmenst. Az ofszet pedig belépési pontot szolgáltat a kódszegmensbe. Ez a belépési pont pedig általában a meghívott rutin az első végrehajtandó utasítását (annak címét) adja meg. A DPL mező megadja a call-kapu privilégium szintjét, azt a privilégium szintet, mellyel a hívónak rendelkeznie kell, hogy elérhesse a kívánt rutint a call-kapun keresztül. A P flag
jelzi, hogy a call-kapu érvényes -e. (A call-kapu által mutatott kódszegmenst jelenlététét, annak szegmens leírójának P flagjét jelenti.) A paraméterek száma mező megadja verem váltás esetén az átmásolandó paraméterek számát a hívó verméből a meghívott, új kódszegmenshez tartozó verembe (lásd a 4.85-ös, a "Verem váltás" részt) A paraméterek száma megadja 16 bites call-kapu esetén az átmásolandó word-ök (szavak), illetve 32 bites call-kapu esetén az átmásolandó doubleword-ök (dupa szavak) számát. Fontos, hogy a kapu leíróban a P flag alapállapotban mindig 1-re állított. Amennyiben 0, akkor egy nem jelenlévő, nem betöltött (#NP - not present) kivétel generálódik, amikor a program ezen nem jelenlévő szegmens leírójához kíván hozzáférni. Az operációs rendszer használhatja a P flaget speciális saját céljaira is Például megfigyelhető segítségével, hogy a kaput hányszor használták. Ilyen esetben a
P flag kezdőértéke legyen 0, és első használatakor meghívódik a nem jelenlévő kivételkezelő rutin. Ekkor a kivétel kezelő rutin növeli eggyel a hozzáférés számát megadó számlálót, majd a P flaget 1-re állítja. Így a kivétel kezelő rutinból való visszatérés után már érvényes lesz a kapu leíró. Kódszegmens elérése call-kapun keresztül Ahhoz, hogy a call-kaput elérhessük, egy a kapura mutató távoli mutatót (far pointer) szolgáltat a CALL, illetve JMP utasítás operandusa. Ebből a mutatóból a szegmens kiválasztó rész azonosítja a call-kaput (lásd az ábra); a mutatóban az ofszet szükséges, de a processzor által nem használt, s nem is vizsgált, így bármilyen értékre állítható. Amikor a processzor elérte a call-kaput, akkor a call-kapuban található szegmens kiválasztót használja, hogy elérje a cél kódszegmens szegmens leíróját. (Ez a szegmens leíró lehet akár a GDT illetve akár az LDT-ben is) Ezután
kombinálja a kódszegmens leíróból nyert kezdőcímet a call-kapuban elhelyezett ofszettel, hogy lineáris címet képezzen az elérni kívánt kódszegmens rutinjának belépési pontjához. Call-kapu működési mechanizmusa Privilégium vizsgálat call-kapuval történő vezérlés átadás során Ahogy a fenti ábra mutatja, négy különböző privilégium szintet vizsgál meg a processzor, hogy eldöntse érvényes -e a vezérlés átadás a call-kapun keresztül: • A CPL (aktuális privilégium szint). • Az RPL (a kérelmező privilégium szintje) a call-kapu kiválasztójában. • A DPL (leíró privilégium szint) a call-kapu leírójában. • És a cél kódszegmens leírójának DPL-je. Továbbá a cél kódszegmens leírójában a C (illeszkedő) flag is vizsgált. A privilégium vizsgálati szabályok különbözőek attól függően, hogy a vezérlés átirányítás CALL vagy JMP paranccsal történt -e. Az alábbi táblázat foglalja össze a
privilégium vizsgálati szabályokat: Utasítás Privilégium vizsgálati szabályok CALL CPL ≤ call-kapu DPL; RPL ≤ call-kapu DPL Az illeszkedő cél kódszegmens DPL ≤ CPL A nem illeszkedő cél kódszegmens DPL ≤ DPL JMP CPL ≤ call-kapu DPL; RPL ≤ call-kapu DPL Az illeszkedő cél kódszegmens DPL ≤ CPL A nem illeszkedő cél kódszegmens DPL = DPL Privilégium vizsgálati szabályok a call-kapuk esetén A call-kapu leírójának DPL mezője meghatározza szám szerint azt a legmagasabb privilégium szintet ahonnan a hívó rutin még elérheti a call-kaput; ez a feltétele a call-kapu elérésének, hogy a hívó rutin CPL-jének kisebb vagy egyenlőnek kell lennie, mint a call-kapu DPL-jének. Például, az alábbi ábrában az A call-kapu DPL-je 3 Így bármely privilégium szintről (CPL lehet 0-tól 3-ig) elérhető a kapu, tehát tartalmazza mind az A, B, és C hívó rutinok kódszegmenseit. A B call-kapu DPL-je 2, így csak a 0, 1, 2 CPL-ü hívó
rutinok érhetik el, tehát csak a B és C kódszegmensbeli rutinok számára hozzáférhető. A szaggatott vonal az ábrán azt jelenti, hogy az A kódszegmensbeli hívó rutin nem férhet hozzá a B call-kapuhoz. A call-kapu szegmens kiválasztójában található RPL-nek a hívó CPL-jével megegyező követelményeket kell teljesítenie; tehát az RPL-nek kisebb vagy egyenlőnek kell lennie, mint a call-kapu DPL-jének. Például az ábrában, a C kódszegmensbeli hívó rutin hozzáférhet a B call-kapuhoz a B2 vagy B1 kapu kiválasztókat használva, de nem érheti el a B call-kaput a B3 kiválasztó használatával. Ha a privilégium szint vizsgálat sikeres, a hívó rutin és a call-kapu között, akkor a processzor a továbbiakban összehasonlítja a kódszegmens leírójának DPL-jét a hívó rutin CPL-jével. Itt különböznek a privilégium szint vizsgálatok attól függően, hogy a CALL vagy JMP utasítás kezdeményezte a vezérlés átirányítást. Csak a CALL
utasítás használhatja a call-kaput arra, hogy privilegizáltabb (szám szerint alacsonyabb privilégium szintű) nem illeszkedő kódszegmensbe irányítsa a vezérlést. Ez olyan esetben történik meg amikor a nem illeszkedő kódszegmens DPL-je kisebb, mint a CPL. A JMP utasítás nem illeszkedő kódszegmens esetén csak arra használhatja a call-kaput, hogy olyan (nem illeszkedő) kódszegmensbe adhatja át a vezérlést melynek DPL-je megegyezik a CPL-lel. A CALL és JMP utasítások egyaránt átirányíthatják a vezérlést egy privilegizáltabb illeszkedő kódszegmensbe; ez az amikor az illeszkedő kódszegmens DPL-je kisebb vagy egyenlő a CPL-lel. Ha meghívott nem illeszkedő kódszegmens privilegizáltabb (szám szerint alacsonyabb privilégium szinttű), a CPL kisebb, mint a cél kódszegmens DPL-je, akkor verem váltás történik (lásd a "Verem váltás" c. részt) Ha a hívás vagy ugrás (CALL / JMP) egy privilegizáltabb, de illeszkedő cél
kódszegmensbe történik, akkor a CPL nem változik meg és nem lép fel verem váltás. Példa call-kapu elérésére különböző privilégium szintekről A call-kapuk megengedik egy sima kódszegmensnek, hogy olyan rutinai legyenek melyeket különböző privilégium szintekről lehet elérni. Például ha az operációs rendszer egy kódszegmensben van, akkor lehet néhány olyan szolgáltatás, melyet mind az operációs rendszer és mind a felhasználói program használni kíván (pl. az olyan rutinok melyek a karakteres I/O-t látják el). Ezen rutinok számára a call-kaput beállíthatjuk úgy, hogy bármely privilégium szinről (0-tól 3-ig) elérhető legyen. A privilegizáltabb call-kapuk (0-s, vagy 1-es DPL-lel) beállíthatóak úgy, hogy csak az operációs rendszer szolgáltatásai (services) érhessék el, melyeket pedig az operációs rendszer fog használni (ilyenek pl az eszköz meghajtókat inicializáló rutinok). Veremváltás Valahányszor call-kaput
használunk a program vezérlés átadására egy privilegizáltabb, nem illeszkedő kódszegmensbe (amikor a nem illeszkedő cél kódszegmens DPL-je kisebb, mint a CPL), akkor a processzor automatikusan a cél szegmens privilégium szintjének megfelelő veremre vált. Erre a verem váltásra azért van szükség, hogy elkerüljük a privilegizáltabb kódszegmensű programok összeomlását az elégtelen veremterület miatt (amit az alacsony privilégiumú programok futtatása okozhat). Továbbá még megakadályozza az alacsonyabb privilégiumú rutinokat abban, hogy érintkezhessen (véletlenül vagy szándékosan) a privilegizáltabb rutinokkal a vermen keresztül. Minden taszknak 4 vermet kell definiálnia: egyet a felhasználó kód részére (a 3-as privilégium szintű kód) és egyetegyet a további 2-es, 1-es és 0-s privilégium szintek számára. Csak a használt - a létező - privilégium szintek számára kell vermet definiálni, így ha csak kettő (pl. 3-ás és a
0-s) használt akkor elég csak nekik, kettőt definiálni Mindegyik így definiált verem egymástól elkülönített szegmensben kell, hogy elhelyezkedjen és mindegyiket megfelelően szegmens kiválasztóval, illetve az ofszettel azonosítjuk (stack pointer - verem mutató). A 3-as privilégium szint számára a verem kiválasztó és a verem mutató az SS illetve ESS regiszterben található, amikor 3-as privilégium szintű kódot kívánunk végre hajtatni. Ha verem váltás történik, akkor automatikusan elmentődik az új verembe a hívó (a kódszegmens melyről elváltottunk) verem kiválasztója és verem mutatója. Mutatók a 0-s, 1-es és 2-es privilégium szintű vermekre az éppen futó taszk TSS-ében találhatóak (lásd az alábbi ábrát illetve a teljes ábrát: 6.2-es ábra) 6.2 ábra: 32 bites Taszk állapot szegmens (TSS) Részlet Minden verem mutató (amint az ábrán is megfigyelhető) tartalmaz egy (verem) szegmens kiválasztót és egy verem mutatót (az
ESP regiszterbe kell tölteni). Ezen kezdeti mutatók szigorúan csak olvasható értékűek A processzor nem változtatja meg őket míg a taszk fut. A (kezdeti) mutatókat csak arra használja a processzor, hogy új vermet hozzon létre, amikor privilegizáltabb (szám szerint alacsonyabb számú privilégium szint) kódszegmenst hívunk meg. Ezek a vermek felszabadulnak a hívott rutinból való visszatérés után. Amikor a rutint újra meghívjuk, akkor újra létrehoz a processzor egy új vermet a kezdeti mutatókkal. (A TSS, amint a fenti 62-es ábra részlet is mutatja, nem tartalmaz a 3-as privilégium szint számára verem kiválasztót vagy mutatót, ugyanis a processzor nem engedélyezi a program vezérlés átirányítást a 0, 1, 2-es CPL-ról egy 3-as CPL-ü rutinra kivéve, ha ilyen (0, 1, 2 CPL-ü) rutinból térünk vissza az őt meghívóra.) Az operációs rendszer a felelős azért, hogy minden használt privilégium szinthez készítsen verem leírót és, hogy
ezen leírókra mutató kiválasztókat és a verem mutatókat a TSS-be másolja. Minden veremnek írható/olvashatónak kell lennie (a szegmens leíró típus mezője adja meg) és megfelelő nagyságúnak kell lennie (a határ (limit) mező adja meg), hogy az alábbi elemeket eltárolhassa: • A hívó rutin SS, ESP, CS és EIP regisztereinek tartalmát el kell menteni. • A hívott rutinnak átadandó paramétereket, illetve az ideiglenes változókat is el kell tárolnia. • Az EFLAGS regisztert, illetve a hiba kódot is el kell tudnia tárolni, ha implicit hívás történik egy kivételt vagy megszakítást lekezelő rutinra. A vermen továbbá még kellhet annyi hely, hogy tartalmazhassa a további meghívott rutinok hasonló eltárolandó elemeit. Ugyanis a rutinok maguk is meghívnak más rutinokat, és az operációs rendszer is használhatja az egymásba ágyazott, többszörös megszakításokat. Minden veremnek olyan nagynak kell lennie, hogy a legrosszabb esetre
is felkészítsük, az-az nagyobb nagyságú paraméter listával és nagyon mély hívás egymásba ágyazással kell számolni az adott privilégium szinten, hogy elkerüljük az esetleges program hibát. (Ha az operációs rendszer nem használja a processzor multitaszking mechanizmusát, akkor is létre kell hozni legalább egy TSS-t, részint a verem szintű követelmények teljesítése végett.) Amikor egy rutin call-kapun keresztül hív egy másik rutint, akkor privilégium szint váltás történik, és a processzor az alábbi lépéseket hajtja végre a verem váltás megvalósításához, illetve hívott rutin új privilégium szinten történő futtatásához: 1. Használja a cél kódszegmens DPL-jét (az új CPL-t) mutatót képezve a TSS-ben elhelyezett új veremre (szegmens kiválasztó és verem mutató). 2. Kiolvassa az új verem (a verem amire váltunk) szegmens kiválasztóját és verem mutatóját az aktuális TSSből Bárminemű határ sértés a verem szegmens
kiválasztó vagy verem mutató vagy a verem szegmens leíró olvasása esetén, érvénytelen TSS (#TS) kivétel generálását vonja maga után. 3. Megvizsgálja a verem szegmens leírót a megfelelő jogosultságért és típusért, bármilyen jog illetve típus sértés hasonlóan érvénytelen TSS (#TS) kivétel generálását vonja maga után. 4. Átmenetileg elmenti az SS és ESP regiszterek aktuális értékét 5. Betölti az SS és ESP regiszterekbe a verem szegmens kiválasztót és verem mutatót 6. Lenyomja az ideiglenesen elmentett SS és ESP regisztereket (a hívó rutin verem kiválasztója és verem mutatója) az új verembe (lásd ábra). 7. Átmásolja a call-kapuban (a call-kapu paraméterek száma mezőben) meghatározott számú paramétert, a hívó verméből az új verembe. Ha a paraméterek száma 0, akkor nem másolódik át paraméter 8. Lenyomja az új verembe a visszatérési utasítás mutatót (a CS és EIP regiszterek aktuális értékét) 9. Betölti a
CS regiszterbe az új kódszegmens kiválasztót, és az EIP-be az új utasítás mutatót (a belépési pont ofszetje). És végül megkezdődik az meghívott rutin futtatása Ezt a hívási mechanizmust bővebben a 2. felhasznált irodalom 3 fejezete az "Utasításkészlet referencia", a CALL utasítás leírásánál tárgyalja. Itt bővebben tárgyalt a processzor által call-kapun keresztüli hívás esetén végrehajtott a privilégium szint és egyéb vizsgálat. Verem váltás privilégium szintek közötti hívás során A call-kapuban a paraméterek száma mező meghatározza azon adat elemek számét (egészen 31-ig) melyeket a processzor átmásol a hívó rutin verméből a hívott rutin vermébe. Ha több mint 31 paramétert kell átadni a meghívott rutinnak, akkor az egyik paraméter lehet egy mutató, mely egy a maradék átadandó paraméterek által létrehozott adatstruktúrára mutat, vagy az elmentett régi (az új rutint meghívó régi rutin
regiszterei) SS és ESP regiszterek is használhatóak, hogy elérhessük a régi verem területen a paramétereket. (Ez elméletileg sem sérti a biztonsági megfontolásokat, ugyanis csak olvasunk a régi veremből. Ugyanakkor multiuser-es környezetű operációs rendszer felépítésekor óvatosan bánjunk ezen lehetőséggel is.) A meghívott rutin számára átadandó adatelemek méretét a call-kapu mérete határozza meg, amint ezt a "Call-kapuk" rész leírja. Visszatérés a meghívott rutinból A RET utasítás használható egy közeli visszatérés (near return) végrehajtására, a távoli visszatérés (far return) egyaránt használható az azonos privilégium szinten belüli visszatérésre illetve egy másik privilégium szintre való visszatérésre. Ez az utasítás feltételezi, hogy a hívott rutinra CALL utasítással jutottunk el Nem támogatja a visszatérést egy JMP-vel elért utasításról, mert a JMP utasítás nem ment el visszatérési
utasítás mutatót a vermen. A közeli visszatérés csak az aktuális kódszegmensen belül hajt végre vezérlés átirányítást; viszont a processzor határ vizsgálatot végez. Amikor a processzor kiemeli a veremből a visszatérési utasítás mutatót az EIP regiszterbe, akkor végez vizsgálatot, hogy a pointer nem haladja -e meg az aktuális kódszegmens méretét. Azonos privilégium szinten a távoli visszatérés esetén, a processzor kiemeli a veremből mind a szegmens kiválasztót melyre majd a vezérlés visszatér, és mind az utasítás mutatót, mely helyen a végrehajtás folytatódik. Normál körülmények között ezek a mutatók valósak, hiszen a CALL utasítás által lettek a veremre lementve. Ennek ellenére, a processzor végrehajt privilégium vizsgálatot, hogy lekezelje azon eseteket mikor a rutin (véletlenül) módosítja a mutatót, vagy a verem integritása véletlenül megsérült egyéb hiba miatt. Távoli visszatérésnél mikor privilégium
szintet váltunk, csak kevésbé privilegizált szintre térhetünk vissza (amikor a kódszegmens DPL-je melyre visszatérünk szám szerint nagyobb, mint a CPL). A processzor használja a hívó rutin elmentett a CS regiszter RPL mezőjét (lásd ábra), hogy meghatározza, ha magasabb privilégium szint szükséges. Ha az RPL szám szerint nagyobb (kevésbé privilegizált), mint a CPL, akkor privilégium szinteken keresztüli visszatérés történik. A processzor az alábbi lépéseket hajtja végre távoli rutinból való visszatérés esetén: A verem a közeli és a távoli hívások esetén Verem váltás más a privilégium szintre történő hívás esetén 1. A processzor megvizsgálja az elmentett a CS regiszter RPL mezőjét, hogy szükség van -e privilégium szint váltásra a visszatérés során. 2. Betölti a CS és EIP regiszterekbe a hívott rutin vermébe elmentett megfelelő értékeket (Típus és privilégium szint vizsgálatot végez a processzor a
kódszegmens leírón és a kódszegmens kiválasztó RPL-jén.) 3. (Ha a RET utasítás tartalmaz egy paraméterek száma operandust és a visszatérés privilégium szint váltást igényel.) Hozzáadódik a paraméterek száma (bájtban mérve, a RET utasítás operandusából véve) az aktuális ESP regiszter értékéhez (miután ki lett véve a régi - hívó CS és EIP regiszter értéke), hogy átugorjuk a régi, most már szükségtelen paramétereket. Ezen művelet elvégzése után az ESP regiszter által mutatott elem a veremben az elmentett SS és ESP, a hívó rutin vermének mutatói (lásd a fenti két ábrát). (Figyelem, a RET utasításnál megadható paraméterek számának mérete bájtban meg kell, hogy egyezzen a call-kapuban megadott paraméterek számának szorozva egy paraméter méretével kialakított bájtok számával. Ennek hibás megadása általános védelmi hibát (#GP) vonhat maga után.) 4. (Ha a visszatérés privilégium szint váltást igényel)
Betöltődik az SS és ESP regiszterekbe a hívó vermén elmentett SS és ESP értékek (amikhez az előző pontban jutottunk hozzá a paraméterek felszabadításával), majd megtörténik a hívó vermére történő verem visszaváltás. A meghívott rutin SS és ESP regisztereinek értéke (melyek immáron felülíródtak) megsemmisülnek, ugyanis nincs rájuk szükség a továbbiakban. Bármilyen határ sértés a veremszegmens kiválasztó illetve a verem mutató regiszterbe töltése során általános védelmi hibát (#GP) generál. Az új veremszegmens leírót is megvizsgálja a processzor, a típusát és privilégiumait tekintve. 5. (Ha a RET utasítás tartalmaz egy paraméterek száma operandust) Hozzáadja a processzor a paraméterek számát (bájtban mért, a RET utasítás operandusából véve) az aktuális ESP regiszterhez, hogy visszafelé átlépjünk a hívó rutin vermében a paramétereket. Ezután a lépés után az ESP már nem vizsgált határ
szempontjából. Ha az ESP értéke a szegmens határon túl van, akkor azt csak a következő verem művelet során észleljük hibaként. 6. (Ha a visszatérés privilégium szint váltást igényel) Megvizsgálja a processzor a DS, ES, FS, és GS szegmens regisztereket. Ha bármelyik regiszternek ezek közül a DPL-je kisebb, mint az új CPL (kivéve az illeszkedő kódszegmenseket), akkor azon szegmens regiszterek null szegmens kiválasztóval lesznek feltöltve. A RET utasítás bővebb leírását lásd a 2. felhasznált irodalom 3 fejezete az "Utasításkészlet referencia", RET utasítás helyen. Itt bővebben tárgyalt a privilégium szint és egyéb védelmi vizsgálat, amikor a processzor távoli visszatérést hajt végre. Privilegizált műveletek és ellenőrzések Kiváltságos utasítások Néhány rendszer utasítás ("kiváltságos (privilegizált) utasításoknak" hívjuk őket) használata védve van a felhasználói programoktól. A
kiváltságos utasítások rendszer funkciókat befolyásolnak (úgymint a rendszer regiszterek írása) Ezen utasításokat csak 0-s CPL-ű (legprivilegizáltabb) privilégium szinten hajthatjuk végre. Ha egyet is az alábbi utasítások közül nem 0 CPL-en hajtunk végre, akkor általános védelmi hiba (#GP) generálódik. Az alábbi rendszer utasítások a kiváltságos utasítások: • LGDT - Érték töltése a GDT regiszterbe. • LLDT - Érték töltése az LDT regiszterbe. • LTR - Érték töltése az IDT regiszterbe. • MOV (vezérlő regiszterek) - Érték töltése a vezérlő regiszterbe (CRx), illetve regiszterből. • LMSW / (Load Machine Status Word) - A machine állapot word-be érték töltése. • CLTS / (Clear Task-Switched Flag) - A taszk váltás flag kitörlése a CR0 regiszterben. • MOV (debug regiszterek) - Érték töltése a debug regiszterbe illetve regiszterből. • INVD / (Invalidate cache, without writeback) - A cache
érvénytelenítése visszaírás nélkül. • WBINVD / (Invalidate cache, with writeback) - A cache érvénytelenítése visszaírással. • INVLPG / (Invalidate TLB entry) - A TLB egy (vagy több) bejegyzésének érvénytelenítése. • HLT / (Halt processor) - A processzor megállítása. • RDMSR / (Read Model-Specific Registers) - Modell specifikus regiszter olvasása. • WRMSR / (Write Model-Specific Registers) - Modell specifikus regiszter írása. • RDPMC / (Read Preformance-Monitoring Counter) - Teljesítmény figyelő számláló olvasása. • RDTSC / (Read Time-Stamp Counter) - Az órajel-számláló regiszter olvasása. Néhány a kiváltságos utasítások közül csak az újabb (pl. Intel) processzorokban található meg Mutatók érvényesítése Amikor védett módban használjuk a processzort, akkor érvényesít minden mutatót, hogy érvényt szerezzen a szegmensek közötti védelemnek, illetve fenntartsa a privilégium szintek közötti
izolációt. A mutató érvényesítés az alábbi vizsgálatokat tartalmazza: 1. Megvizsgálja a hozzáférési jogokat, hogy megállapítsa a szegmens típusát, hogy megegyezik -e amiként használni kívánjuk. 2. Megvizsgálja az írás illetve olvasási jogokat 3. Megvizsgálja a mutató ofszetet, hogy nem lépi -e túl a szegmens határt 4. Megvizsgálja, hogy a mutatót szolgáltatónak van -e engedélye a szegmens eléréséhez 5. Megvizsgálja az ofszet illeszkedést A processzor automatikusan végrehajtja az első, második és harmadik vizsgálatot az utasítás végrehajtásával párhuzamosan. A programnak magának kell a negyediket, explicit módon megvalósítania az ARPL utasítással Az ötödik vizsgálat az (ofszet illeszkedés) a 3-as privilégium szinten automatikusan lefut, ha az illeszkedés vizsgálat bekapcsolt. Az ofszet illeszkedésnek nincs hatása a privilégium szintek elválasztására A hozzáférési jogosultságok vizsgálata (LAR utasítás)
Amikor a processzor hozzáfér egy szegmenshez távoli mutatót használva, akkor végrehajt a távoli mutató által meghatározott szegmens leírón egy hozzáférési jogosultság vizsgálatot. Ez a vizsgálat arra irányul, hogy a processzor meggyőződjön arról, hogy a művelet, amit el akarunk végezni kompatíbilis -e a szegmens leíró típusával és (DPL) privilégium szintjével. Például, amikor védett módban hajtunk végre távoli hívást, a szegmens leíró típusának illeszkedő, vagy nem illeszkedő kódszegmensnek, vagy call-kapunak, vagy taszk kapunak, vagy TSS-nek kell lennie. Ha a hívás nem illeszkedő kódszegmensre irányul, akkor a kódszegmens DPL-jének egyenlőnek kell lennie a CPL-lel, és a kódszegmens kiválasztójának RPL-jének kisebb vagy egyenlőnek kell lennie a DPL-lel. Ha a típus, vagy privilégium szint nem kompatíbilis az utasítással, akkor megfelelő kivétel generálódik. Hogy megakadályozzuk az összeférhetetlenségi kivételek
generálódását, a szoftver megvizsgálhatja a szegmens leíró hozzáférési jogait a LAR (load access rights) utasítással. A LAR utasítás meghatároz egy szegmens kiválasztót a megvizsgálandó szegmens leíró számára és egy cél regisztert. Ezután az utasítás az alábbi műveleteket végzi el: 1. Megvizsgálja, hogy a szegmens kiválasztó nem nulla -e (hogy null kiválasztó -e)? 2. Megvizsgálja, hogy a szegmens kiválasztó a leíró táblán (GDT, vagy LDT) belüli szegmens leíróra mutat -e? 3. Megvizsgálja, hogy a szegmens leíró kód-, adat-, LDT, call-kapu, taszk kapu, vagy TSS-szegmens leíró típusú -e? 4. Ha szegmens egy nem illeszkedő kódszegmens, akkor megvizsgálja, hogy a szegmens leíró látható -e a CPLnél (az-az, hogy a CPL és a szegmens kiválasztó RPL-je kisebb vagy egyenlő -e a DPL-lel)? 5. A privilégium szint és típus vizsgálat sikeresen megtörtént, akkor betölti a szegmens leíró második doubleword-jét a cél regiszterbe
(maszkolva a 00FXFF00h értékkel, ahol X jelzi, hogy a megfelelő 4 bit határozatlan) és beállítja az EFLAGS regiszter ZF flag értékét. Ha a szegmens kiválasztó nem látható az aktuális privilégium szinten (CPL) vagy hibás a szegmens leíró típusa (a LAR utasítás számára: 3. pont), akkor az utasítás nem módosítja a cél regisztert, és kinullázza a ZF flaget. Ha a cél regiszterbe az utasítás betöltötte a maszkolt értéket, akkor a szoftver végrehajthat rajta további vizsgálatokat. Az olvasási és írási jogok vizsgálata (VERR és VERW utasítások) Amikor a processzor bármely kód- vagy adatszegmenshez hozzáfér, akkor megvizsgálja a szegmenshez rendelt olvasás, írás jogosultságokat, hogy ellenőrizze, hogy a várható olvasás és/vagy írás engedélyezett -e. A szoftver is megvizsgálhatja az olvasás, írás jogosultságokat a VERR (verify for reading) és a VERW (verify for writing) utasításokkal. Mindkét utasításnak a
megvizsgálandó szegmensre mutató szegmens kiválasztót kell megadnunk Az utasítás a meghívás után az alábbi műveleteket hajtja végre: 1. 2. 3. 4. Megvizsgálja, hogy a szegmens kiválasztó nem nulla -e (hogy null kiválasztó -e)? Megvizsgálja, hogy a szegmens kiválasztó a leíró táblán (GDT, vagy LDT) belüli szegmens leíróra mutat -e? Megvizsgálja, hogy a szegmens leíró kód- vagy adatszegmens leíró típusú -e? Ha szegmens egy nem illeszkedő kódszegmens, akkor megvizsgálja, hogy a szegmens leíró látható -e a CPLnél (az-az, hogy a CPL és a szegmens kiválasztó RPL-je kisebb vagy egyenlő -e a DPL-lel)? 5. Megvizsgálja, hogy a szegmens olvasható -e a VERR utasítás esetén, vagy írható -e a VERW utasítás esetén A VERR utasítás bekapcsolja a ZF flaget az EFLAGS regiszterben, ha a szegmens látható a CPL-nél és olvasható; a VERW utasítás esetén bekapcsolja a ZF flaget az EFLAGS regiszterben, ha a szegmens látható a CPL-nél és
írható. (A kódszegmens soha sem írható.) A ZF flag kinullázódik, ha bármely vizsgálat ezek közül sikertelen Vizsgálat, hogy a mutató ofszete a határon belül van -e (LSL utasítás) Amikor a processzor bármely szegmenshez hozzáfér, akkor végrehajt egy határ (limit) vizsgálatot, hogy meggyőződjön, hogy a mutató ofszete a szegmens határon belül van -e. A szoftver is végrehajthat ilyen határ vizsgálatot az LSL (load segment limit) utasítással. Hasonlóan a LAR utasításhoz, az LSL is meghatároz egy szegmens kiválasztót a szegmens leíróra melynek szegmens határát vizsgáljuk és egy cél regisztert. Ezután az utasítás az alábbi műveleteket végzi el: 1. Megvizsgálja, hogy a szegmens kiválasztó nem nulla -e (hogy null kiválasztó -e)? 2. Megvizsgálja, hogy a szegmens kiválasztó a leíró táblán (GDT, vagy LDT) belüli szegmens leíróra mutat -e? 3. Megvizsgálja, hogy a szegmens leíró kód, adat, LDT, vagy TSS szegmens leíró
típusú -e? 4. Ha szegmens egy nem illeszkedő kódszegmens, akkor megvizsgálja, hogy a szegmens leíró látható -e a CPLnél (az-az, hogy a CPL és a szegmens kiválasztó RPL-je kisebb vagy egyenlő -e a DPL-lel)? 5. Ha a privilégium szint és típus vizsgálat sikeres volt, akkor a kiszámított szegmens határt (a sima szegmens határ módosítva - beszorozva - a szegmens leíróban található G flag értéke szerint) betölti a cél regiszterbe és bekapcsolja az EFLAGS regiszterben a ZF flaget. Ha a szegmens kiválasztó nem látható az aktuális privilégium szinten (CPL) vagy hibás a szegmens leíró típusa (az LSL utasítás számára: 3. pont), akkor az utasítás nem módosítja a cél regisztert, és kinullázza a ZF flaget. Ha a cél regiszterbe az utasítás betöltötte a megfelelő szegmenshatár értéket, akkor a szoftver összehasonlíthatja a valós szegmenshatárt a mutató ofszetjével. A hívó jogainak vizsgálata (ARPL utasítás) A kérelmező
privilégium szintje (RPL) mező a szegmens kiválasztóban arra szolgál, hogy a hívott rutinnak átadja a hívó rutin aktuális privilégium szintjét (a hívó CPL-jét). Ezután a hívott rutin arra használja az RPL-t, hogy megállapítsa, hogy a szegmenshez a hozzáférés megengedett -e. Az operációs rendszerek rutinjai tipikusan arra használják az RPL-t, hogy megakadályozzák a kevésbé privilegizált programokat abban, hogy hozzáférjenek a privilegizáltabb adatszegmensekhez. Amikor az operációs rendszer egy rutinja (a hívott rutin) kap egy szegmens kiválasztót a felhasználói programtól (a hívó rutintól), akkor ez beállítja a szegmens kiválasztó RPL-jének értékét a hívó privilégium szintjére. Ezután az operációs rendszer használja a szegmens kiválasztót, hogy hozzáférhessen a hozzárendelt szegmenshez, a processzor pedig végrehajt egy privilégium szint vizsgálatot, használva a hívó rutin privilégium szintjét (amit az RPL-ben
tárolt) inkább, mint az operációs rendszer rutinjának szám szerint kisebb privilégium szintjét a CPL-t. Az RPL így biztosítja, hogy az operációs rendszert nem fér hozzá a felhasználói program érdekében olyan szegmenshez, melyhez a felhasználói program maga ne férhetne hozzá (az operációs rendszer csak olyan szegmenst fog elérni, melyet maga a felhasználó program is elér). Az alábbi ábra egy példát mutat, hogy hogyan használja a processzor az RPL mezőt. Ebben a példában, egy felhasználói program (az A kódszegmensben) rendelkezik egy szegmens kiválasztóval (D1 szegmens kiválasztó), ami egy privilegizált adatstruktúrára mutat (az adatstruktúra a D adatszegmensben található, 0 privilégium szinten). A felhasználói program nem férhet hozzá a D adatszegmenshez, mivel nincs elég joga, de az operációs rendszer (a C kódszegmensben) már igen. Tehát a D adatszegmenshez való hozzáférés (próbálása) során a felhasználói program
végrehajt egy hívást az operációs rendszerre, és átadja a D1 szegmens kiválasztót az operációs rendszernek, mint egy paramétert a vermen keresztül. Mielőtt átadná a szegmens kiválasztóját, a (jól viselkedő) felhasználói program beállítja a szegmens kiválasztó RPL mezőjét a saját privilégium szintjére a CPL-re (ami példánkban 3). Ha az operációs rendszer hozzá akar férni a D adatszegmenshez a D1 szegmens kiválasztót használva, akkor a processzor összehasonlítja a CPL-t (ami már 0 a hívás után), a D1 szegmens kiválasztó RPL-jét és a D adatszegmens DPL-jét (ami 0). Mivel az RPL nagyobb, mint a DPL a D adatszegmenshez való hozzáférés nem engedélyezett Így a processzor védelmi mechanizmusa megvédi a D adatszegmenst az operációs rendszer hozzáférésétől, ugyanis a felhasználói program privilégium szintje nagyobb (az B szegmens kiválasztó RPL-jében reprezentált privilégium szint), mint a D adatszegmens DPL-je. Az
RPL használata a hívott procedúra privilégium szintjének "gyengítésére" Tegyük fel, hogy a felhasználói program 0 RPL-t állít be a szegmens kiválasztóban (D2 szegmens kiválasztó) ahelyett, hogy 3-at állítana be. Most már az operációs rendszer hozzáférhet a D adatszegmenshez, hisz a CPL és a D2 szegmens kiválasztó RPL-je azonosan egyenlő a D adatszegmens DPL-jével. Mivel a felhasználói program képes a szegmens kiválasztó RPL-jét változtatni bármilyen értékre, egy kevésbé privilegizált rutin potenciálisan hozzáférhet egy védett adatstruktúrához. Ez a lehetőség - hogy az RPL szabadon választható - felborítja, felborítaná a processzor védelmi mechanizmusát. Mivel a hívott rutin nem bízhat abban, hogy a hívó helyesen állítja be az RPL-t, az operációs rendszer rutinjainak (alacsonyabb privilégium szinten végrehajtott rutinok), melyek magasabb privilégium szintről (kevésbé privilegizált szintről) szegmens
kiválasztókat fogadnak, meg kell vizsgálniuk a szegmens kiválasztó RPL-ét, hogy megfelelően van -e beállítva. Az ARPL (adjust requested privilege level) utasítás használható e célra Ez az utasítás beállítja az egyik szegmens kiválasztó RPL-jét úgy, hogy az hasonlítson a másik szegmens kiválasztóra. Megjegyzendő, hogy a felhasználó program privilégium szintje meghatározható úgy, hogy kódszegmensének RPL mezőjét kiolvassuk. Ez a szegmens kiválasztó a vermen van letárolva a hívás (az operációs rendszer rutinjának hívásának) részeként. Az operációs rendszer lemásolhatja ezt a szegmens kiválasztót a veremből egy regiszterbe, hogy az ARPL utasítás operandusaként használhassa. Az illeszkedés vizsgálata Amikor a CPL 3, a memória referenciák illeszkedését vizsgálhatjuk a CR0 regiszter AM flagjének és az EFLAGS regiszter AC flagjének bekapcsolásával. A nem illeszkedő memória referenciák illeszkedési kivételt
generálnak (#AC alignment exception) A processzor nem generál illeszkedési kivételt, a 0, 1, 2-es privilégium szinteken Lásd az 56os táblázatot, mely leírja a szükséges beállításokat illeszkedés vizsgálat esetén Lapszintű védelem A lap szintű védelem használható önmagában és a szegmensekkel együtt is. Amikor lap szintű védelem és az egybefüggő (sík) memória modellel használt, akkor megengedett a felügyelői (supervisor) kód és adat számára (az operációs rendszer vagy a futtató számára), hogy védett legyen a felhasználó kódtól illetve adattól. Továbbá tartalmazhat olyan lapokat, melyek írásvédett kódot tartalmaznak. Amikor a szegmentálásos és lapozásos védelmet kombináljuk, akkor a lap szintű olvasás, írás védelem megengedi, hogy a több lapra osztott szegmenseknek külön külön definiáljuk a védelmi jogait. A lap szintű védelemmel (ahogyan a szegmens szintű védelemmel) minden memória referencia megvizsgált,
hogy kielégíti -e a védelmi vizsgálatokat. Minden vizsgálat a tényleges memória ciklus megkezdése előtt indul el, bármilyen jogosultság sértés a memória ciklus elkezdését megakadályozza, illetve laphibát (#PF) generál. Mivel a védelmi vizsgálatok párhuzamosan történnek a címfordítással nincs sebesség, teljesítmény csökkentő hatása a védelem használatának. A processzor az alábbi kettő lap szintű védelmi vizsgálatot hajtja végre: • A címezhető tartomány leszűkítése (a felügyelői és a felhasználói módok) • Lap szintű vizsgálat (csak olvasható illetve olvasható, írható) E kettő védelem közül bármelyik megsértése védelmi hibát generál. Ezeket itt bővebben nem tárgyaljuk, ez a rész csak a védelmi jogok megsértését írja le, melyek laphibát okoznak. A címezhető tartomány korlátozása A lapozásos védelmi mechanizmus megengedi a hozzáférés korlátozását a lapok szintjén az alábbi két
privilégium szintre hagyatkozva: • Felügyelői mód (U/S flag 0) - (A legprivilegizáltabb.) Az operációs rendszer vagy futtató, vagy egyéb rendszer szoftver (úgymint device driver), és védett rendszer adat (úgymint a lap táblázat) számára. • Felhasználói mód (U/S flag 1) - (A legkevésbé privilegizált.) A felhasználói kód és adat számára A szegmens szintű privilégium szintek a következő képen egyeztethetőek a lapok védelmi szintjéhez. Ha a processzor jelenleg CPL 0, 1, vagy 2 szinten működik, akkor felügyelői (supervisor) módban van; ha 3-as CPL-en működik akkor felhasználói módban. Amikor a processzor felügyelői módban van, akkor hozzáfér minden laphoz; felhasználói módban csak a felhasználói-szintű lapokat éri el. (Fontos, hogy a CR0 regiszterbeli WP flag módosítja a felügyelői szintű engedélyeket, ahogy ezt az elkövetkező részekben leírjuk.) Megjegyzendő: ahhoz, hogy használhassuk a lap szintű védelmi
mechanizmust, a kód és adat szegmenseket legalább két szegmens-alapú privilégium szinttel kell használni: a 0-s szintet a felügyelői kód- és adatszegmensek számára, és a 3-as szintet a felhasználói kód- és adatszegmensek számára. (Ebben a modellben a verem az adatszegmensbe van ágyazva.) Hogy minimalizáljuk a szegmensek használatát, használhatjuk az egybefüggő (sík) memória modellt (Lásd a 3.21, "Sík modell" c részt) Itt, a felhasználói és felügyelői kód- és adatszegmensek mind a 0 lineáris címnél kezdődnek és átfedik egymást. Ezzel az elrendezéssel az operációs rendszer kódja (felügyelői privilégium szinten fut) és felhasználói kód (felhasználói privilégium szinten fut) akkor is tudnak futni, ha nincsenek szegmensek. A védelmet az operációs rendszer és felhasználói kód- illetve adatszegmensek között a processzor lap szintű védelmi mechanizmusa valósítja meg. Lap típus A lap szintű védelem az alábbi
két lap típust különbözteti meg: • Csak olvasható hozzáférés (az R/W értéke 0). • Olvasható és írható hozzáférés (az R/W értéke 1). Amikor a processzor felügyelői módban van és a CR0 regiszterbeli WP flag üres (az a reszet utáni állapot), az összes lap mind olvasható és írható (az írási védelem nem használt). Amikor a processzor felhasználói módban van, akkor csak azon lapokat írhatja melyek olvasható, írható hozzáférésre állítottak. Felhasználói módban az olvasható, írható vagy csak olvasható lapok olvashatóak; a felügyelői módú lapok se nem olvashatóak, se nem írhatóak felhasználói módból. Laphiba generálódik (#PF) bármely védelmi szabály megszegésének kísérletekor A P6 processzor család tagjai, a Pentium, és Intel486 processzorok megengedik, hogy a felhasználói lapok írásvédettek legyenek a felügyelői módú hozzáféréssel szemben. A CR0 regiszterbeli WP flag 1-re állításával
engedélyeztethetjük a felügyelői módú érzékenységet a felhasználói módú, írásvédett lapok számára. Ez a felügyelői írásvédelmi lehetőség használható, hogy a "copy-on-write" operációs rendszer (pl. Unix*) által használt stratégiát valósítsuk meg. Amikor egy új taszkot hozunk létre, lehetséges a szülő taszk egész címtartományának lemásolása az új taszk számára. Ez a gyermek taszk számára egy a szülőhöz hasonló teljesen ugyanolyan szegmenst és lapokat eredményez. Ez az alternatív "copy-on-write" stratégia memóriát, időt takarít meg azáltal, hogy a gyermek taszk szegmenseit és lapjait a szülő azonos szegmenseibe és lapjaiba képezzük le. A taszkok számára akkor készül saját másolat egy lapról, amikor valamelyik taszk írni kíván bele. A WP flag használata által és a megosztott lapok csak olvashatóra állításával, a felügyelő észlelheti a felhasználói szinten történő lap írás
próbálkozást, és létrehozhatja a lapmásolatokat azonnal. A védelem kombinálása mindkét lap tábla szinten Bármely lap számára, a lapcímtár (első szintű lap táblázat) védelmi attribútumok különbözhetnek a lap táblázat (második szintű lap táblázat) védelmi attribútumaitól. A processzor megvizsgálja mind a lapcímtár és lap táblázatok bejegyzéseit a védelmet illetően. A táblázat mutatja a védelem lehetséges kialakítását a védelmi attribútumok kombinációjaként, amikor a WP flag kikapcsolt. A lap szintű védelem felülbírálása Az alábbi memória hozzáférési típusok vizsgáltak, mivel 0 privilégium szintű hozzáférésnek számítanak a CPL-től függetlenül: • Szegmens leírók elérése a GDT, LDT vagy IDT-ben. • Egy belső privilégium szintű verem elérése belső privilégium szintű hívás során, vagy egy kivétel, illetve megszakítás rutin elérése olyan esetben, amikor privilégium szint váltás
szükséges. A lapozásos és szegmentálásos védelem kombinálása Amikor a lapozás engedélyezett, a processzor először a szegmentálásos védelmet elemzi ki először, majd a lapozásos védelmet. Ha a processzor bármilyen védelem sértést észlel akár a szegmentálásos, akár a lapozásos védelem során, akkor a memória hozzáférés nem hajtódik végre, és kivétel generálódik. Ha a szegmentálásos védelem megsértése miatt már generálódott kivétel, akkor a lapozásnál nem generálódik újabb. A lap szintű védelem nem használható a szegmentálásos védelem felülírására. Például, a kódszegmens definíciója szerint nem írható. Ha a kódszegmens be van lapozva, akkor az R/W flag beállítása nincs hatással a lapra, tehát a lap továbbra sem írható. A - kódszegmens - lapba történő írás próbálkozás, szegmentálásos védelmi kivételt generál A lap szintű védelem használható a szegmentálásos védelem kibővítésére.
Például, ha egy nagy méretű olvashatóírható adatszegmens belapozott, akkor használható a lapozásos mechanizmus arra, hogy (csak) egyes lapokat tegyünk írásvédetté. Lapcímtár bejegyzés Privilégium Hozzáférési típus Lap táblázat bejegyzés Privilégium Hozzáférési típus Együttes hatás Privilégium Hozzáférési típus Felhasználói Csak olvasható Felhasználói Csak olvasható Felhasználói Csak olvasható Felhasználói Csak olvasható Felhasználói Olvasható/írható Felhasználói Csak olvasható Felhasználói Olvasható/írható Felhasználói Csak olvasható Felhasználói Csak olvasható Felhasználói Olvasható/írható Felhasználói Olvasható/írható Felhasználói Olvasható/írható Felhasználói Csak olvasható Felügyelői Csak olvasható Felügyelői Olvasható/írható* Felhasználói Csak olvasható Felügyelői Olvasható/írható Felügyelői Olvasható/írható* Felhasználói Olvasható/írható
Felügyelői Csak olvasható Felügyelői Olvasható/írható* Felhasználói Olvasható/írható Felügyelői Olvasható/írható Felügyelői Olvasható/írható Felügyelői Csak olvasható Felhasználói Csak olvasható Felügyelői Olvasható/írható* Felügyelői Csak olvasható Felhasználói Olvasható/írható Felügyelői Olvasható/írható* Felügyelői Olvasható/írható Felhasználói Csak olvasható Felügyelői Olvasható/írható* Felügyelői Olvasható/írható Felhasználói Olvasható/írható Felügyelői Olvasható/írható Felügyelői Csak olvasható Felügyelői Csak olvasható Felügyelői Olvasható/írható* Felügyelői Csak olvasható Felügyelői Olvasható/írható Felügyelői Olvasható/írható* Felügyelői Olvasható/írható Felügyelői Csak olvasható Felügyelői Olvasható/írható* Felügyelői Olvasható/írható Felügyelői Olvasható/írható Felügyelői Olvasható/írható A lapcímtár
és a lap táblázat szintű védelem kombinálása Megjegyzés: * Ha a CR0 regiszterbeli WP flag bekapcsolt, akkor a hozzáférés típusa a lapcímtár és laptábla bejegyzésének R/W flagjétől függ