1. Reaalitilan muistinhallinta
2. Johdanto suojattuun tilaan
3. Suojatun tilan muistinhallinta
4. Keskeytykset ja poikkeukset

Katso myös:
1. Prashant TR:n laaja-alainen ja havainnollinen suojattua tilaa käsittelevä opas
2. Chris Giesen opas suojatusta tilasta

Suojatun tilan muistinhallinta

Tekijä: Yariv Kaplan
Suomentanut Jouni Kähkönen 18.4.2005, 24.10.2005, 29.3.2007, 30.4.2007

Johdanto

Suorittimen ollessa suojatussa tilassa muistinkäännösprosessissa (engl. memory translation process) käytetään kahta järjestelmää: segmentointia ja sivutusta. Vaikka nämä kaksi järjestelmää toimivat yhdessä, ne ovat täysin toisistaan riippumattomia. Periaatteessa voimme poistaa sivutusyksikön käytöstä nollaamalla suorittimen sisäisestä rekisteristä yhden ainoan bitin. Tällöin segmentointiyksikön luomat, lineaariset osoitteet sivuuttaisivat tyystin sivutusyksikön ja kulkisivat suoraan suorittimen osoiteväylään.


Kuvio 1 - Suojatun tilan osoitteenkäännösprosessi

Segmentointi

Segmentointiyksiköllä on suojatussa tilassa sama tehtävä kuin sillä on 8086-suorittimessa. Sen avulla käyttöjärjestelmä jakaa sovellukset loogisiin lohkoihin ja sijoittaa lohkot omille muistialueilleen. Tämän ansiosta sovelluksen kriittisten alueiden käyttöäkin voidaan rajoittaa. Lisäksi segmentointi helpottaa kehitettävien sovellusten virheiden paikantamista. 80386-suorittimen segmentointiyksikön toteutus (katso yllä) on laajennettu versio vanhan, 8086:n yksikön toteutuksesta. Mukana on lukuisia uusia ominaisuuksia kuten mahdollisuus määritellä muistissa oleville segmenteille täsmälliset sijainnit ja koot sekä mahdollisuus asettaa segmentille erityinen oikeustaso (engl. privilege level), jolla segmentin sisältö saadaan suojattua luvattomalta käytöltä.

Muistia käsitellään segmenttirekisterein niin reaali- kuin suojatussa tilassa. Näiden tilojen välillä on kuitenkin eroja, jotka on tarpeen käydä lähemmin läpi. Ensinnäkin termistössä on eroja. Suojatun tilan segmenttirekistereitä kutsutaan selektoreiksi. Nimitys kertoo niiden uudesta tehtävästä muistinkäännösprosessissa. Vaikka ne ovat yhäkin 16-bittisiä, suoritin tulkitsee ne luonnollisesti erilailla. Kuvio 2 kuvaa selektorin rakenteen muodostavat bittikentät.


Kuvio 2 - Selektorin sisäinen rakenne

Reaalitilassa segmenttirekistereitä (niin ikään selektoreita) siirretään neljä bittiä vasemmalle ja tuloon lisätään siirros, mutta suojatussa tilassa selektoreilla on eri merkitys; ne ovat deskriptoritaulukon indeksejä.

Deskriptoritaulukot

Deskriptoritaulukot sijaitsevat järjestelmän muistissa, ja suoritin käyttää niitä osoitteenkäännöksen suorittamiseen. Deskriptoritaulukon tietueet ovat kooltaan 8 tavua ja ne edustavat muistissa aina yhtä segmenttiä. Deskriptoritietue sisältää yhdistetyn segmentin ensimmäisen tavun osoitteen sekä segmentin koon määrittelevän 20-bittisen arvon. Deskriptoritietueessa on useita muitakin kenttiä, jotka sisältävät erityistietoa segmentin ominaisuuksista kuten oikeustason (engl. privilege level) ja segmentin lajin. Kuvio 3 kuvaa deskriptoritietueen tarkan rakenteen sekä sisältää kuvauksen tietueen kentistä.


Kuvio 3 - Deskriptoritietueen rakenne

Taulukko 1 sisältää kattavan luettelon kaikista deskriptorikentistä vaikutuksineen.

Kenttä Suunniteltu tehtävä
BASE
Segmentin kantaosoite (32 bittiä)
Tämä kenttä osoittaa segmentin aloituskohtaan 4 gigatavun kokoisessa lineaarisessa osoiteavaruudessa.
D/B
Segmentin koko (1 bitti)
Deskriptoritietueen kuvatessa koodisegmenttiä tällä bitillä esitetään operandien ja osoitteiden oletuspituus.
Tämän bitin ollessa päällä suoritin olettaa käytettävän 32-bittistä segmenttiä.
Tämän bitin ollessa pois päältä oletetaan käytössä olevan 16-bittinen segmentti.

Deskriptoritietueen kuvatessa datasegmenttiä tällä bitillä ohjataan pinon toimintaa.
Tämän bitin ollessa päällä pinotoiminnot käyttävät ESP-rekisteriä.
Tämän bitin ollessa pois päältä pinotoiminnot käyttävät SP-rekisteriä.

DPL
Deskriptorin oikeustaso (2 bittiä)
Tämä kenttä määrittelee segmentin oikeustason. Suorittimen sisäinen suojausjärjestelmä rajoittaa sillä segmentin käyttöä.
G
Granulariteetti eli tiheys (1 bitti)
Tämä bitti ohjaa segmentin raja-arvon määräävän kentän erottelutarkkuutta.
Tämän bitin ollessa pois päältä erottelutarkkuus asetetaan yhdeksi tavuksi.
Tämän bitin ollessa päällä erottelutarkkuus asetetaan 4 kilotavuksi.
LIMIT
Segmentin raja-arvo (20 bittiä)
Tämä kenttä määrää segmentin koon käyttäen yksikkönä yhtä tavua (granulariteettibitin ollessa pois päältä) tai käyttäen yksikkönä 4 kilotavua (granulariteettibitin ollessa päällä).
P
Segmentin läsnäolo (1 bitti)
Tämä bitti määrää, onko segmentti muistissa.
Kun tämä bitti on pois päältä, ladattaessa segmenttirekisteriin jotakin deskriptoriin osoittavaa selektoria aiheutetaan "segmentti ei ole käytössä" -tyyppinen poikkeus.
Tällä käyttöjärjestelmälle huomautetaan näennäismuistiin tallennetun segmentin (tuki näennäismuistille) tai entuudestaan varaamattoman (suojauksen loukkaus) segmentin käytöstä.
S
Deskriptorin tyyppi (1 bitti)
Tämä bitti määrää, onko tämä tavallinen segmentti vai järjestelmäsegmentti.
Tämän bitin ollessa päällä tämä on joko koodi- tai datasegmentti.
Tämän bitin ollessa pois päältä tämä on järjestelmäsegmentti.
Tyyppi
Segmentin tyyppi (4 bittiä)
Deskriptoritietueen kuvatessa koodisegmenttiä tämä kenttä määrää segmentin tyypin seuraavasti: vain suoritus vai suoritus ja luku; varmistava vai varmistamaton.

Deskriptoritietueen kuvatessa datasegmenttiä tämä kenttä määrää segmentin tyypin seuraavasti: vain luku vai luku ja kirjoitus; alaspäin vai ylöspäin laajeneva.

Käytettäessä ylöspäin laajenevaa segmenttiä segmentin raja-arvon ylittävän siirroksen käyttö aiheuttaa poikkeuksen.

Alaspäin laajenevien segmenttien raja-arvoja suoritin käsittelee toisin. Siirrokset, jotka ylöspäin laajenevassa segmentissä aiheuttaisivat poikkeuksen, kelpaavat alaspäin laajenevissa segmenteissä. Alaspäin laajenevaa segmenttiä on käytettävä segmentin raja-arvoa suuremmalla siirroksella, tai muutoin aiheutuu poikkeus.

Alaspäin laajenevan segmentin raja-arvon pienentäminen johtaa siihen, että muistia varataan alkaen segmentin alarajasta. Pinot kasvavat kohti pienempää muistiosoitetta, joten niille tämä on varsin hyödyllinen.

Taulukko 1 - Deskriptoritietueen bittikenttien kuvaukset

Koska selektorit osoittavat erityiseen deskriptoritietueeseen, vallitsee selektorien ja segmenttien välillä "kullekin yksi" -suhde. Tämä käsite kuvataan seuraavassa kaaviossa.


Kuvio 4 - Selektorien ja segmenttien välinen suhde

Kuviosta 5 näkee, että lineaarinen osoite lasketaan (joka voi olla sama kuin fyysinen osoite sivutuksen ollessa poissa käytöstä) käyttäen selektoria indeksinä deskriptoritaulukkoon, hakien segmentin kantaosoite ja lisäten siihen siirros.


Kuvio 5 - Osoitteen kääntäminen virtuaalisesta lineaariseksi

Suoritin käyttää suojatussa tilassa kahdenlaista deskriptoritaulukkoa. Ensimmäinen on nimeltään GDT (engl. global descriptor table) ja käyttöjärjestelmän segmenttien deskriptoritietueet tallennetaan pääosin siihen. Toinen taulukko on nimeltään LDT (engl. local descriptor table) ja se sisältää tavallisten sovellusten segmenttien tietueet (tosin ei välttämättä). Käynnistyessään käyttöjärjestelmän ydin luo yksittäisen GDT-taulukon. Tätä taulukkoa säilötään muistissa kunnes käyttöjärjestelmä suljetaan tai suoritin palaa reaalitilaan.

Sovelluksen käynnistyessä käyttöjärjestelmä luo uuden LDT-taulukon ja tallentaa sinne uuden työtehtävän segmenttejä kuvaavat deskriptoritietueet. Näin käyttöjärjestelmä voi eristää työtehtävien osoiteavaruudet toisistaan ottamalla työtehtävänvaihdoksessa käyttöön aina eri LDT-taulukon. Sovellusvirheet ja muut häiriöt eivät pysty vaikuttamaan muihin käynnissä oleviin prosesseihin. Prosessit voivat käyttää vain kunakin hetkenä varattuna olevia muistisegmenttejä.

Kaikki käyttöjärjestelmät eivät käyttäydy juuri yllä mainitulla tavalla (esimerkiksi Windows-sovellukset saavat kukin oman LDT-taulukkonsa). Tämä on kuitenkin suositeltu ohjelmointikäytäntö, jota muiden muassa Intel kehottaa käyttämään.

Tiettyä deskriptoritietuetta hakiessaan suorittimen osoitusyksikkö valitsee TI-bitin (joka on selektorin osa) mukaan, mitä deskriptoritaulukkoa tulisi käyttää (GDT-taulukkoa vai aktiivisena olevaa LDT-taulukkoa). Kuvio 6 näyttää tämän toimenpiteen havainnollisesti.


Kuvio 6 - Käytettävän taulukon ilmaiseva TI-bitti

Itsensä GDT-taulukon lineaarinen osoite sekä koko tallennetaan suorittimen GDTR-nimiseen erityisrekisteriin. Käynnistyessään käyttöjärjestelmä asettaa tämän rekisterin osoittamaan vastikään luotuun GDT-taulukkoon eikä muuta laisinkaan sen arvoa koko istunnon aikana.

Samaan tapaan LDTR-rekisteri sisältää kulloinkin aktiivisena olevan LDT-taulukon koon ja sijainnin. Itse asiassa se on GDT-taulukon selektori ja osoittaa kaiken tarpeellisen tiedon sisältävään deskriptoritietueeseen. LDTR-rekisteri toimii GDT-taulukon selektorina eikä sisällä mitään erityisarvoja. Tämä helpottaa LDT-taulukoiden vaihdosta sekä välttää samalla työtehtävien väliset muistin turmeltumiset.

Valaisen suojatun tilan segmentointijärjestelmän toimintaa pienellä C-koodin pätkällä:

char far *pChar;

pChar = GlobalAlloc(GMEM_FIXED, 100);
pChar[50] = 'A';

Tämä koodinpätkä pyytää Windowsia varaamaan 100 tavun puskurin ja tallentamaan sen osoitteen pChar-nimiseen far- eli etäosoittimeen. Tutkimalla GlobalAlloc-funktion sisältämää koodia huomataan, että Windows varaa selektorin LDT-taulukostaan kutsuessasi sovelluksestasi tätä funktiota. Sovelluken pChar-niminen osoitin sisältää siis itse asiassa sekä selektorin että siirroksen. Muokatakseen varattua puskuria suoritin osoittaa LDTR-rekisterillä vastaavaan GDT-taulukkoon ja noutaa sillä hetkellä aktiivisena olevan LDT-deskriptorin. Sen jälkeen se hakee segmentin kantaosoitteen indeksoimalla LDT-taulukkoon pChar-osoittimen selektoriosalla. Siirros (50) lisätään noudettuun segmentin kantaosoitteeseen ja "A"-kirjain kirjoitetaan näin laskettuun muistikohtaan.

Yllä kuvattu tieto koskee vain Windowsin 16-bittistä toteutusta. Luonnostaan GlobalAlloc-funktio käyttäytyy Win32-alustalla hieman eri tavoin.

Sivutus

Sivutus on järjestelmä, jonka avulla käyttöjärjestelmä luo erityislaatuisia virtuaalisia eli näennäisiä osoiteavaruuksia. Sivutuksella on lisäksi tärkeä merkitys levytilaa käyttävässä muistin simuloinnissa eli näennäismuistin käytössä.

Toinen osoitteenmuunnosprosessi voidaan suorittaa syöttämällä segmentointiyksikön luoma, 32-bittinen lineaarinen osoite sivutusyksikköön. Lineaarisen osoitteen ja siihen liitetyn fyysisen vastineen välillä ei vallitse mitään matemaattista korrelaatiokerrointa, vaan sivutusyksikkö muuntaa annetun lineaarisen osoitteen sivutustaulujen mukaisesti suoritinväylälle lähetettäväksi, fyysiseksi osoitteeksi.

Sovellukset sijaitsevat 4 gigatavun lineaarisessa osoiteavaruudessa ja niillä ei ole mitään tietoa siitä, kuinka fyysinen muisti on jäsennelty. Näin sovellukset eivät voi nähdä saati muokata muitten sovellusten tietorakenteita. Useat moniajojärjestelmät vaativat näitä ominaisuuksia.

Sivutusyksikkö käsittelee lineaarisia ja fyysisiä osoiteavaruuksia peräkkäisinä 4 kilotavun sivusina (Pentium ja Pentium Pro osaavat käsitellä myös 4 megatavun sivuja). Lineaarinen sivu voidaan joko kartoittaa mihin tahansa fyysiseen sivuun tai sen muistinkäytöstä voidaan tehdä reaktiokykyinen merkitsemällä se käyttämättömäksi. Jos käyttämättömäksi merkittyä sivua yritetään käyttää, suoritin aiheuttaa sivuvirhepoikkeuksen (engl. Page Fault Exception, poikkeus 0xEh). Yleensä käyttöjärjestelmän sisäinen koodi käsittelee tämän poikkeuksen.

Tämä onkin näennäismuistin idea. Sovellusten käytettyä kaiken saatavilla olevan muistin käyttöjärjestelmä yrittää hankkia muistia lisää heittämällä taannoimmin käytetyt muistisivut (engl. least recently used pages, LRU) levylle. Näennäismuistiin heitetyt sivut merkitään käyttämättömiksi, jotta sovelluksen yrittäessä käyttää näitä sivuja käyttöjärjestelmä voi ladata ne levyltä takaisin muistiin (Sivutuspoikkeuksia käsittelevä koodi vastaa näiden sivujen lataamisesta ja niiden kartoittamisesta jälleen fyysiseen muistiin). On helppo kuvitella, että toimenpiteessä käyttöjärjestelmä ikään kuin "varastaa" muistia rinnalla ajettavilta työtehtäviltä, vapauttaakseen muistia aktiivisena olevalle sovellukselle.

Näennäismuistijärjestelmä on luonteensa omaisesti sovelluskoodilta täysin huomaamattomissa. Ladattuaan sivun levyltä ja kartoitettuaan sen takaisin fyysiseen muistiin käyttöjärjestelmä suorittaa uudestaan käskyn, joka aiheutti sivuvirhepoikkeuksen. Näin ajettava sovellus ei edes ole tietoinen siitä, että osa sen muistista tallennettiin tilapäisesti levylle.

Ennen sivutusyksikön käyttöönottoa käyttöjärjestelmän on laadittava muistiin sivuhakemistotaulukko (engl. page directory table). Sivuhakemistotaulukon 1024 DWORD-tietuetta sisältävät sivutaulujen fyysiset osoitteet. Sivutaulut osallistuvat osoitteenkäännösprosessin viimeiseen vaiheeseen, koska juuri ne sisältävät neljän (4) kilotavun kokoisten muistisivujen fyysiset osoitteet.

Tarkempi sivutusprosessin tutkiminen paljastaa, että suoritin pilkkoo lineaarisen osoitteen kolmeen osaan ennen sen kääntämistä fyysiseksi osoitteeksi. Suoritin indeksoi kymmenellä (10) lineaarisen osoitteen ylimmällä bitillä sivuhakemistotaulukkoon. Suoritin noutaa arvon taulusta ja hakee sen perusteella sivutaulun fyysisen osoitteen. Lineaarisen osoitteen seuraavat 10 bittiä toimivat indeksinä vastaavaan sivutauluun. Summaamalla sivutaulun kyseisen kohdan arvon (mikä kuvaa fyysisen sivun fyysistä muistiosoitetta) ja lineaarisen osoitteen 12 alinta bittiä yhteen (kyseiseen sivuun osoittava siirros) suoritin paikantaa halutun fyysisen osoitteen ja lähettää sen osoiteväylälleen. Kuviossa 7 kuvataan lineaarisen osoitteen sisäinen rakenne ja sitä vastaava suorittimen tulkinta.


Kuvio 7 - Lineaarisen osoitteen rakenneosat

Epäsuora, kaksiosainen osoitejärjestelmä (sivuhakemisto ja sivutaulu) valittiin ratkaisemaan sivutaulujen varaamaan muistiin liittyvä ongelma. Koska sivutaulut (sivuhakemistotaulukko mukaan lukien) varaavat kukin 4 kilotavua fyysistä muistia (kukin 1024 tietuetta * 4 tavua), 1024 sivutaulua (sivuhakemiston jokaiselle tietueelle oma sivutaulunsa) vaatisi 4 megatavua muistia - hirveätä kalliiden tavujen tuhlausta. Sivuhakemiston käyttö ratkaisee tämän ongelman, sillä 4 kilotavun sivutaulua edustavat tietueet voidaan merkitä käyttämättömiksi. Käyttämättömiksi merkityille sivutauluille ei tarvitse varata muistia laisinkaan.

Windows 95:ssä ajettavat 32-bittiset prosessit kartoitetaan 4 megatavun ja 2 gigatavun väliseen lineaariseen osoiteavaruuteen. Vaikka 32-bittiset sovellukset käyttävät samaa lineaarista osoiteavaruutta, Windows lataa kunkin sovelluksen erilliseen fyysisen muistin kohtaan (olettaen muistia olevan riittävästi saatavilla). Työtehtävänvaihdoksen ilmetessä Windows panee järjestelmän sivutaulut viittaamaan uuteen osoitteenkartoitusmalliin sekä heittää taannoimmin käytetyt sivut muistista levylle.

Tähän päättyy 80x86-suorittimien muistinhallintayksikön tarkastelu. Seuraavassa kappaleessa käsitellään monitahoisia suojatun tilan keskeytyksiä ja poikkeuksia.


Copyright © 1997, 1998 Yariv Kaplan
yariv(at)internals(dot)com
Suomennos: Jouni Kähkönen, käyttäjä=kajouni, palvelin=mbnet.fi

Avainsanat: suojattu tila protected mode 386 80386 x86 os operating system developing reaalitila käyttöjärjestelmäohjelmointi käyttöjärjestelmäohjelmoinnin ohjelmointi ohjelmoinnin operating system development memory management interrupts ints keskeytykset poikkeukset keskeytys poikkeus interrupts expections exceptions suoritin suorittimen suoritinta suorittimien suoritinten prosessori prosessorin prosessoria prosessoreiden sovellus sovelluksen sovellusta ohjelma ohjelman ohjelmaa application program hallinnansiirto ohjauksensiirto ohjauksen siirto control transfer