################################## Prosessinhallinnan peruskäsitteitä ################################## Konteksti (context) =================== - prosessorin tila, kun se suorittaa ohjelmaa + rekisterit, ml, IP, FLAGS, datarekisterit, osoiterekisterit - jokaisella prosessilla on oma kontekstinsa. - vain yhden prosessin konteksti on muuttuvassa tilassa yksiprosessorijärjestelmässä (kaksiprosessorijärjestelmässä kahden prosessin kontekstit, jne...) - muiden prosessien kontekstit ovat "jäädytettynä" jemmassa (käyttöjärjestelmä pitää niitä tallessa, kunnes prosessi saa ajovuoron, ja konteksti siirretään rekisteriin). Kontekstin vaihto (context switch) on terminä häilyvä. Konteksti on esim. meidän luentomonisteemme mukaan prosessorin tila. Jotkut lähteet (kuulemma) laventavat kontekstin olemaan myös muuta prosessiin liittyvää tietoa. Puhukaamme mieluummin prosessin vaihdosta, kun puhutaan siitä, että prosesseja vaihdellaan perätysten. Yksi osa on se, että prosessori saa "pureskeltavakseen" eri prosesseja, ja sen "työympäristö" eli laitteiston suorituksen "konteksti" vaihtuu samalla kun prosessista toiseen vaihdetaan. Kontekstin vaihto on siis tämän saivartelun mukaan laitteiston kokema toimintatilan vaihdos. Prosessin vaihto sisältää prosessorin kontekstin vaihdon lisäksi myös käyttöjärjestelmän suorittamaa kirjanpitoa keskusmuistissa, levyllä, ym., mikä on siis prosessorin kontekstista (rekisterien sisällöt) erillinen asia. Puhhuh. Prosessin vaihto (process switch) on tätä: - yhden prosessin tilan tallentaminen prosessorista jonnekin muistiin. (kernel-pinon kautta, FLIH laitteistotasolla, käyttöjärjestelmä hoitaa siitä eteenpäin) - jonkun toisen prosessin tilan palauttaminen prosessoriin (käyttöjärjestelmä hoitaa) + Prosessi itse ei huomaa vaihtoa + Koko prosessorin tila (siten kuin käyttäjän ohjelma voi sen nähdä == "user visible registers") on täysin sama kuin silloin kun prosessi joskus aiemmin keskeytettiin. Toki käyttöjärjestelmän näkemä, käyttäjän prosessilta piilossa oleva, osa prosessoria voi olla muuttunut sen mukaan, mitä käyttäjärjestelmä teki sillä välillä kun prosessi oli keskeytettynä. + prosessia ei välttämättä tarvitse vaihtaa joka keskeytyksellä; se riippuu käyttöjärjestelmän vuorontajaan valituista algoritmeista. Yleensä mm. kellokeskeytys moniajojärjestelmässä tarkoittaa nykyisen prosessin aikaviipaleen loppumista, ja ainakin silloin vaihdetaan prosessia. Toisaalta joku I/O, vaikkapa näppäinpainallus, voidaan lyhyesti kirjata käyttöjärjestelmän sisäiseen puskuriin odottamaan myöhempää käsittelyä, ja keskeytynyttä prosessia voidaan jatkaa ilman mitään vaihdosta aina aika-askeleen loppuun tai muuhun luonnolliseen vaihdokseen saakka. - myös muista tilatiedoista kuin rekisterikontekstista pidetään kirjaa vaihdosten yhteydessä. Tämä tapahtuu käyttöjärjestelmäohjelman tasolla, toisin kuin kontekstin (rekisterit) vaihto, joka on useimmiten toteutettu hyvin pitkälle laitteistotasolla FLIH-toiminnassa ja keskeytyksestä palaavan konekielikomennon toteutuksessa. Käyttöjärjestelmän tietorakenteita ================================== Process Table (prosessitaulu): Process Control Block, PCB (prosessielementti, yksi per prosessi) PID #1 Process Control Block, PCB (prosessielementti, yksi per prosessi) PID #2 Process Control Block, PCB (prosessielementti, yksi per prosessi) PID #3 ... PCB:itä on monta -- jokaisella käynnistetyllä prosessilla yksi ... Process Control Block, PCB (prosessielementti, yksi per prosessi) PID #21189 ... mutta niiden maksimimäärä on rajoitettu -- esim. positiivisten, etumerkillisten, 16-bittisten kokonaislukujen määrä, eli 32768 kpl. Enempää ei pystyisi prosesseja luomaan. Esim. tuollainen määrä kuitenkin on jo aika riittävä. Esim. 3000 käyttäjää voisi käyttää yli kymmentä ohjelmaa yhtä aikaa. Ennemmin luultavasti loppuu prosessoriteho kaiken laskennan aikaansaamiseksi. Yhden PCB:n sisältö: ------------------- - prosessin yksilöivä tunnus eli prosessi-ID, "PID". Voi olla toteutuksen kannalta PCB:n indeksi prosessitaulussa. - konteksti eli prosessorin "user-visible registers" sillä hetkellä kun viimeksi tuli keskeytys, joka johti tämän prosessin vaihtamiseen pois Running-tilasta. - PPID (parent eli vanhempiprosessin ID) - voi olla PID:t myös lapsiprosesseista ja sisaruksista - UID, GID (käyttäjän & ryhmän ID:t) - prosessin tila (ready/blocked/jne...) ja prioriteetti - resurssit + avatut/lukitut tiedostot + muistialueet (koodi, data, pino, dynaamiset alueeet) - viestit muilta prosesseilta, mm. + Sanomanvälitysjono + Signaalijono + putket Esimerkki tilavaihdoksista: --------------------------- tilanne: PID 24: User Running. PID 343: Blocked Prosessori suorittaa prosessin 24 koodia. tapahtuma: keskeytys/KJ-kutsu tilanne: PID 24: Kernel Running Prosessori suorittaa käyttöjärjestelmän koodia. Esim. tuli merkki päätteeltä prosessille PID 343 -> KJ laittaa merkin tiedoksi prosessille PID 343 ja siirtää sen Ready-tilaan (oli Blocked, koska odotti merkkiä) ja jatkaa PID 24:n suorittamista. tilanne: PID 24: User Running. PID 343: Ready Prosessori suorittaa yhä prosessin 24 koodia. (343 voi jatkua sitten joskus ja saa sitten odottamansa merkin paluuarvona käyttöjärjestelmäkutsulta.) TAI (toinen tapa toteuttaa, tapoja on paljon, ne riippuvat käyttöjärjestelmän toteutusvalinnoista; eri menettelyt soveltuvat paremmin joihinkin tietotekniikan käyttötarkoituksiin ja huonommin joihinkin): tilanne (sama kuin edellisen esimerkin alussa): PID 24: User Running. PID 343: Blocked Prosessori suorittaa prosessin 24 koodia. tapahtuma: keskeytys/KJ-kutsu tilanne: PID 24: Kernel Running Prosessori suorittaa käyttöjärjestelmän koodia. Esim. tuli merkki päätteeltä prosessille PID 343 -> KJ laittaa PID 24:n Ready-tilaan -> KJ laittaa merkin tiedoksi prosessille PID 343 ja siirtää sen Running-tilaan (oli Blocked, koska odotti merkkiä) ja jatkaa siis siitä. tilanne: PID 24: Ready PID 343: User Running Prosessori suorittaa prosessin 343 koodia. Vuorontamismenettelyt, prioriteetit =================================== Esim. ready-jono (pysähtyneinä, mutta valmiina suoritukseen, kun aikaviipale olisi tarjolla): [READY] -> PID 24 -> PID 7 -> PID 1234 -> null Blocked-jono (pysähtyneinä, odottavat esim. I/O:ta): [BLOCKED] -> PID 9139 -> PID 45 -> PID 343 -> null "Round robin" -menettelyssä valitaan aina jonon kärjestä kauiten odotellut prosessi suoritukseen. "Ohiajot" eivät mahdollisia; ei sovellu reaaliaikajärjestelmiin, kuten musiikkiohjelmien suorittamiseen. Millisekunninkin katkos audion tuotannossa kuulostaa poksahdukselta; audio-ohjelman pitäisi pystyä "etuilemaan" jonossa, jos sen odottama keskeytys saapuu, tai aina korkeintaan tietyn aikavälin kuluttua. Samoin muiden kriittisten reaaliaikasysteemien, kuten rakettimoottorin tai ydinvoimalan ohjaus mittaritiedon perusteella. Tarvitaan siis pre-emption, eli aikaviipaleen keskellä tapahtuva nykyisen prosessin keskeytys ja prosessin vaihto tärkeämpään -- ja jokin toteutus prioriteettien hallintaan. Esimerkki prioriteeteista, monta Ready-jonoa: Ready-jonot (pysähtyneinä, mutta valmiina suoritukseen): Prioriteetti 0 [READY0] -> NULL Prioriteetti 1 [READY1] -> PID 24 -> PID 7 -> PID 1234 -> PID 778 -> NULL Prioriteetti 2 [READY2] -> PID 324 -> PID 1123 -> NULL ... Prioriteetti 99 [READY99] -> NULL Esim. suositaan pienemmän prioriteettitason jonoja vuoronnuksessa. Prosesseja voitaisiin myös tietyin perustein siirtää prioriteettitasojen välillä, eli prioriteetit voisivat olla dynaamisia. Joka tapauksessa tulee välttää tilanne, jossa joku prosessi jäisi ikuisesti saamatta yhtään aikaa. Reaaliaikaprioriteetin prosessit otetaan jonojen ohi välittömästi, kun esim. niihin liittyvää I/O:ta havaitaan. Edelleen reaaliaikaohjelmatkin räjähtävät käsiin, jos niiden yhteenlaskettu laskentatarve ylittää kaiken käytettävissä olevan prosessoriajan. Jonotusongelmat ovat vaikeita hallita, ja ne edellyttävät kompromissiratkaisuja. Laajemmin mm. jonotussääntöjen kaltaisia kysymyksiä tutkii tieteenala nimeltä operaatiotutkimus (OR, "operations research"). Käyttöjärjestelmän vuorontaminen on yksi sovellusala, jossa jotkut teoreettiset mallit voivat olla samankaltaisia kuin esim. pilvenpiirtäjän hissien ruuhka-aikojen mallinnuksessa käytettävät. Ja toisin päin. Prosessin luonti fork():lla =========================== Käyttöjärjestelmäkutsu fork() on ainoa tapa, jolla perus-Unixissa voi kukaan tehdä uuden prosessin. Linuxissa fork() toimii, koska yleensäkin unix-jutut siinä toimivat -- kuitenkin fork() on toteutettu Linuxissa erityistapauksena clone() -kutsusta, jolla voi tehdä myös säikeitä (Linuxissa säie on "light weight process"; prosessin ja säikeen "aste-erot" ovat hienosäädettävissä clone()-kutsun parametreilla, ja isoimmillaan ero on niin iso, että toteutuu perus-unixin fork()). Säikeistä lisää myöhemmin. Käyttöjärjestelmä luo fork()-kutsua käsitellessään hiukan muutetun kopion nykyisestä prosessista (joka pyysi forkkausta eli haaroitusta): Uuden PCB:n sisältö: Nämä tulee uusiksi uudelle haaroitetulle prosessille: - prosessin yksilöivä tunnus eli prosessi-ID, "PID" - PPID (parent eli vanhempiprosessin ID) := forkin kutsujan PID Tämä on melkein sama: - prosessin konteksti (pysyy samana fork() -kutsun paluuarvoa lukuunottamatta!) Nämä kopioituvat: - UID, GID (käyttäjän & ryhmän ID:t) - resurssit (HUOM! täytyypä tarkistaa speksi, että miten näiden kanssa itse asiassa tapahtuukaan...) + tiedostot + muistialueet (ainakin jossain määrin samat) (koodi, data, pino, dynaamiset alueeet) - viestit muilta prosesseilta + Sanomanvälitysjono + Signaalijono Forkin käyttö on esitelty luentomonisteessa esimerkin ja kuvien kera, ja siitä on saatavilla esimerkkikoodi minish.c kurssin nettisivulta.