Ytimet 121 7. Ytimet Sulautettujen järjestelmien toteuttaminen erilaisten käyttöjärjestelmän ytimien avulla lienee nykyisin tavanomaisin toteutustekniikka. Seuraavassa esittelemme joitakin käyttöjärjestelmän ytimen toteutukseen ja valintaan liittyviä seikkoja. 7.1 Ytimen roolista Pienten reaaliaikajärjestelmien ytimet toteutetaan usein täysin ilman käyttöjärjestelmän apua. Tällöin sovelluksen toiminnat ja tavallisesti käyttöjärjestelmätasolla toteuttavat tehtävät sekoittuvat toisiinsa, ja ohjelmoijan on huolehdittava itse kaikesta laitteiston ohjauksesta kuten keskeytysten käsittelystä, prosessien hallinnasta (jos niitä edes on) ja muistin hallinnasta. Toisaalta tämä lähestymistapa myös tarjoaa ohjelmoijalle kaikkein parhaat mahdollisuudet hallita järjestelmän resursseja, sillä mukana on harvoin ohjelmakoodia, joka ei olisi ohjelmoijan muokattavissa osana projektia. Yleiskäyttöisten käyttöjärjestelmien puolella käytetään termiä prosessi tarkoittamaan yhtä muistinhallinnan kokonaisuutta, jonka sisällä voi olla prosessia kevyempiä prosessimaisia olioita, säikeitä. Tämä tekniikka siis saman muistiavaruuden käyttö useammalle prosessille tai oikeastaan säikeelle on melko tyypillinen pienen sulautetun järjestelmän toteutusratkaisu. Kyse ei kuitenkaan ole yleensä ohjelmistoteknisestä suunnitteluratkaisusta, vaan yksinkertaisesti siitä, että laitteiston tarjoamat mahdollisuudet ovat siinä määrin rajallisia, että muut ratkaisut ovat käytännössä mahdottomia erilaisista syistä, esimerkiksi siitä, että laitteisto ei tue prosessirajoihin liittyvää muistinhallintaa lainkaan. Yleiskäyttöisen käyttöjärjestelmän ytimen päätehtävät ovat seuraavat:
122 Sulautettu ohjelmointi keskeytysten käsittely: mahdollinen ympäristön talletus ja oikean keskeytyskäsittelijän valinta, prosessien hallinta: luominen, lopetus, skedulointi, ajastus ja prosessien keskinäinen kommunikointi, ja oheislaitteiden ohjaus: siirrännän käynnistäminen ja keskeytyksen käsittely. Nämä kaikki on siis tarvittaessa oltava valmis toteuttamaan itse, tai vaihtoehtoisesti järjestelmää on pyrittävä yksinkertaistamaan siinä määrin, että osa toiminnoista muuttuu tarpeettomaksi. Kun ollaan toteuttamassa sulautettuihin järjestelmiin tarkoitettua ydintä, pitää huomioida, että tavoitteena on yleensä rakentaa järjestelmä, jonka toiminnan varmentaminen on olennaista. Tästä syystä suunnittelussa kannattaa pyrkiä suoraviivaisuuteen ja yksinkertaisuuteen, mikä puolestaan saattaa yksinkertaistaa toteutusta joidenkin ominaisuuksien osalta jopa siinä määrin, että osa ominaisuuksista jätetään kokonaan toteuttamatta. Joskus on tehtävä myös erilaisia kompromisseja ennustettavuuden ja joustavuuden välillä. Esimerkiksi voidaan pyrkiä yhdistämään pienytimen reaaliaikaan ja sen skeduloitavuuteen kiinteästi liittyvät ominaisuudet sekä yleiskäyttöisen käyttöjärjestelmän tarjoama dynaamisuus käyttämällä kahta ydintä. Näistä yksinkertaisempi, reaaliaikaominaisuudet takaava ydin, vastaa siitä, että ne tehtävät, joihin liittyy kovia reaaliaikavaatimuksia, tulevat ajallaan suoritettua. Yleiskäyttöinen käyttöjärjestelmä puolestaan suorittaa niitä tehtäviä, joiden suoritus voi olla dynaamista ja jossa ohjelmistosuunnittelulla on enemmän vapausasteita. Koska eri ytimet joutuvat kommunikoimaan, on myös tähän löydettävä sopiva ratkaisu. Reaaliaikaytimen kannalta yleiskäyttöinen käyttöjärjestelmä voi näyttäytyä yhtenä prosessina, esimerkiksi vaikkapa järjestelmän taustaprosessina, jota suoritetaan, kun mitään varsinaista reaaliaikaista tehtävää ei ole suoritettavana. Yleiskäyttöinen käyttöjärjestelmä saattaa puolestaan pitää reaaliaikakäyttöjärjestelmää laiteajurina, joka kuluttaa kohtuuttoman paljon laskentatehoa keskeytyksiensä käsittelyyn. 7.2 Perusydintyypit Pienytimillä on kolme eri päätyyppiä: pollaava (silmukka), keskeytysohjattu, ja
Ytimet 123 prosessiydin. Valittava ratkaisu riippuu suorittimesta ja toteutettavan ohjelmiston koosta ja monimutkaisuudesta. Silmukka- ja keskeytysydin ovat yleensä mahdollisia kaikilla suorittimilla, mutta niihin liittyy kiinteästi ohjelmistosuunnittelua tarpeettomasti monimutkaistavia ja rajoittavia tekijöitä. Prosessiydin ei ole mahdollinen kaikissa tapauksissa, sillä aivan pienimmillä suorittimilla ympäristön talletusta ei välttämättä voi tehdä. Syy voi olla esimerkiksi se, että pino-osoittimen arvoa ei voi muuttaa. Sen sijaan prosessiydin tarjoaa yleensä enemmän liikkumavaraa ohjelmistoa suunniteltaessa kuin mitä pollaava ja keskeytysohjattu ydin. 7.2.1 Pollaavat ytimet Pollaava ydin on yksinkertaisin ydin, joissa tavallisesti ei ole alustuksen lisäksi keskeytyspalveluja lainkaan, tai jos on niin vain yksi keskeytyslähde, esimerkiksi kello, on käytettävissä. Ytimen rakenne on silmukka, joka testaa oheispiirien tilarekistereitä. Mikäli tila on muuttunut, tehdään tilamuutoksessa tarvittava toimenpide. Pollaava ydin on yksinkertainen, kyseessähän on itse asiassa vain ikuinen silmukka. Rakenne ja sen toiminnot ovat yksinkertaisuudessaan seuraavat: int main () { alusta laitteet; while (1) { testaa ja käsittele laite 1; testaa ja käsittele laite 2; testaa ja käsittele laite n; Kun alustus ohjataan aliohjelmaan, joka alustaa pino-osoittimen, voidaan tästä aliohjelmasta kutsua yllä esitetty pääohjelmaa main, jolloin koko ydin onkin sitten jo toteutettu. Niinpä pollaavan ytimen hyviin puoliin kuuluu kiistatta se, että se on yksinkertainen ja nopea tehdä. Rakenne sopii pieniin sovelluksiin, ja keskeytysten puutteen takia siinä ei voi esiintyä poissulkemisongelmaa. Täydellinen tämä ratkaisu ei ole. Ongelmia tuottaa mm se, että kaikki toiminnot on ehdittävä tehdä silmukan kierroksen aikana, mikä rajoittaa ohjelmien kokoa. Tämä vaatimus aiheuttaa myös sen, että järjestelmä on mitoitettava pahimman mahdollisen tilanteen mukaan,
124 Sulautettu ohjelmointi void palvele() { static int tilamuuttuja = 0; switch (tilamuuttuja) { case 0: if (laitteella töitä) { aloita työt tilamuuttuja = 1; break; case 1: jatka käsittelyä; tilamuuttuja = 2; break; case 2: tee työt loppuun; tilamuuttuja = 0; break; default: virhe; Kuva 7.1 Paloissa tapahtuva laskenta. Huomaa määre static, joka tarkoittaa sitä, että muuttujaa ei alusteta kuin kerran ja että sen arvo ei muutu kutsukertojen välillä. eli sen, että jokainen oheislaite tarvitsee palvelua saman kierroksen aikana. Tämä johtaa siihen, että tavallisella kierroksella on luppoaikaa runsaasti. Mikäli oheislaitteen palvelu kestää kauan, voidaan se jakaa osiin. Eri ytimen kierroksilla voidaan laskennan eteneminen tunnistaa tilamuuttujasta. Tämä ratkaisu edellyttää tietenkin sitä, että sama laite ei kaipaa palvelua kovin usein muuten sen palvelemiseen ei voida käyttää monta kierrosta. Ohjelmassa 7.1 on hahmoteltu yksinkertainen, kolmessa eri palassa suoritettavan palvelun rakenne. Ratkaisun ydin on luoda tilakone (kuvassa muuttuja tilamuuttuja), jonka tilasiirtymiä suoritettavat toiminnot ovat. Tilakone ilmaisee tilan, johon asti suoritus on edennyt, minkä perusteella voidaan jatkaa toimintaa oikeasta kohdasta. Tässä tavassa on olennaista huomata, että laitteen käsittely, siis kaikki laitteistotason ohjaukset, ja tiedon käsittely tehdään yhtä aikaa. Mikäli kumpikaan ei tarvitse muita laitteita, rakenne on mahdollista säilyttää suhteellisen selkeänä. Ongelmia kuitenkin syntyy heti, kun laskennan tulos pitää toimittaa toiselle laitteelle, sillä kohdelaitteen toimintoja ohjaa sen oma ohjausohjelma. Koska järjestelmässä ei ole viestinvälitystä, tarkoittaa tämä käytännössä sitä, että tiedon tuottanut
Ytimet 125 ohjelma kirjoittaa tulostiedot suoraan kohdelaitteen ohjausohjelman muistiin. Koska järjestelmä on niin yksinkertainen, että keskeytyksiä ei ole, tämä ei onneksi johda poissulkemisongelmiin, mutta sen sijaan virheiden jäljittäminen tai ohjelmalogiikan seuraaminen voivat osoittautua monimutkaisiksi. Esimerkiksi tämäntyyppisestä laitteesta voisi käydä lämpömittari, joka mittaa sekä sisä- että ulkoilman lämpötilaa. Toiminta voisi tällöin olla seuraavanlainen: alusta laitteet; while (1) { lue sisälämpötila ja laita se muistiin; lue ulkolämpötila ja laita se muistiin; näytä lämpötilat jos min-näppäin alhaalla, näytä minimit, jos max-näppäin alhaalla, näytä maksimit muuten tuorein arvo; lue nollauspainike ja nollaa minimi- ja maksitiedot, jos painettu; 7.2.2 Keskeytysohjatut ytimet Keskeytysohjatun ytimen rakenne on ikuinen odotussilmukka, jonka keskeytyspalvelut keskeyttävät ajoittain. Ikuisen odotuksen tilalla voi olla myös jotain hyödyllistä tehtävää ja usein onkin, joten tätä rakennetta kutsutaan myös taustaprosessiytimeksi, vaikka varsinaista prosessikäsitettä ei järjestelmässä olekaan. Järjestelmän rakenne on nyt seuraavaa muistuttava int main () { alusta laitteet; while (1) { taustatoiminnon tekemistä; keskeytys 1 () { laitteen palvelu; tiedon käsittely; keskeytys n () { laitteen palvelu; tiedon käsittely;
126 Sulautettu ohjelmointi Nyt keskeytykset tulee sitoa vastaaviin keskeytyspalveluihin. Tämä tapahtuu aiemmin esitetyllä tavalla joko laitteistolla tai ohjelmistolla. Pääohjelma sidotaan edelleen reset-toimintoon. Kuten pollaavakin ydin, on myös keskeytysohjattu ydin nopea ja helppo kirjoittaa tarvittaessa, sillä tämäkään järjestelmä ei ole kovin monimutkainen. Tässäkään ytimessä ei esiinny poissulkemisongelmaa laitteita käsittelevien ohjelmien kesken, mikäli keskeytyspalvelut aina kieltävät keskeytykset. Taustatoiminnon ja keskeytysten välille voi syntyä kriittinen alue saman muuttujan käsittelystä. Poissulkemisen voi hoitaa kieltämällä hetkeksi keskeytykset taustatoiminnon suorittaessa kriittistä aluetta. Tätä ei pidä tehdä pitkäksi aikaa kerrallaan, sillä muuten koko järjestelmän toimivuus on uhattuna. Mikäli eri prioriteettiset keskeytykset ovat mahdollisia, tulee ohjelmoinnissa huomioida, että suuriprioriteettinen keskeytys pystyy keskeyttämään matalaprioriteettiset keskeytykset. Tällöin mahdollinen poissulkeminen on otettava huomioon myös keskeytysten välillä. Tämä voi osoittautua vaikeaksi käytännössä esimerkiksi käänteisprioriteettiongelman vuoksi. Keskeytyspalveluissa ei voi käyttää kuvassa 7.1 esitettyä ohjelmaa, koska palvelurutiinia ei kutsuta kuin keskeytyksen tullessa. Sen sijaan taustaprosessissa voidaan käyttää vastaavaa tekniikkaa, jossa tilamuuttujilla ohjataan toimintoja. Tosin tilamuutokset eivät aina johdu laskennan etenemisestä kuten kuvassa 7.1, vaan keskeytyspalvelut voivat muuttaa suoraan tilamuuttujia, ja vaikuttaa näin taustatoiminnon käyttäytymiseen. Vastaavasti taustatoiminto voi käsitellä suoraan laiterekistereitä ja käynnistää tiedonsiirto-operaatioita. Tästä kaikesta seuraa usein se, että sovelluksen logiikka on hajautettuna keskeytysten ja taustaprosessin välillä. Tämä rikkoo ohjelman sisäisen rakenteen, ja kokonaisuus voi pahimmillaan olla hyvin sekava. Edellisen kohdan lämpömittariesimerkkiin sopii hyvin pollaava ydin, koska lämpötilaa mittaava laite (A/D-muunnin) ei tavallisesti ole keskeyttävä. Näytölle kirjoitus ei myöskään ole tavallisesti keskeyttävä, joten lämpömittaria ei kannattane esittää keskeytysohjatulla ytimellä. Keskeytysohjattuna järjestelmänä toimii tyypillisesti jokin ulkoinen oheislaite, vaikkapa kirjoitin. Kirjoittimille tulee viestejä sarjatai rinnakkaisliityntälinjaa pitkin, ja jokainen merkki aiheuttaa keskeytyksen. Merkki siirretään palveltavien merkkien jonoon, ja taustaprosessi käsittelee merkit merkkiähän ei aina tulosteta, vaan se voi olla osa ohjauskoodia. Kun taustaprosessi on saanut tulkattua koko rivin, se aloittaa rivin kirjoittamisen. Rivin kirjoittamisen aikana voidaan kellokeskeytyksillä huolehtia siitä, että kirjoittaminen edistyy oikeaa
Ytimet 127 int main() { alusta laitteet(); while (1) { if (uusia merkkejä) { tulkitse niin pitkälle kuin mahdollista; if (kirjoitettava rivi valmis) { aloita rivin kirjoitus sallimalla kellokeskeytykset; void sarjaliitynta() { siirrä merkki käsiteltävien puskuriin; void kellokeskeytys() { kirjoita seuraava merkki; siirrä kirjoituspäätä (mahdollisesti vaihda riviä); Kuva 7.2 Keskeytysohjattu yksinkertainen ohjelma. vauhtia, eli merkin kirjoittamisen jälkeen odotetaan hetki kirjoituspään siirtymistä. Samalla luetaan jo seuraavaa riviä koneelle, ja tulkataan sitä. Pseudokoodina ohjelma näyttäisi siis kuvan 7.2 mukaiselta. Kuvan esimerkki on esitetty todella pelkistetyssä muodossa, mutta antaa hahmon siitä, miten järjestelmä voisi toimia. Yksityiskohtia, joita koodista ei näe on useita: Koska kuvattu laite tulkitsee rivin kerrallaan, se voi kirjoittaa joka toisen rivin vasemmalta oikealle ja joka toisen oikealta vasemmalle. Myöskään kaikki liikkeet eivät välttämättä ole yhtä nopeita, vaan niiden vaatima aika voi vaihdella melkoisesti (rivinvaihto, merkki eteenpäin, telanpalautus). 7.2.3 Prosessiytimet Prosessiydin on yksinkertaisenakin edellä esitettyjä ytimiä selvästi monimutkaisempi. Itse sovellus on toteutettu prosesseissa, ja keskeytyspalvelut (laiteajurit) huolehtivat tiedonsiirrosta fyysisen laitteen kanssa. Prosessiydin soveltuu melko pieniinkin ongelmiin, joskin aivan pienimmissä se on turhan raskas. Ylärajaa sovelluksen koolle ei ole, sillä kaikki yleiskäyttöiset käyttöjärjestelmät ovat prosessiytimiä. Edellisiin ytimiin verrattuna suurin rakenteellinen ero on laitteiden ohjauksen ja sovelluksen logiikan erottaminen toisistaan. Tämä
128 Sulautettu ohjelmointi selkeyttää ohjelmaa ja mahdollistaa laitteita ohjaavien ajureiden käytön muissakin sovelluksissa. Prosessikäsite antaa siten mahdollisuuden käsitellä tietoa hyvinkin monipuolisilla algoritmeilla, jolloin käyttöjärjestelmän tietorakenteet toimivat laitteilta tulevien tietojen puskureina. Näin laite voidaan mitoittaa keskimääräisen kuorman mukaan huippukuorman asemesta. Lisäksi loogisia toimintoja voi olla ajossa useita toisistaan (lähes) täysin riippumatta; vain niiden keskinäisen prioriteetin avulla päätetään, mille niistä annetaan ensisijaisesti aikaa. Erilaiset poissulkemistilanteet kuitenkin saattavat mutkistaa suoritusta. Kaikesta yllä olevasta hyvästä saadaan myös maksaa: Ydin on monimutkaisempi kuin muut vaihtoehdot ja tarvitsee myös enemmän muistia. Lisäksi rinnakkaisuuden ongelmat tulevat voimakkaasti esiin, erityisesti poissulkeminen. Myös reaaliaikakäyttäytymisen laskeminen etukäteen tulee vaikeammaksi. Kaikki nämä ominaisuudet ovat luonteeltaan sellaisia, että ne vaikeuttavat oikean toiminnan varmentamista ja mahdollisten ongelmatilanteiden selvittämistä. Toisaalta prosessiytimen tarjoamat edut ovat niin merkittäviä, että jo varsin yksinkertaisten järjestelmien toteuttamisessa sellaisen käyttö on usein perusteltua. Prosessiytimet voidaan edelleen jakaa kahteen kategoriaan sillä perusteella, miten eri oheislaitteita hallitaan. Kategoriat ovat monoliittinen ja mikroydin. Monoliittisessa ytimessä oikeastaan kaikki käyttöjärjestelmän vastuulle vähänkin ajateltavat toiminnot on sisällytetty ytimeen, kun taas mikroytimen tapauksessa tavoitteena on siirtää suuri osa toiminnoista omiin prosesseihinsa, ja pitää ytimessä vain olennaisimmat tehtävät, kuten prosessinhallinta ja skedulointi. Koska kummallakin ydintyypillä on omat vahvuutensa monoliittisessa ytimessä voidaan esimerkiksi jakaa tietoa eri toimintojen välillä melko helposti, kun taas mikroydin tarjoaa paremman vikasietoisuuden estämällä tilanne, jossa yksi vikaantunut toiminto rikkoo myös kaikki muut käyttöjärjestelmän osat monet käytännön toteutukset sisältävät piirteitä molemman tyyppisistä ytimistä. Prosessiytimessä on tavallisesti yksi prosessi, joka ei tee mitään. Tämän prosessin tehtävä on yksinkertaistaa skeduleria, ja sillä on pienin mahdollinen prioriteetti. Usein prosessia käytetään myös keräämään tietoa siitä, mikä järjestelmän tila on, kun prosessi saa suoritusvuoron. Tällöin on mahdollista kerätä tietoa järjestelmän toiminnasta tavalla, joka ei häiritse varsinaista toimintaa. Ongelmaksi saattaa muodostua yhtäältä se, että tietoa ei ole saatavissa silloin, kun sitä eniten tarvittaisiin, esimerkiksi ylikuormituksen yhteydessä, ja toisaalta se, että taustap-
Ytimet 129 rosessin toiminnot monimutkaistuvat, ja sillä alkaa olla todellinen rooli järjestelmän kokonaisuuden kannalta. Tällöin voidaan luoda toinen, prioriteetiltaan vähän korkeampi prosessi, joka ottaa hoitaakseen joitakin tehtäviä taustaprosessilta. Näin järjestelmän kokonaisrakenne muistuttaa seuraavaa: int main () { alusta laitteet; luo prosessit; while (1) { odota, että joku keskeyttää niin sanottu idle-prosessi prosessi 1 { prosessin 1 käskyt; prosessi n { prosessin n käskyt; Järjestelmässä on yhä keskeytysrutiinit, mutta niiden tehtävä on suorittaa siirräntä ja ilmoittaa siirräntää odottavalle prosessille siirrännän päättymisestä. Oikeastaan ytimessäkään ei ole mitään loogiseen toimintaan liittyvää, joten jäljelle jäävät vain prosessit ja niihin liittyvä toiminnallisuus. 7.2.4 Yhteenveto perusytimistä Eri ratkaisut toteuttavat saman ongelman kovin eri tavalla. Pollaavassa ratkaisussa ytimen toiminnot ja itse sovelluksen logiikka ovat kietoutuneet toisiinsa siten, että niitä ei voi erottaa toisistaan. Pollaavan ytimen laitteistotarpeet ovat yksinkertaisia: se ei tarvitse suorittimen ja välittömästi tarvittavien oheislaitteiden lisäksi kuin muistia. Tästä yksinkertaisuudesta maksetaan ohjelmien huonolla rakenteella. Keskeytysohjatussa ratkaisussa on paljon pollaavan ytimen piirteitä. Suurin ero on toiminnan perusideassa, mutta sekä hyvät että huonot puolet muistuttavat toisiaan, eli laitteistoratkaisu on yksinkertainen joskin vaatii keskeytyslinjojen kytkennän mutta ohjelmat ovat rakenteellisesti heikkoja. Prosessiydin vaatii mahdollisuuden ympäristön tallettamiseen ja vaihtamiseen verrattuna keskeyttävään ytimeen. Tämän lisäksi voidaan tarvita kellolaite. Varjopuolena on, että monen prosessin järjestelmässä alkaa laitteistokin monimutkaistua: käyttöön voi tulla DMA, muistin-
130 Sulautettu ohjelmointi hallintajärjestelmä ja niin edelleen. Koska jokaisella prosessilla tulee olla oma muistinsa ja erityisesti pinonsa, on muistin tarve prosessiytimellä myös suurempi kuin keskeyttävällä ytimellä. Ohjelmistopuolella ydin monimutkaistuu selvästi. Erityisesti ytimen toiminnot ja sovellus erotetaan toisistaan. Mikäli ydin on saatavissa valmiina, on itse sovellus huomattavasti helpompi kirjoittaa kuin edellisissä vaihtoehdoissa. 7.3 Valmiina saatavat ytimet Valmiina saatavat ytimet ovat käytännössä kaikki edellä mainittuja prosessiytimiä. Tyypillisesti sulautettuihin järjestelmiin saatavat ytimet ovat reaaliaikaisia. Niille on tyypillistä vaan ei välttämätöntä: Pieni koko. Prioriteetteihin perustuva irrottava skedulointi. Niissä ajetaan vain valmiiksi testattuja ohjelmia, joten suojauksia ei välttämättä tarvita, mikä nopeuttaa ympäristön vaihtoa ja prosessien välistä kommunikointia. Sovellusohjelmat ovat usein etuoikeutetussa tilassa, joten käyttöjärjestelmäkutsut, jotka eivät vaihda prosessia, voidaan toteuttaa aliohjelmakutsuilla ohjelmoitujen keskeytysten asemesta. Mikroydintyyppinen ratkaisu, jossa ydin sisältää vain prosessien hallinnan ja kommunikoinnin tarvitsemat palvelut. Kunkin laitteen ohjauksesta huolehtii jokin sovellusprosessi. Kaupallisten ytimien tarjoamat palvelut vaihtelevat hyvin yksinkertaisista (esimerkiksi C Executive, joka on lähinnä aliohjelmakirjasto) POSIXin (a portable operating system interface) mukaiseen käyttöjärjestelmään (muun muassa LynxOs, PDOS, psos+ ja UniFLEX). Skedulointimenetelmiä on valittavana oikeastaan vain kiinteäprioriteettinen irrottava skedulointi, joskin Linuxin mukana on tullut mukaan myös perinteinen vaihtuvaprioriteettinen Unix-skedulointi. Aiemmin ytimet olivat pieniä ja reaaliaikaisia, lähes askeettisia. Kannettaviin kuluttajalaitteisiin on kuitenkin saatavilla ytimiä, jotka tarjoavat palveluja käyttöliittymän tekemiseen ja jättävät perinteisen reaaliaikaisuuden sivuosaan. Esimerkkejä tällaisista ytimistä ovat Symbian ja jotkut Linux-pohjaiset toteutukset (lähinnä Android ja Meego).
Ytimet 131 7.3.1 Skaalattavuus Monet ytimet ovat skaalattavia. Pienimmässä kokoonpanossaan ne ovat vain muutamia kilotavuja, ja suurimmillaan ne ovat reaaliaikapiirteillä höystettyjä versioita Unixista. Skaalattavuus on edullista, jos yritys tekee hyvin erilaisia sovelluksia, koska henkilökunnan ei tarvitse opetella erilaisia ympäristöjä projektin vaihtuessa. Skaalattavuus voi kuitenkin johtaa tehottomuuteen, kun ytimessä on tarkistuksia sen varalle, onko jokin ominaisuus mukana vai ei. Ratkaisuna voidaan ajatella sitä, että räätälöinti tehdään kokonaan osana kehitystyötä, jolloin käytettävä ydin kyetään optimoimaan toteutettavan järjestelmän tarpeita varten. Samaten muistinkulutus vaihtelee merkittävästi sen mukaan, miten kehittynyt ydin on kyseessä lukuunottamatta erityisesti alhainen muistinkulutus mielessä kehitettyjä järjestelmiä. 7.3.2 Laitteistovalikoima Ytimet voidaan luokitella myös sen mukaan, miten suurta joukkoa suorittimia ne tukevat. Joitakin ytimiä saa käytännöllisesti katsoen mille suorittimelle tahansa, jolloin ydin ei rajoita suoritinvalikoimaa. Toisaalta jotkin suorittimet ovat sellaisia, että niihin saa lähes minkä ytimen tahansa esimerkiksi Intelin 80x86 ja Pentium muodostavat tällaisen suoritinperheen. Mobiililaitteiden puolella vastaavassa asemassa on ARM-suoritin, jota käytetään monissa mobiililaitteissa. Varsinaista suoritinta tärkeämmäksi onkin varsinkin monimutkaisimpien laitteiden tapauksessa nousemassa se, minkälaista tukea käyttöjärjestelmä ja laitteisto yhdessä tarjoavat sovelluskehitykselle. Tällöin esimerkiksi mahdollisuus tukea laitteistolla kiihdytettyä grafiikkaa voi olla merkittävä etu, sillä tällöin sovelluksista voidaan tehdä muita vastaavia ympäristöjä näyttävämpiä. Samaan tapaan myös muut kirjastot, joiden avustaminen laitteistolla on mahdollista, voivat vaikuttaa laitteiston valintaan. Suorittimien ja muun laitteiston tiukempi nivoutuminen onkin johtanut siihen, että useita suorittimia sisältäviä järjestelmiä on saatavilla. Esimerkkinä mainittakoon Texas Instrumentsin OMAP3, joka sisältää ARM-suorittimen, 2D/3D-grafiikkakiihdyttimen, kuvankäsittelyyn erikoistuneen signaalisuorittimen, yhden lisäkiihdyttimen, sekä suuren määrän erilaisia liityntöjä.
132 Sulautettu ohjelmointi 7.3.3 Kommunikointi- ja poissulkemismenetelmät Yleisimmät prosessien väliseen kommunikointiin tarkoitetut mekanismit ovat semaforit ja sanomat. Vaikka vain toisen olemassaolo on lähes välttämätöntä, monista ytimistä löytyvät molemmat. Myös signaalit, jotka eivät yleensä välitä muuta tietoa kuin tapahtumanumeron, ovat suhteellisen tavallisia. Kaikkien näiden käyttö on lähes aina yksinkertaista C- ja C++-ympäristöissä. Korkeamman tason mekanismit kuten monitorit ja Adan kohtaamismekanismi ovat harvinaisia. Ne tosin ovat enemmänkin ohjelmointikielen kuin ytimen rakenteita, mutta tarvitsevat ytimen tukea tehokkaalle toteutukselle. Lisäksi näitä rutiineja ei yleensä käytetä C- tai C++-kielistä, joilla monet sulautetut järjestelmät on toteutettu. Sen sijaan monitorit ovat käytössä Javassa, missä ne on tyypillisesti toteutettu osana Java-virtuaalikonetta, ei käyttöjärjestelmän palveluina suoraan. Tietysti virtuaalikone voi periaatteessa hyödyntää käyttöjärjestelmän palveluita, mutta siirrettävyyssyistä näin ei yleensä tehdä. Yhä suurempi osuus sulautetuista järjestelmistä liitetään myös verkkoon. Saatavissa on muun muassa digitaalikameroita, jotka voidaan liittää suoraan verkkoon. Aivan pienimmissä ytimissä ei ole mukana verkko-ominaisuuksia, mutta vähänkin monimutkaisemmissa käyttöjärjestelmissä hyvin yleisiä ovat TCP/IP- ja UDP/IP -perheiden protokollat. Vaikka jopa NFS (lähinnä asiakaspää) ja X-ikkunointi ovat perinteisesti olleet jonkin verran käytettyjä, nykyään monessa ympäristössä yleisemmin mukana on myös web-palvelin. Niinpä vaikkapa esimerkkinä mainittu digitaalikamera pystyy vastaamaan selainten kyselyihin ilman edustakonettakin. 7.3.4 Kehitysympäristö Kehitysympäristö tarkoittaa niitä laitteita ja ohjelmistoja, joilla ytimelle tehdään ohjelmat. Ytimien toteutuskielenä on useimmiten C- tai konekieli. Nämä ovat myös yleisesti saatavissa olevia kieliä sovellusten tekemiseen näihin ympäristöihin. Muita käytettävissä olevia kieliä ovat muun muassa PL/M, Ada ja Fortran, mutta prosentuaalisesti näiden käyttö on vähäisiä. Oliokielistä lähinnä C++ ja Java tulevat kysymykseen, mutta niiden soveltuvuus sulautettuun ohjelmointiin ei aina ole selvää. Java on alunperin tarkoitettukin sulautettuun järjestelmään, mutta käytännössä sen sovelluskohteet ovat olleet muualla. Myös C++ voi olla su-
Ytimet 133 lautetussa käytössä aika vaarallinen, ellei ongelmallisimpia olio-ominaisuuksia jätetä käyttämättä. Yleensä sulautetuissa järjestelmissä yleisin toteutuskieli on C (noin 80 %), toisena on symbolinen konekieli (n. 10 %) ja loppu 10 prosenttia jakaantuu C++:n, Javan ja muiden ohjelmointikielten kesken. Monille ytimille tehdään ohjelmat eri koneella kuin missä ohjelmistoa tullaan ajamaan (ristikehitys). Suurehkoille ytimille on mahdollista kohdekehitys, jossa tekoympäristö ja käyttöympäristö ovat samat. Kohdekehitys ei välttämättä ole ristikehitystä parempi vaihtoehto, koska kehitysympäristön ja kohdeympäristön vaatimukset voivat erota paljon toisistaan. Kehitysympäristön pitää tukea ohjelmointityötä, kohdeympäristössä taas reaaliaikaominaisuudet ja pieni koko ovat usein tärkeämpiä. Varsinaisen kehitystyön kannalta tärkeää on myös käytettyjen työkalujen hyvä laatu ja laaja käyttö, mikäli suinkin mahdollista. Monet sulautettujen järjestelmien tekoon tarkoitetut työkalut on tarkoitettu hyvin tarkkaan rajattuun ympäristöön, ja joskus voi olla niin, että samaa ympäristöä ei käytä kukaan toinen kehittäjä koko maailmassa. Tällaisessa tilanteessa esimerkiksi kääntäjän versiosta toiseen siirtyminen voi aiheuttaa merkittäviä ongelmia kehitystyölle, sillä uudessa versiossa kääntäjästä osa vanhoista, tunnetuista ongelmista on ehkä korjattu, mutta mukana saattaa olla uusia, kehittäjälle tuntemattomia ongelmia. Tästä syystä kovin erikoinen työympäristö kannattaa mahdollisuuksien mukaan jäädyttää joksikin sopivaksi ajaksi, ja tarvittaessa vaikka ostaa kehitysympäristön kehittäjältä tukipalveluita, jotta mahdollisesti kehitystyössä vastaan tulevat kriittiset ongelmat saadaan ratkaistua mahdollisimman nopeasti ja vaivattomasti. Sen sijaan laajasti käytössä olevien ympäristöjen kanssa ongelmia on perinteisesti ollut vähemmän, sillä laaja käyttäjäkunta auttaa löytämään (ja joskus esimerkiksi avoimen lähdekoodin kääntäjien tapauksessa myös ratkaisemaan) ongelmat nopeammin. 7.3.5 Kustannukset Ytimeen liittyvät kustannukset voidaan jakaa kolmeen kategoriaan: laitteiston aiheuttamat kustannukset, ytimen itsensä aiheuttamat kustannukset, ja kehitys- ja testausympäristön aiheuttamat kustannukset. Varsinkin massalevitykseen tarkoitettujen laitteiden tapauksessa tärkeä kustannustekijä on tietenkin suorittimen hinta. Erityisesti, jos lämpötila-alue on hyvin vaativa (-55 80 C), voivat suorittimien hinnat
134 Sulautettu ohjelmointi nousta korkealle. Pienimmät suorittimet maksavat noin 10 euroa ja kalleimmat noin 500 euroa, jos ei oteta huomioon kaikkein erikoistuneimpia laitteita. Hintaan vaikuttavat monet tekijät, kuten tehokkuus, virrankulutus, ja valmistusmäärä, sillä suurissa erissä hinnat ovat neuvottelukysymys. Suoritinvalikoimaa ei myöskään kannata laajentaa yhden tuotteen takia, joten yrityksen muissa tuotteissa käytetty suoritin tai suoritinperhe vaikuttaa valintaan. Tämä pyrkimys pitää erilaisten suorittimien määrä pienenä voi johtaa siihen, että joudutaan tekemään töitä huonosti asiaan sopivalla suorittimella. Ytimen tapauksessa oma toteutus on yleensä mahdollinen vaihtoehto, mutta se vaatii jonkin verran alkuinvestointia. Siksi ainakin aivan pienten tuotantoerien tapauksessa kannattaa usein käyttää jotain valmista ydintä. Sen sijaan massatuotteelle voi ongelmaksi muodostua lisenssimaksu, joka on maksettava jokaisesta reaaliaikaytimen sisältävästä tuotteesta. Vaikka tämäkään ei olisi kovin suuri yhtä konetta kohti, niin tuontantosarjojen ollessa satoja tuhansia vuodessa voi hyvinkin tulla halvemmaksi tehdä itse oma ydin tai käyttää jotain avoimen lähdekoodin ratkaisua. Kehitysympäristön suhteen tilanne on usein yksinkertainen, sillä niiden hinta ei yleensä ole kovin korkea ja silloin kun hinta on korkea, kyseessä saattaa olla ainoa mahdollinen tapa tuottaa ohjelmia valitulle laitteistolle ja ytimelle. Vaikka hintaa jonkin verran kertyisikin, se ei silti välttämättä muodostu ongelmaksi, koska kyseessä on yleensä kertaluontoinen maksu. 7.4 Esimerkki: Linux-ydin Vaikka Linux-ydin ehkä useimmiten yhdistetäänkin pöytäkoneen käyttöjärjestelmän ytimeen, on se yhä yleisemmin käytössä myös sulautetuissa ympäristössä. Koska ydin on alunperin rakennettu PC-käyttöön, sitä voidaan pitää jokseenkin raskaana sulautettuun ympäristöön, mutta toisaalta ytimen moduulaarinen rakenne mahdollistaa ytimen räätälöinnin melko helposti. Linux-ytimen rakenne on esitetty kuvassa 7.3. Sen tärkeimmät käsitteet ovat varsinainen ydin ja siihen ladattavat lisäosat. Varsinainen ydin sisältää yleensä seuraavat toiminnat: suorittimen ja välimuistijärjestelmän hallinta muistinhallinta prosessien hallinta liityntä tiedostojärjestelmiin,
Ytimet 135 Laiteajuri 1 Suorittimen ja välimuistin hallinta Laiteajuri 2 Muistinhallinta Laiteajuri 3 Laiteajuri 4 Prosessien hallinta Tiedostojärjestelmärajapinta Ytimen tilatiedot Tietoliikenneprotokollat Laajennos 1 Laajennos 2 Laite- ja I/O-rajapinnat Tietoturvaominaisuudet Kuva 7.3 Linux-ytimen osat tietoliikenneprotokollat laite- ja I/O-rajapinnat tietoturvaominaisuudet ytimen tilatieto. Ytimeen ladattavia lisäosia puolestaan ovat laiteajurit ja erilaiset ytimen laajennokset ja lisämoduulit. Räätälöimällä ydintä sopivasti voidaan rakentaa juuri kohdejärjestelmään sopiva ydin. Esimerkiksi jos ollaan rakentamassa Linuxpohjaista järjestelmää, jossa tietoliikenneyhteyksiä ei lainkaan käytetä, lienee lopputuloksen kannalta turhaa säilyttää tietoliikenneprotokollat toteuttava moduuli ytimessä olettaen, että siitä ei ole hyötyä kehitysvaiheessa, minkä seikan merkitystä ei tule väheksyä. Toinen syy säilyttää tietoliikenneprotokolliin liittyvä moduuli on, että käytettävät sovellukset luottavat siihen, että tietoliikenneominaisuudet ovat aina käytettävissä vähintään degeneroituneessa muodossa, jolloin niiden
136 Sulautettu ohjelmointi poistaminen voi johtaa suureen määrään toteutustyötä sovellusten yhteydessä. Kehitysympäristönä Linuxin yhteydessä käytetään melko yleisesti erilaisia virtualisoituja kehitysympäristöjä. Esimerkkinä voidaan mainita vaikkapa Nokian Maemo-ympäristön virtualisointiin käytetty Scratchbox (http://www.scratchbox.org/), joka mahdollistaa Maemoympäristöön tarkoitettujen ohjelmien kääntämisen ympäristössä, joka muistuttaa laitetta itseään. Erojakin kuitenkin on, kuten esimerkiksi käytettävissä oleva muistin määrä, joka on yleensä laitteessa huomattavasti pienempi kuin virtualisoidussa ympäristössä. Toinen joskus ongelmia aiheuttava ero on käytettävissä olevan tiedostojärjestelmän koko: virtualisoidussa ympäristössä tiedostojärjestelmän täyttyminen on harvoin ongelma, kun taas sulautetussa ympäristössä tähän ongelmaan voi törmätä helposti. Lisäksi suorituskyky sekä käyttökokemus kokonaisuudessaan ovat usein melko tavalla erilaisia virtualisoidussa ja kohdeympäristössä. 7.5 Yhteenveto Ytimien erot kulminoituvat siihen, miten ytimen ja sovelluksen toiminnot erotetaan: 1. Pollava ydin: Ydin ja sovelluslogiikka kietoutuneet toisiinsa; ohjausrakenne ikuinen silmukka. 2. Keskeyttävä ydin: Ydin ja sovelluslogiikka kietoutuneet toisiinsa; ohjausrakenne keskeytykset. 3. Prosessiydin: Ydin ja sovelluslogiikka erotettu toisistaan; laiteohjaus ytimessä, sovelluslogiikka prosesseissa. Prosessiydin voi olla monoliittinen tai mikroydin; monoliittisessa ytimessä lähes kaikki käyttöjärjestelmän mahdolliset toiminnot on upotettu ytimeen, kun taas mikroytimessä oheistoiminnot kuten esimerkiksi tiedostojärjestelmä pyritään toteuttamaan omina prosesseinaan. Useita valmiina saatavia ytimiä, joiden koko ja ominaisuudet vaihtelevat. Lähes aina kyse kuitenkin prosessiytimestä, johon on sisällytetty tukea reaaliaikaominaisuuksien toteuttamista varten.