Poikkeukset¶
Osaamistavoitteet': Millaisia poikkeustilanteita ohjelmien suorituksessa voi tulla ja miten ohjelma käyttää järjestelmäpalveluita
Poikkeus on tapahtuma (engl. event), joka tulee suoritettavan ohjelman ulkopuolelta, keskeyttää ohjelman kontrollivuon (engl. control flow) ja johon prosessorin on reagoitava suorittamalla poikkeuksen käsittelijän (engl. exception handler). Jonka jälkeen prosessori palaa suorittamaan keskeytettyä ohjelmaa siitä mihin se jäi.
Suorittimen sisäisesti jokin tapahtuma (engl. event) muuttaa sen tilaa, esim. tilabittejä tai rekisterien arvoja. Tapahtuma voi liittyä suoritettavana olevaan käskyyn, esimerkiksi jos yritetään jakaa nollalla. Myös ulkoiset tapahtumat voivat keskeyttää ohjelman suorituksen, esimerkiksi viesti I/O-laitteelta.
Poikkeuksen käsittely on siis laitteiston ja ohjelmiston yhteistyötä. Laitteisto asettaa tilamuutoksen ja ohjelmisto reagoi siihen. Yleensä käyttöjärjestelmissä on valmiit oletuskäsittelijät poikkeuksille, mutta ohjelmoija voi myös laatia omat käsittelijät. Suoritinarkkitehtuureissa on myös ohjelmallisesti asetettavia poikkeuksia, jotka on ohjelmoijan vapaassa käytössä. Ero ajettavaan ohjelmaan tässä on, että poikkeuksia eivät ole ohjelmoijan asettamat tilamuutokset ohjelmassaan, esimerkiksi ohjelman toimintaa kontrolloivat tilamuuttujat.
Lisäksi, poikkeukset tarjoavat keinon sovellusten ja käyttöjärjestelmän väliseen yhteistoimintaan. Sovellukset voivat pyytää alemman tason palveluita, kuten I/O:ta, käyttöjärjestelmältä poikkeuksien avulla. Vastaavasti oheislaitteet viestivät sovelluksen kanssa poikkeusten avulla. Moniajoa tukevasti ohjelman eri tehtävien yhtäaikainen suoritus voidaan toteuttaa ja kontrolloida poikkeusten avulla. Esimerkkinä TI RTOS:n kirjastot Task, Pin, UART, jne..
Poikkeusten käsittely¶
Poikkeukset identifioidaan niille annetun (0:sta alkavan positiivisen) numeron avulla, joiden järjestys määritellään suorittimen suunnitteluvaiheessa. Järjestys kuvaa poikkeuksien prioriteettijärjestyksen, jossa pienin numero on korkein prioriteetti. Numeroiden perusteella saadaan poikkeustaulukko (engl. exception table), johon tallennetaan jokaiselle poikkeukselle sen käsittelijän muistiosoite. Usein poikkeustaulukko itse sijaitsee muistin aivan ensimmäisissä osoitteissa, eli alkaen muistipaikasta 0! Sulautetuissa järjestelmissä puhutaan keskeytysvektorista (engl. interrupt vector table).
Esimerkkinä x86:sen keskeytystaulukko.
Kun tietokone käynnistetään, laiteohjelmisto jatai käyttöjärjestelmä alustaa poikkeustaulukon oletusarvoihin. Käyttöjärjestelmä voi myös luoda omia poikkeuksiaan poikkeustaulukon loppuun. Sovellukset voivat muutella arvoja, ts. muistiosoitteita, oikeuksiensa rajoissa. Sulautetuissa järjestelmissä esimerkiksi ohjelmoijalla on yleensä vapaus muutella keskeytysvektorin arvoja, käsittelijöitä toteuttaessa.
Jokaisella poikkeuksella on oltava käsittelijä (engl. handler), joka on joko käyttöjärjestelmän oletuskäsittelijä tai ohjelmoijan laatima. Kuten keskeytyksen yhteydessä aiemmin esitettin, käsittelijä itsessään on kuten ohjelman funktio, mutta konepellin alla eroja löytyy.
- Poikkeustilanteessa tallennetaan ohjelman tilasta rekisterien lisäksi muitakin parametrejä, kuten tilabitit. Jossain suorittimissa on omia rekistereitä poikkeustilanteiden tallennukseen, esimerkiksi poikkeuksen aiheuttaneen käskyn osoite.
- Jos käytetään pinoa, se on käyttöjärjestelmän omaan pino, eikä ohjelman pino.
- Joissain prosessoreissa on oma konekielen paluukäsky käsittelijärutiineista (engl. exception return), jotta keskeytetystä suorittimen tilasta saataisiin palautettua kaikki oleellinen.
- Käsittelijä voi lopettaa ohjelman suorituksen, jolloin sieltä ei koskaan palata.
- Paluuosoite ei aina ole seuraava ohjelman käsky, vaan voidaan myös uudelleensuorittaa nykyinen käsky
- Poikkeuskäsittelijöillä, ainakin käyttöjärjestelmän omilla, on laajemmat, jos ei täydet, oikeudet järjestelmän resursseihin kuin käyttäjien ohjelmilla.
Poikkeustyypit¶
Olemme jo aiemmassa materiaalissa tutustuneet yhteen poikkeustyyppiin, keskeytyksiin. Mutta itseasiassa poikkeustyyppejä on muitakin. Materiaalissa alla käytämme näille varsin kömpelöitä suomenkielisiä nimityksiä..
Keskeytys¶
Keskeytyksen siis aiheuttaa asynkroninen (tässä ajasta riippumaton) signaali suorittimen sisältä tai oheislaitteelta. Suoritin tarkistaa keskeytyslippujen tilat aina käskyn suorituksen jälkeen. Keskeytys ei siis keskeytä konekielisen käskyn suoritusta prosessorissa.
Kun lippu on ylhäällä, esimerkiksi keskeytysrekisterissä, suoritetaan sitä vastaava käsittelijä. Käsittelijän suorituksen jälkeen palataan ohjelmaan ja suoritetaan sen seuraava käsky.
Järjestelmäkutsu¶
Poikkeuksia voidaan myös aiheuttaa ohjelmallisesti, tällöin puhutaan järjestelmäkutsuista (systeemikutsu, engl. trap / system call). Verrattuna keskeytyksiin nämä poikkeukset ovat synkronisia, koska ne aiheutetaan halutussa kohti ohjelmaa. Tätä mekanismiä käytetään usein käyttöjärjestelmän matalan tason palveluiden kutsumiseen ohjelmasta, kuten I/O tai moniajo. Esimerkkejä ovat tiedoston lukeminen, toisen prosessin / ohjelman käynnistäminen tai nykyinen ohjelman ajon lopettaminen.
Ohjelmoijan kannalta järjestelmäkutsua varten voi olla valmiita konekielen käskyjä (engl. trap instructions) tai sitä käytetään kuten funktiokutsua, joka itseasiassa on järjestelmäkutsun käsittelijä (engl. wrapper), joka kutsuu varsinaista kutsua. Tällainen kerrostus tarvitaan, mm. siksi että järjestelmäkutsulla on laajemmat oikeudet järjestelmän resursseihin (engl. kernel mode), kuin sitä kutsuvalla ohjelmalla (engl. user mode). Ennen kutsun suorittamista oikeudet tietenkin tarkistetaan. Lisäksi joissain prosessoreissa on suojattua käskyjä, joita vain järjestelmäkutsut voivat käyttää.
Esimerkki C-kielisestä ohjelmasta (linux/x86), joka käyttää printf:n sijaan järjestelmäkutsuja.
int main() {
write(1,"hello world\n",13);
_exit(0);
}
write
-funktion ensimmäinen parametri 1
tarkoittaa tässä tulostusvuota (engl. stdout). Huomataan, että C-kielen standardikirjasto (unistd.h) tarjoaa järjestelmäkutsut ohjelman käyttöön. Koska ne ovat alemman tason funktiota, ne vaativat enemmän yksityiskohtia ohjelmoijalta.
Vastaava x86-assembly-koodi olisi seuraava:
string:
.ascii "hello world\n"
main:
movq $1,%rax # write on kutsu nro 1
movq $1, %rdi # tulostusvuo näytölle on 1
movq $string, %rsi # merkkijono
movq $13, %rdx # merkkijonon pituus
syscall # suorita write
exit:
movq $60,%rax # exit on kutsu nro 60
movq $0, %rdi # palautusarvo 0
syscall
Assembly-kielessä kaikki järjestelmäkutsujen parametrit annetaan rekistereissä ja pinoa ei käytetä. Parametreilla on myös tietty järjestys, jossa
%rax
-rekisteriin menee kutsun numero, jne. Palautusarvo tulee myös %rax-rekisteriin.Vika¶
Vika (engl. fault) on sellainen virhetilanne, jonka mahdollisesti käsittelijä pystyy korjaamaan. Käsittelijän suorituksen jälkeen nykyinen ohjelmakäsky suoritetaan uudelleen. Jos käsittelijä ei pysty korjaamaan virhetilannetta, ohjelman suoritus lopetetaan virheeseen.
Esimerkki on vaikkapa viittaus muistiosoitteeseen, jota ei ole valmiina prosessorin välimuistissa (engl. page fault). Tällöin käsittelijä noutaa kyseisen muistiosoitteen sisällön käyttömuistista (tai alemman tason välimuistista) ja tallentaa sen välimuistiin.
Virhelopetus¶
Virhelopetus (huoh.. engl. abort) tapahtuu kun prosessorissa ilmeni virhetilanne jota ei voida ohjelmassa / käyttöjärjestelmässä korjata. Esimerkkinä tästä voisi olla viallinen data (pariteettivirhe) muistipiirissä tai oheislaitteen vika. Tällöin ohjelman suoritus loppuu tähän kutsuun, eikä enää palata takaisin pääohjelmaan.
y86 poikkeukset¶
y86-arkkitehtuurissa on kolme sisäistä poikkeusta
- Konekielen käsky
halt
- Väärä operaatiokoodi
INS
- Väärä muistiosoite
ADR
Mutta, poikkeuskäsittelijöitä ei ole y86-simulaattorissa toteutettu.
Sekventiaalisessa y86:sessa ohjelman suoritus keskeytyy ja vastaava tilabitti asetetaan. Tämän jälkeisillä käskyillä ei ole vaikutusta prosessorin tilaan.
Esimerkkinä liukuhihna-toteutuksessa poikkeusten käsittely onkin astetta monimutkaisempaa:
- Usea käsky eri vaiheissa aiheuttaa poikkeuksen yhtaikaa (samalla kellojaksolla). Tällöin pitää selvittää mikä poikkeus raportoidaan käyttöjärjestelmälle. y86:ssa raportoidaan pisimmälle liukuhihnalle ehtinyt käsky.
- Ehdollisen hypyn tuloksena muistista haettu käsky aiheuttaa poikkeuksen ja käy ilmi että ennustus oli väärä. Tämä tilanne voi syntyä esimerkiksi jos muistiosoite tai operaatiokoodi on väärä.
main:
xorq %rax,%rax
jne target
irmovq $1,%rax
target:
.byte 0xff # väärä operaatiokoodi
- Liukuhihnakäskyt muuttavat yhteistä resurssia virheelliseen tilaan (esim. pino-osoitin) ja tästä aiheutuu poikkeus. Tämä tilanne on mahdollinen jos käsky on Memory-tilassa ja toinen käsky on Execute-tilassa ja päivittää tilabitit. Eli myöhempi käsky ei tarkista tilabittejä ennen suoritustaan. Tämän vuoksi
STAT
-rekisterin pitäisi kulkea liukuhihnarekisterien mukana.
Tämän vuoksi liukuhihnaprosessorissa Write back-vaiheessa tarkistetaan tilabitit ja jos poikkeus on tapahtunut, W-liukuhihnarekisterin arvot voidaan tallettaa prosessorin tilana.
Ohjelman suoritus keskeytyy ja tätä seuraavat käskyt eivät enää muuta prosessorin tilaa.
Lopuksi¶
Poikkeuksien hallinta on nykyisissä prosessoreissa varsin monimutkainen asia, kuten huomasimme y86-liukuhihnaprosessorin tapauksesta..
Kuten olemme oppineet, sulautetuissa järjestelmissä keskeytykset ovat hyvä tapa kommunikoida oheislaitteiden kanssa niiden asynkronisuuden takia.
Anna palautetta
Kommentteja materiaalista?