Prosessi
Prosessi
Kun jotain ohjelmaa halutaan suorittaa järjestelmässä, se pitää ladata massamuistista. Latauksessa käyttöjärjestelmä luo uuden prosessin, joka on kyseisen ohjelman yksi suorituskerta järjestelmässä. Yhdestä ohjelmasta voi järjestelmässä olla samanaikaisesti usea prosessi järjestelmässä, vaikkakin vain yksi niistä voi kerrallaan olla suorituksessa (yhden suorittimen järjestelmässä). Esimerkiksi, sinulla voi olla monta selainikkunaa samanaikaisesti käytössä ja niissä kaikissa suorituksessa sama selainohjelma. Toisaalta, järjestelmässä voi samanaikaisesti olla prosesseja eri ohjelmista, joista niistäkin tietenkin vain yksi kerrallaan on suorituksessa suorittimella.
Ladattava ohjelma sijaitsee massamuistissa kääntämisen ja linkittämisen (ks. Luku 1) lopputuloksena syntyneessä latausmoduulissa, jossa on mm. ohjelmakoodi konekielisessä muodossa. Latauksessa käyttöjärjestelmä varaa tarvittavan määrän muistitilaa tälle prosessille ja kopioi sen ohjelmakoodin muistiin annetulle alueelle. Se myös tallettaa kaikki tähän prosessiin liittyvät hallintotiedot erityiseen tietueeseen, prosessin kuvaajaan (PCB, Process Control Block) tai siihen linkitettyihin muihin tietorakenteisiin. Kuvaajan tarkempi esittely on alla.
Juuri luotu uusi prosessi ei suinkaan ole vielä suorituksessa, koska sen luontihetkellä suorituksessa oli käyttöjärjestelmään sisältyvä lataaja. Uuden prosessin luomisen jälkeen käyttöjärjestelmä päättää, mikä prosessi saa seuraavaksi suoritusvuoron. Se voi olla juuri luotu prosessi, mutta yhtä hyvin se voi olla esimerkiksi se prosessi, jonka suoritus keskeytettiin uuden prosessin luontia varten.
Kun uusi prosessi pääsee vihdoin suoritukseen, suorittimen rekisterit täytyy alustaa sopivilla arvoilla. Ensimmäisellä suorituskerralla tämä tarkoittaa, että esimerkiksi muistin käyttöä rajaaville rekistereille (BASE ja LIMIT, ks. Luku 2.1) ja tilarekisterille (SR) asetetaan sopivat arvot prosessin kuvaajasta. Lopulta paikanlaskurille (PC) asetetaan uusi arvo tämän prosessin ohjelmakoodiin ja juuri sillä hetkellä kontrolli (suoritusvuoro) siirtyy tälle uudelle prosessille. Seuraava konekäsky on jo uuden prosessin suoritusta.
Prosessin käsite on kerta kaikkiaan nerokas. Se on täysin näennäinen (virtuaalinen) käyttöjärjestelmän luoma abstrakti käsite, johon kuitenkin kaikki tietokoneen laskenta perustuu. Kun käyttöjärjestelmä käynnistetään, siellä aivan aluksi (käyttöjärjestelmän ytimessä) luodaan rakenteet prosessien luontia ja prosessien välistä kommunikointia varten. Useat käyttöjärjestelmät (esim. Windows tai Linux) on itsekin toteutettu osittain prosessien avulla, jotka sitten voivat olla suorittamassa käyttöjärjestelmän hallintoa silloin tällöin.
Suoritin ei tiedä mitään prosesseista ja järjestelmässä suoritettavista tehtävistä. Se näkee ainoastaan peräkkäisesti suoritettavia konekäskyjä, joista osa on etuoikeutettuja ja osa ei. Konekäskyjen suoritus tapahtuu käskyjen nouto- ja suoritussyklin mukaisesti. Yleensä suoritin suorittaa konekäskyjä niiden muistissaolojärjestyksen mukaisesti. Aina silloin tällöin kontrolli siirtyy muualle ehdollisen haarautumisen (JZER, etc), hypyn (JUMP), aliohjelmakutsun (CALL), käyttöjärjestelmän palvelupyynnön (SVC) tai keskeytyksen vuoksi.
Prosessin kuvaaja
Prosessin kuvaajassa on kaikki tiedot, mitä järjestelmä tarvitsee tästä prosessista. Prosessin kuvaaja (tai ainakin tärkeimmät osat siitä) pidetään muistissa myös silloin kun prosessi ei ole suorituksessa, koska käyttöjärjestelmä käsittelee prosesseja nimenomaan niiden kuvaajien kautta. Kaikki prosessin tiedot eivät ole suoraan tässä kuvaajassa. Esimerkiksi tiedostojen käyttöön liittyvät tiedot voi olla keskitetty tiedostojärjestelmään ja prosessin kuvaajassa on sitten linkki tai linkkejä tiedostojärjestelmän tietoihin. Vastaavasti laitteiden hallinta pitää kirjaa kaikista järjestelmän laitteista ja tietyn prosessin kuvaajassa on linkkejä sen käyttämiin laitteisiin laiteiden hallinnassa.
Prosessin kuvaajassa on järjestelmässä uniikki prosessin tunniste (kokonaisluku, pid, process id), jonka avulla prosessi tunnistetaan tässä järjestelmässä ja muissakin järjestelmissä. Esimerkiksi, kun pyydät uutta verkkosivua joltain web-palvelimelta, selaimesi lähettää verkon kautta viestin palvelintietokoneelle (esim. www.helsinki.fi eli 128.214.189.90) siellä suorittavalle web-palvelinprosessille (tunniste esim. 2345). Pyynnössä on mukana sinun koneesi verkko-osoite (esim. 128.214.170.60) ja selainprosessisi tunniste (esim. 1287). Näiden avulla web-palvelinprosessi voi lähettää pyytämäsi verkkosivun sinun koneellesi (128.214.170.60) ja siellä juuri oikealle selaimelle (tunniste 1287). Prosessin tunniste voi olla esimerkiksi indeksi suureen prosessitauluun, josta prosessin tunnisteen perusteella löytyy prosessin kuvaajan osoite muistissa.
Kuten jo äsken mainittiin, niin kuvaajasta löytyy selkeästi määriteltynä mm. kaikki prosessille varatut muistialueet. Ne voivat olla yhtenä ryppäänä tai hajautettuna sinne tänne keskusmuistia. On myös täysin mahdollista, että muistialueita varataan ja vapautetaan dynaamisesti suoritusaikana. Vastaavasti kaikki prosessin käyttämät tiedostot löytyvät täältä (linkitettynä tiedostojärjestelmään). Kuvaajasta näkyy, mitkä tiedostot on avattu esim. lukuoikeuksilla ja missä kohtaa tiedostoa ollaan tällä hetkellä lukemassa. Kuvaajassa on tieto myös kaikista muista järjestelmän resursseista (esim. näppäimistö tai näytöllä oleva ikkuna), jotka prosessi on varannut käyttöönsä.
Kuvaajassa on myös prosessin prioriteetti (esim. 35 tai 85) suorittimen vuoronantoa varten. Käyttöjärjestelmäprosesseilla on aina parempi prioriteetti kuin tavallisilla käyttäjän aloittamilla prosesseilla. Ainahan hallinto on tärkeämpää kuin tavallinen työnteko ja varsinkin tietokonejärjestelmissä. Jos esimerkiksi muistinhallintaa ei tehdä ajoissa kuntoon, järjestelmä kaatuu.
Joissakin järjestelmissä prosessit saavat olla suorituksessa korkeintaan jonkin tietyn ajan, jonka jälkeen niiden pitää antaa vuoro toisille. Tällaista kerralla laskentaan käytettävää maksimiaikaa kutsutaan prosessin aikaviipaleeksi ja sen pituus löytyy myös kuvaajasta. Kuvaajasta löytyy myös tieto siitä, suorittaako prosessi (tällä hetkellä) etuoikeutetussa tilassa vai ei.
Jos prosessi joutuu odottamaan jotain asiaa (esim. syötettä käyttäjältä), niin odotuksen syy löytyy kuvaajasta. Käyttöjärjestelmä pitää kuvaajassa kirjaa prosessin käyttämästä suoritinajasta, jota joissakin pilvipalveluissa käytetään laskutusperusteena.
Suoritinympäristö
Tärkeänä komponenttina kuvaajassa on prosessin suoritinympäristö. Ajatellaan tilannetta, jossa prosessi on menettänyt suoritusvuoronsa suorittimelle vaikkapa keskeytyksen jälkimainingeissa. Prosessi täytyy nyt laittaa odottamaan uutta vuoroa suorittimelle ja haluamme suorituksen jatkuvan sitten joskus ihan samalla tavalla kuin jos keskeytystä ei olisi ikinä tapahtunutkaan. Tämä toteutetaan tallettamalla lähes kaikki suorittimen rekistereiden arvot tämän prosessin suoritinympäristöön. Tämä ei ole mikään valtava määrä tietoa, koska rekistereitä on korkeintaan muutama sata.
Sitten kun prosessi pääsee suoritukseen uudestaan, rekistereiden tallennetut arvot kopioidaan kuvaajasta takaisin suorittimelle ja suoritus voi jatkua normaalisti. Erona aikaisemmin mainittuun ensimmäiseen suorituskertaan on, että suoritinympäristöstä haetaan nyt myös kaikki aikaisemmin laskennassa käytetyt työrekisterit. Niillähän ei ensimmäisellä suorituskerralla ollut vielä mitään järkeviä arvoja, kun prosessi ei ollut vielä suorittanut yhtään konekäskyä.
Suoritinympäristön tietojen kopiointi tapahtuu ihan tavallisilla konekäskyillä ja muistiinviittauksilla kyseisen prosesssin kuvaajaan. Kopiointi tapahtuu etuoikeutetussa suoritustilassa, kuten kaikki muukin prosessien hallintaan liittyvä käyttöjärjestelmän tekemä työ. Prosessien kuvaajiin ei tavallisilla käyttäjätason prosesseilla ole pääsyä.
Suoritinympäristöön ei tarvitse kopioida ihan kaikkia suorittimen rekistereiden arvoja, koska suorituksessa oleva prosessi voi vaihtua toiseen ainoastaan konekäskyjen suoritusten välissä. Sellaisia rekistereitä, joiden arvot asetetaan joka tapauksessa uudelleen yhden konekäskyn suorituksen aikana, ei tarvitse tallettaa suoritinympäristöön. Tällaisia ovat esimerkiksi väylää ohjaavat rekisterit MAR ja MBR (ks. Luku 2.1).
Prosessin ollessa suorituksessa sen suoritinympäristö on siis suorittimen rekistereissä ja muulloin se on talletettuna prosessin kuvaajaan.
Käyttöjärjestelmän tietorakenteiden yhteiskäyttöongelma
Prosessien kuvaajat ja niihin linkitetyt eri alijärjestelmien (esim. tiedostojärjestelmä) tiedot muodostavat yhdessä valtavan tietorakenteen, jota hyvin moni käyttöjärjestelmän osa käsittelee. Tämä aiheuttaa käyttöjärjestelmän suunnittelussa ja toteutuksessa hankalan ongelman, koska käyttöjärjestelmän osia on järjestelmässä useita suorituksessa samanaikaisesti. Esimerkiksi, usea prosessi on saattanut pyytää levy-I/O:ta tai lisämuistitilaa samanaikaisesti.
Käyttöjärjestelmän omien tietorakenteiden hallinnassa on tämän vuoksi samanaikaisuusongelma, koska usean prosessin tarvitsee käsitellä (ja muuttaa) samoja tietorakenteita samanaikaisesti. Samanaikaisuusongelma ratkaistaan rajoittamalla tietyn tietorakenteen osan käyttö yhdelle prosessille kerrallaan. Se on kuitenkin vaikeata tehdä, kun yleensä ei ole tietoa näistä muista prosesseista, jotka juuri nyt haluaisivat ehkä koskea siihen samaan tietorakenteeseen. Jos ratkaisu epäonnistuu, kyseinen käyttöjärjestelmän tietorakenne jää rikkinäiseen tilaan ja koko järjestelmä voi hyytyä (esim. Blue Screen of Death). Tämä onkin yleisin syy tietokonejärjestelmien jumiutumiseen siten, että ne eivät reagoi enää mihinkään.
Yleensä ainoa toipumiskonsti hyytyneeseen järjestelmään on pistää virrat pois ja alustaa (bootata) koko järjestelmä uudelleen. Tästä aiheutuu usein ärsyttävän paljon haittaa, kun kaikki levylle tallentamaton keskeneräinen työ voi mennä hukkaan.
Samanaikaisuusongelmat ovat vaikeita poistaa, koska käyttöjärjestelmässä voi olla satoja tuhansia tai miljoonia rivejä koodia, ja sitä on toteuttamassa suuri määrä ohjelmoijia eri organisaatioissa. Ongelma tulee vähän paremmin hallittavaksi, jos tärkeimpiä tietorakenteita käsitellään vain käyttöjärjestelmän etuoikeutetussa ytimessä (kernel). Etuoikeutetusta ytimestä voidaan tehdä mahdollisimman pieni, jolloin myös yhteisiä tietorakenteita käyttävästä koodista tulee pienempi ja sieltä on helpompi löytää virheitä.
Prosessin vaihto
Prosessin vaihto on tapahtumaketju, jossa suorittimella suoritusvuorossa oleva prosessi vaihtuu. Se tapahtuu käytännössä aika usein, esimerkiksi 1-30 ms välein. Prosessin vaihto tapahtuu usein sen vuoksi, että suorituksessa oleva prosessi ei voi jatkaa suoritusta, vaan sen pitää jäädä odottamaan jotain. Syynä voi olla, että prosessi tarvitsee jotain tietoa massamuistista tai verkosta, tai sitten se jää odottamaan esimerkiksi vastausviestiä toiselta prosessilta pyytämäänsä palveluun.
Esimerkiksi, prosessi P pyytää viestin avulla levy I/O:ta laiteajuriprosessilta DD ja jää sitten odottamaan vastausviestiä. Laiteajuri DD ottaa viestin vastaan ja pyytää levyn laiteohjainta tekemään kyseisen I/O:n (tai osan I/O:sta). Sitten laiteajurikin jää odottamaan, kunnes laiteohjain ilmoittaa I/O-keskeytyksen avulla sille annetun tehtävän valmistumisesta.
Toinen syy prosessin vaihtoon voi olla, että prosessin aikaviipale on tullut täyteen ja on aika antaa suoritusvuoro jollekin toiselle prosessille. Tämä havaitaan, kun käyttöjärjestelmä on saanut suoritusvuoron esimerkiksi kellolaitekeskeytyksen (ks. Luku 2.2) tai jonkin muun keskeytyksen kautta. Joka kerta, kun käyttöjärjestelmä saa suoritusvuoron, se voi tehdä hallintoa ja mahdollisesti päättää prosessin vaihdosta. Tämä on itse asiassa todella ärsyttävä ja vaikeita ongelmia aiheuttava käyttöjärjestelmän ominaisuus.
Kolmas syy prosessin vaihtoon on, että nyt suorituksessa oleva prosessi päättyy eli ohjelman suoritus päättyy normaalisti. Tämä toteutetaan yleensä kutsumalla jotain käyttöjärjestelmärutiinia SVC-konekäskyllä (ks. Luku 2.3).
Prosessin vaihdon toteutus
Prosessin vaihto tapahtuu kahdessa vaiheessa. Ensiksi, nyt suorituksessa ollut prosessi joko lopetetaan kokonaan tai sitten se laitetaan odottamaan jotain. Jos prosessi lopetetaan, niin sitten kaikki sen käyttämät tiedostot suljetaan ja kaikki sen käyttämät resurssit vapautetaan muiden prosessien käyttöön. Tämä on "helpohkoa" tehdä, koska kaikki tiedot löytyvät (linkitettynä) prosessin kuvaajasta. Lopulta kaikki tiedot on poistettu ja prosessin tunnistekin vapautetaan uusiokäyttöön.
Jos prosessi laitetaan odottamaan jotain, niin sen suoritinympäristö pitää ottaa talteen sen kuvaajaan. Tämänkin työn tekee prosessienhallinnan joku moduuli.
Toiseksi, prosessienhallinta kutsuu prosessien vuoronantajaa (scheduler), joka sopivaa vuoronantoalgoritmia käyttäen valitsee uuden prosessin suoritukseen. Suorittimen rekistereihin kopioidaan uuden prosessin suoritinympäristö sen kuvaajasta ja suoritusvuoro siirtyy näin uudelle prosessille.
Prosessin elinkaarimalli
Prosessin "elämää" järjestelmässä sen luonnista sen päättymiseen kuvataan prosessin elinkaarimallin avulla. Tästä mallista on muutamia variantteja riippuen siitä, kuinka paljon yksityiskohtia halutaan ottaa mukaan. Tässä esitellään mallin yksinkertaisin versio.
Prosessin luonnin ollessa vielä kesken sen ajatellaan olevan tilassa "luonti" ("uusi", "new"). Prosessi on tilassa "valmis suoritukseen" ("ready", "ready to run"), kun se odottaa suoritusvuoroa suorittimelle. Tuolloin sen kaikki tarvittavat muistialueet (koodi ja data) ovat valmiina keskusmuistissa ja se vain odottaa vuoroaan päästä suoritukseen. Kun prosessi vihdoin on suorittamassa suorittimella, sen tilana on "suorituksessa" ("running"). Sillä on tuolloin tietenkin kaikki sen tarvitsemat koodi- ja data-alueet muistissa. Jos prosessi on odottamassa mistä tahansa syystä, se on tilassa "odottaa" ("waiting", "suspended"). Kun prosessi joko itse pääsee koodin loppuun tai käyttöjärjestelmä on tappanut sen, prosessin tilana on "päättynyt tai poistettu" ("tapettu", "terminated", "killed"), kunnes kaikki sen rakenteet on vapautettu uusiokäyttöön. Joskus prosessia ei voi poistaa kokonaan ennen kuin kaikki sen itse käynnistämät prosessit ("lapsiprosessit") on ensin poistettu järjestelmästä. Tuollaisia vähän pidempään tapettu-tilassa olevia prosesseja kutsutaan joskus kuvaavasti zombie-prosesseiksi, koska ne eivät enää oikeastaan tee mitään paitsi odottavat "lapsiensa" "kuolemaa" eli lapsiprosessiensa päättymistä. Tarkemmassa elinkaarimallissa niillä voi tuolloin olla oma tilansa.
Prosessien elinkaarimallin tilasiirtymät
Jos uusi prosessi saa heti käyttöönsä kaikki sen tarvitsemat (esim. muisti-) resurssit, se voidaan siirtää ready-jonoon. Jos taas muistitilaa ei ole vapaana juuri nyt tarpeeksi, käyttöjärjestelmä siirtää uuden prosessin odotustilaan sellaiseen jonoon, jossa odotetaan vapautuvaa muistitilaa. Kun joku muu prosessi päättyy tai tapetaan, siltä vapautunut muistitila voidaan antaa tämän uuden prosessin käyttöön. Tarvittavat koodi/data-tiedot kopioidaan muistiin ja uusi prosessi voidaan vihdoin siirtää ready-jonoon odottamaan suoritusvuoroaan.
Aina kun vuoronantaja on valitsemassa seuraavaa prosessia suoritukseen, se käy läpi ready-jonossa olevia prosesseja ja antaa vuoron korkeimman prioriteetin omaavalle prosessille. Usein ready-jono on oikeasti toteutettu useana jonona, yksi kutakin prioriteettia kohden. Esimerkiksi, prioriteetteja voi olla 128, jolloin ready-jonoja on 128. Jotta kaikkia prosesseja kohdeltaisiin "reilusti", useat käyttöjärjestelmät (esim. Windows ja Linux) vaihtelevat prosessien prioriteetteja dynaamisesti suoritusaikana. Prosessin prioriteetti huononee, jos prosessi saa paljon suoritinaikaa. Prioriteetti paranee, jos prosessi joutuu odottamaan kovin kauan suoritusvuoroaan. Tästä huolimatta käyttäjätason prosessien prioriteetit ovat aina huonompi kuin käyttöjärjestelmätason prosessien prioriteetit.
Ready-jono ei ole koskaan tyhjä. Siellä on vähintään joku (alhaisimman prioriteetin) taustaprosessi, joka laskee ikuisessa silmukassa esimerkiksi piin tarkkaa arvoa tai vain pyörii silmukassa tekemättä mitään järkevää. Suoritin suorittaa koko ajan joitakin konekäskyjä, koska se ei osaa tehdä mitään muutakaan. Nykyaikaisissa akkukäyttöisissä laitteissa tosin voi olla virransäästöoptio. Taustaprosessin suorittamisen asemesta suoritin laitetaan virransäästötilaan (horrostilaan tai lepotilaan), josta se herää jonkun sopivan keskeytyksen jälkeen. Taustaprosessi voi olla myös valittu sillä tavoin, että sen suorittaminen kuluttaa mahdollisimman vähän sähköä.
Jos käyttöjärjestelmä havaitsee, että prosessi on käyttänyt aikaviipaleensa loppuun, niin kyseinen prosessi siirretään takaisin ready-jonoon ja vuoro annetaan jollekin toiselle prosessille. Tämä suoritusvuoron vuorottelu tapahtuu käyttäjän (ihmisen) näkökulmasta hyvin tiuhaan tahtiin, esim. 10 ms välein. Näyttää, että kaikki järjestelmässä olevat prosessit olisivat suorituksessa samanaikaisesti, vaikka oikeasti vain yksi prosessi on suorituksessa kerrallaan. Käyttäjä on tyytyväinen, koska ainakin hänen ohjelmansa suoritus näyttää etenevän koko ajan. Vuorottamalla saadaan usean prosessin keskimääräinen vasteaika (aika työn saapumisesta sen valmistumiseen) pienemmäksi verrattuina tilanteeseen, jossa samat prosessit olisi suoritettu loppuun yksi kerrallaan. Palvelu siis paranee ihan oikeastikin.
Esimerkki. Prosessit A, B ja C saapuvat järjestelmään yhtä aikaa ja
vaativat 100, 40 ja 10 ms laskenta-aikaa. Aikaviipaleen koko on 10 ms.
Jos A, B ja C suoritetaan loppuun tässä järjestyksessä, niin niiden
vasteajat ovat 100, 140 ja 150 ms.
Keskimääräinen vasteaika on (100+140+150)/3 = 390/3 = 130 ms.
Jos A, B ja C suoritetaan järjestelmässä aikaviipale kerrallaan,
niin suoritusjärjestys on A B C A B A B A B A A A A A A.
A:n vasteaika on nyt 150 ms, B:n 90 ms ja C:n 30 ms.
Keskimääräinen vasteaika on (150+90+30)/3 = 270/3 = 90 ms.
Huomaa, että erityisesti lyhyiden prosessien B ja C vasteajat
ovat nyt selkeästi paremmat kuin ensimäisessä tapauksessa.
Kun suorituksessa oleva prosessi tarvitsee mitä tahansa resurssia, joka ei juuri nyt ole saatavilla, se siirtyy odotus-tilaan tuon resurssin mukaiseen jonoon. Tällaisia jonoja voi olla esimerkiksi muistitilaa odottavat prosessit, levy I/O:n päättymistä odottavat prosessit ja prosessilta 532 viestiä odottavat prosessit. Sitten kun kyseinen resurssi tulee saataville, se annetaan odottavalle prosessille, joka sitten siirretään ready-jonoon.
Käyttöjärjestelmä voi (saatuaan jollain tavoin suoritusvuoron) tappaa missä tahansa tilassa olevan prosessin, joka sitten siirretään poistettu tai tapettu -tilaan ja lopulta siivotaan pois koko järjestelmästä.
Prosessien kuvaajat elinkaarimallin mukaisissa jonoissa
Prosessin tila voi olla merkittynä sen kuvaajaan. Ennen kaikkea tila selviää siitä, missä jonossa prosessi kulloinkin on. Käyttöjärjestelmä käsittelee prosesseja niiden kuvaajina ja siirtelee kuvaajia jonosta toiseen tarpeen mukaan. Esimerkiksi, prosessi on tilassa ready, jos se on jonkun prioriteettiluokan ready-jonossa. Samoin prosessi on odotustilassa, jos se on johonkin resurssiin liittyvässä sitä resurssia odottavien prosessien jonossa.
Muistathan tarkistaa pistetilanteesi materiaalin oikeassa alareunassa olevasta pallosta!