Järjestelmäpalvelut ja poikkeukset¶
Osaamistavoitteet: Laiteohjelmiston tarjoamien järjestelmäpalveluiden käyttö ja poikkeustilanteet ohjelmien suorituksessa.
Laiteohjelmisto¶
Laiteohjelmisto, PC-maailmassa BIOS (engl. Basic Input Output System) kytkee siis tietokonejärjestelmän osat toimivaksi tietokoneeksi ja tarjoaa käyttöjärjestelmälle ja sovelluksille matalan tason rajapinnan (mm. suojauksia) resurssien käyttöön. Eri BIOS:t yleensä sisältävät käyttäjille näkyvinä osina järjestelmän asennusohjelmiston ja käynnistyksen yhteydessä toteutettavat laitteistotestit. PC-maailmassa BIOS:n oleellinen tehtävä oli tarjota standardoitu integrointiympäristö eri valmistajien eri komponenteille, joka oli oleellinen tekijä alunperin IBM:n kehittämän PC-teknologian voittokululle kotitietokoneiden alkuaikoina.
BIOSn palveluita kutsutaan kahden eri mekanismin kautta: ohjelmallisilla keskeytyksillä konekielestä (engl. trap instructions) tai järjestelmäkutsuilla (systeemikutsu, engl. trap / system call) ohjelmakoodista. Tällöin järjestelmäkutsua käytetään ohjelmassa kuten funktiokutsua,. Usein tarjotaan järjestelmäkutsulle korkeamman tason käsittelijä/kääre (engl. wrapper), joka kutsuu varsinaista BIOS:n järjestelmäkutsua. Esimerkiksi C-kielessä standardikirjasto (unistd.h) tarjoaa järjestelmäkutsut ohjelmien käyttöön. Koska ne ovat alemman tason funktiota, ne vaativat enemmän yksityiskohtia ohjelmoijalta.
Tällainen kerrostus tarvitaan siksi, että järjestelmäkutsulla on laajemmat oikeudet järjestelmän resursseihin (engl. kernel mode), kuin sitä kutsuvalla ohjelmalla (engl. user mode). Esimerkiksi x86-suorittimien arkkitehtuurissa on erilaisia suojaustasoja. Lisäksi joissain prosessoreissa on suojattua käskyjä, joita vain järjestelmäkutsut voivat käyttää. Ennen kutsun suorittamista oikeudet tietenkin tarkistetaan.
Itse laiteohjelmisto on usein lukumuistissa (ROM-muisti, engl. read only memory) oleva pätkä konekielistä koodia, jonne ohjelmakoodista (konekielestä) hypätään hyppykäskyillä kuten aliohjelmiin yleensäkin. Varhaisissa tietokoneissa firmis olikin toteutettu prosessorin kanssa samalle piirikortille integroidulle erilliselle ROM-piirille osaksi ohjelmamuistia.
Palataan järjestelmäkutsujen suoritukseen ihan just..
Poikkeukset¶
Yleisesti 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. 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. Suorittimen sisäisesti tapahtuma muuttaa sen tilaa, esim. tilabittejä tai rekisterien arvoja, joita lukemalla nähdään mikä poikkeus tapahtui.
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 ohjelmaan voidaan myös valikoidusti laatia omat käsittelijät. Suoritinarkkitehtuureissa on myös ohjelmallisesti asetettavia poikkeuksia, jotka on ohjelmoijan vapaassa käytössä.
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) järjestysnumeron avulla, joiden järjestys määritellään suorittimen hardiksen suunnitteluvaiheessa tai BIOS:ssa käynnistysvaiheessa. Kun poikkeuksten numerokoodit on tunnettu ja standardoitu, mahdollistetaan erilaisten oheislaitteiden joustava liittäminen tietokonejärjestelmään sekä niiden yhdenmukainen käyttö ohjelmakoodista.
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. Näin ollen poikkeuksen käsittely on (ehdoton) hyppykäsky muistiosoitteeseen, josta käsittelijän koodi suoritetaan. Joissain arkkitehtuureissa (esim. moderni x86) käsittelijälle voidaan antaa vielä kutsuparametri ihan kuten aliohjelmakutsuissakin.
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 funktio, mutta konepellin alla eroja löytyy.
- Poikkeustilanteessa tallennetaan ohjelman tilasta yleisrekisterien lisäksi tilarekisteri. 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 oma pino, eikä ohjelman pino.
- Joissain prosessoreissa on oma konekielen paluukäsky käsittelijärutiineista (engl. exception return) ja oma paluukäsky aliohjelmasta, jotta keskeytetystä suorittimen tilasta saataisiin palautettua kaikki oleellinen. x86:sessa nämä käskyt ovat
rti
(return from interrupt) jarts
(return from subroutine). - 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 ja ohjelman suorituksesta riippumaton) signaali suorittimen sisältä tai ulkoisesti 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ä tilarekisterissä (erillisessä keskeytysrekisterissä), suoritetaan sitä vastaava käsittelijä. Käsittelijän suorituksen jälkeen palataan ohjelmaan ja suoritetaan sen seuraava käsky, jos se on mahdollista.
Laitteistokeskeytyksistä käytetään kahta nimitystä: IRQ (engl. Interrupt Request) ja NMI (engl. Non-maskable Interrupt). Näistä IRQ:t ovat oheislaitteille varattuja keskeytyslinjoja ohjauspiirissä joka taas on kiinni suorittimen keskeystyslinjoissa. NMI:t varataan keskusyksikön sisäisen toiminnin valvontaan, esimerkiksi nollalla jakaminen tai muistivirhe, ja niillä on korkeampi prioriteetti. Kun laitteistokeskeytys tulee, ihan vastaavasti hypätään sen käsittelijään laiteohjelmistossa.
Keskeytyslinjojen lukumäärä vaihtelee, esimerkiksi x86-perheessä niitä on 16 kpl jaettuna kahteen ohjainpiiriin. Näinollen PC-maailmassa BIOS määritti mikä keskeytyslinja varataan millekin oheislaitteelle.
Kuva. Vanhan PC:n laitteistokeskeytyslinjat
Kuva. Vanhan PC:n laitteistokeskeytyslinjat
Ohjelmallisia keskeytyksiä varten, jotka itseasiassa ovat usein järjestelmäkutsuja (kts. alla), konekielen käskykannassa on oma käsky kuten alla esimerkissä
int
. x86:sessa tämä keskeytyskutsu int 21
pyytää laiteohjelmiston kutsulla 0x2a
laiteohjelmiston palvelua antamaan päivämäärän reaaliaikakello-komponentilta. mov ah,2a
int 21
Järjestelmäkutsu¶
Järjestelmäkutsut ovat siis synkronisia, koska ne aiheutetaan halutussa kohti ohjelmaa. Esimerkkejä järjestelmäkutsuista ovat eriliset I/O-toiminnot, kuten tiedoston lukeminen, tai moniajoon liittyvä toiminallisuus kuten toisen prosessin / ohjelman käynnistäminen tai nykyinen ohjelman ajon lopettaminen.
Järjestelmäkutsua varten voi olla valmiita konekielen käskyjä (engl. trap instructions) tai sitä käytetään kuten funktiokutsua. 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. write-funktion ensimmäinen parametri
1
tarkoittaa tässä tulostusvuota (engl. stdout), ts. yleensä ruutua. int main() {
write(1,"hello world\n",13); // printf("hello world\n");
_exit(0); // return 0;
}
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.Fault¶
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 keskusmuistista (tai ensin alemman tason välimuistista) ja tallentaa sen välimuistiin.
Abort¶
Virheellinen lopetus (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.
Liukuhihna-suorittimessa poikkeusten käsittely onkin astetta monimutkaisempaa:
- Usea käsky eri vaiheissa aiheuttaa poikkeuksen yhtaikaa (samalla kellojaksolla). Tällöin pitää selvittää missä järjestyksessä poikkeukdet käsitellään. y86:ssa ensin käsitellään pisimmälle liukuhihnalla 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 jopa operaatiokoodi) on väärä.
main: xorq %rax,%rax jne target irmovq $1,%rax target: movl $13,%rbx # väärä operaatiokoodi (tässä x86-käsky)
- 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. - Nyt liukuhihnaprosessorissa Write back-vaiheessa tarkistetaan tilabitit ja jos poikkeus on tapahtunut, W-liukuhihnarekisterin arvot voidaan tallettaa prosessorin tilana.
Poikkeuksen tapahduttua ohjelman suoritus keskeytetään 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 aiemmin oppineet, sulautetuissa järjestelmissä poikkeukset/tapahtumat/keskeytykset ovat hyvä tapa kommunikoida oheislaitteiden kanssa asynkronisuuden takia. SensorTagin RTOS:n toiminta on tästä syystä (suurelta osin) rakennettu poikkeuspohjaiseksi.
Anna palautetta
Kommentteja materiaalista?