Prosessien skedulointi

Prosessien tilat

Kukin prosessi on siis välillä suorituksessa prosessorilla ja välillä odottamassa uutta suoritusvuoroa. Sanotaan että CPU:lla suorituksessa oleva prosessi on running-tilassa.

Syitä sille, että prosessi joutuu pois prosessorilta on kaksi:

  • Prosessi tekee systeemikutsun, joka aiheuttaa odotustilanteen, esim. luku kiintolevyltä tai näppäimen painalluksen odotus (esim. gets-komento)
  • Prosessi on suorittanut niin kauan, että KJ päättää että suoritukseen siirretään uusi prosessi
  • Ensimmäisessä tapauksessa prosessi ei voi edetä ennen kun systeemikutsun aiheuttama operaatio on valmiina. Prosessi joka ei voi edetä, on sleeping-tilassa. Jälkimmäisessä tapauksessa prosessi taas voisi periaatteessa edetä jos se saisi CPU-aikaa. Prosessi joka voi edetä mutta ei ole suorituksessa on runnable-tilassa.

    Käynnissä oleva prosessi on siis kullakin hetkellä jossakin kolmesta tilasta: running, runnable tai sleeping. Prosesseilla on myös muita tiloja, esim. zombie, josta puhuttiin viime kerralla, sekä virtuaalimuistiin (josta puhumme myöhemmin) liittyvät tilat, joissa prosessi on swapattuna pois keskusmuistista.

    Alla oleva kuva esittää prosessien tilojen suhteita:

    Kun prosessi ei voi jatkaa siirtyy se tilaan sleeping, näin tapahtuu esim. odotetaan näppäimen painallusta komennolla gets(). Kun prosessi on jälleen valmiina suoritukseen, siirtyy se tilaan runnable.

    Skeduleri

    Jos koneessa on yksi prosessori, vain yksi prosessi kerrallaan on running-tilassa. Käyttöjärjestelmän komponentti, jota nimitetään skeduleriksi valitsee kulloinkin running-tilassa olevan prosessin. Skedulerin tehtävä on päättää kuka on milloinkin suorituksessa ja miten kauan.

    Aika ajoin suorituksessa oleva prosessi vaihdetaan. Syynä prosessin vaihtumiseen voi olla systeemikutsu, joka aiheuttaa prosessin siirtymisen sleeping-tilaan. Prosessi saatetaan vaihtaa myös jos todetaan prosessin jo saaneen riittävästi suoritusaikaa, tällöin prosessi siirretään runnable-tilaan odottamaan seuraavaa suoritusvuoroa.

    Käyttöjärjestelmä tallettaa suorituksesta pois siirtyvän prosessin suoritustilanteen siten, että prosessi voidaan taas aikanaan palauttaa suoritukseen siten, että se aloittaa täsmälleen samasta tilanteesta, missä se oli siirtyessään pois suorituksesta.

    Suorituksessa olevan prosessin vaihtamisen yhteydessä käyttöjärjestelmän skeduleri siis valitsee suoritettavaksi uuden prosessin. Käynnistettävä prosessi jatkaa täsmälleen samasta kohdasta mihin suoritus jäi silloin kun prosessi siirrettiin edellisen kerran pois running-tilasta.

    Kun useita prosesseja on yhtä aikaa ready-tilassa ja prosessoreja on vain yksi, on tehtävä valinta missä järjestyksessä CPU-aikaa jaetaan. Valinnan suorittavalla KJ:n osalla eli skedulerilla on ristiriitaisia tavoitteita. Interaktiivisessa käytössä CPU:lla olevia prosesseja vaihdeltava usein, jotta kaikkien prosessien vasteaika säilyy pienenä. Vasteaika (response time) tarkoittaa aikaa, joka kuluu esim. ikonin klikkaamisesta siihen kunnes kone suorittaa vaaditun toimenpiteen. Prosessin vaihtoon kuluu aikaa, eli liika prosessien vaihtelu syö CPU:n resursseja.

    On siis mahdollista, että prosessi siirretään pois suorituksesta sen takia, että prosessi on jo suorittanut KJ:n mielestä riittävän kauan. Mekanismi joka tekee tämän mahdolliseksi on ns. kellokeskeytys (engl timer interrupt). Kellokeskeskeytys on tasaisin aikavälein (esim. 1 millisekunnin välein) tapahtuva keskeytys, joka antaa KJ:lle hetkeksi suoritusvuoron kesken normaalin prosessin suorituksen. Kellokeskeytyksen ansioista KJ pääsee tarkastamaan onko suorituksessa oleva prosessi jo kuluttaunut kaiken sille varatun suoritusajan, eli aikaviipaleen (engl. time slice). Jos näin on tapahtunut, skeduleri valitsee uuden prosessin suoritukseen.

    On myös mahdollista, että joku korkean prioriteetin prosessi tulee kesken suorituksen runnable-tilaan, tällöin KJ siirtää suorittavan prosessin runnable-tilaan ja antaa suoritusvuoron korkeamman prioriteetin prosessille.

    6-1.c on yksinkertainen ohjelma, joka suorittaa looppia, jossa se tulostaa aika-ajoin ruudulle komentoriviparametrinaan saamaa numeroa:

    #define LIMIT 300000000
    #define PRINT 10000000
    
    int main(int argc, char *argv[]){
      int i, n;
    
      if ( argc<2 ) return 0;
    
      n = atoi(argv[1]);
      
      for ( i=0; i < LIMIT; i++){
        if ( i%PRINT==0 ) {
          fflush(stdout);
          printf("%d ",n);
        }
      }
    
      return 0;
    }
    
    Skripti run1 suorittaa ohjelmasta 6-1.c yhtäaikaa viisi instanssia. Skriptin ajaminen demonstroi, että KJ todellakin vuorottelee yhtä aikaa suorituksessa olevia prosesseja. Vaikka kaikille prosesseille annetaan suunnilleen saman verran suoritusaikaa, niin prosessien suoritusjärjestys näyttää käyttäjälle hiukan satunnaiselta. Ei siis kannata olettaa mitään siitä, missä järjestyksessä KJ prosesseja skeduloi.

    Interaktiivisessa järjestelmässä skeduloinnin tärkein kriteeri on pitää prosessien vasteajat pieninä. Tämän takia aikaviipale pyritään asettamaan melko pieneksi. Suorituksessa olevan prosessin vaihtaminen (englanniksi context switch) on kuitenkin melko kallis operaatio. Esim. Linuxissa prosessin vaihtoon aikaa kuluu (CPU:sta riippuen) noin 2-10 mikrosekuntia. Jos aikaviipale olisi esim. 10 millisekuntia, tarkoittaa tämä että CPU-ajasta noin 0.1% menisi prosessien vaihtoon. Tämä ei ole kovin paljoa, todellinen kustannus onkin muualla, liittyen välimuistiin ja muistinhallintaan, palataan asiaan myöhemmin.

    Aikaviipaleen pienennys siis aiheuttaa sen, että koneen suorittaman hyötyprosessoinnin määrä ei ole suurin mahdollinen. Hyötyprosessoinnin määrä saataisiin maksimoitua tekemällä aikaviipaleista erittäin pitkiä. Tämä kuitenkin aiheuttaisi sen että interaktiivisten ohjelmien vasteaika kasvaisi liian pitkäksi käyttäjän kannalta.

    Round robin ja prioriteetit

    Jokaisella prosessilla on prioriteetti, joka määrittää miten tärkeä prosessi on suoritusajan saamisen kannalta. Mitä korkeampi prioriteetti prosessilla on, sitä enemmän skeduleri antaa prosessille suoritusaikaa. Usein prioriteettitasoja kuvataan numeroilla siten, että mitä pienempi numero, sitä korkeammasta prioriteettitasosta on kysymys.

    Prosessien skedulointi tapahtuu yleensä siten, että jokaiselle prioriteettitasolle on oma skedulointijono. Suoritusvuoroon valitaan korkeimman prioriteettitason jonon kärjessä oleva prosessi.

    Yhden prioriteettitason jonon sisällä prosesseja suoritettaan ns. round-robin -periaatteen mukaan, eli suoritetaan aina jonon kärjessä olevaa prosessia niin kauan kunnes prosessi joko tekee blokkaavan systeemikutsun tai kuluttaa kaiken sille varatun suoritusajan. Kaiken suoritusajan kuluttanut prosessi siirretään jonon perälle ja jonon ensimmäinen prosessi valitaan suoritusvuoroon. Jos korkeimman tason jonossa ei ole yhtään prosessia, katsotaan toisen prioriteettitason jonoa jne. Seuraavassa kuva prioriteettijonoista:

    Linuxissa prosessin prioriteetti on sitä korkeampi mitä pienempi numero prioriteettiä kuvaa. Prosessin prioriteeti ei välttämättä säily koko aikaa samana. Prioriteetit siis vaihtuvat dynaamisesti suoritusajan kuluessa. Jos prosessi tekee paljon I/O-operaatioita, nostetaan sen prioriteettia. Jos prosessi kuluttaa aina aikaviipaleensa loppuun tekemättä I/O-operaatioita, lasketaan prosessin prioriteettia. Kun prosessin prioiriteetti muuttuu, siirretään prosessi uuden prioriteettinsä mukaiseen skedulointijonoon.

    nice

    Prosessin prioriteettiin voidaan vaikuttaa jonkin verran ns. nice-arvolla. Prosessi voi asettaa itselleen nice arvon, eli käytännössä arvon väliltä 1-19, joka huonontaa prosessin prioriteettiä. Jos prosessin normaali prioriteetti on esim. 20 ja prosessi suorittaa komennon nice(15), tulee prosessin prioriteetiksi 35.

    Ote man-sivulta:

           nice - change process priority
    
    SYNOPSIS
           #include < unistd.h>
    
           int nice(int inc);
    
    DESCRIPTION
           nice  adds  inc  to  the nice value for the calling pid.  (A large nice
           value means a low priority.)  Only the superuser may specify a negative
           increment, or priority increase.
    
    Seuraavassa ote ohjelmasta 6-2.c, joka asettaa komentoriviparametrina saadun nice-arvon ja suorittaa paljon aikaa kuluttavan loopin.
    #define LIMIT 1000000000
    int main(int argc, char *argv[]){
      int i, n;
      float f=1;
    
      if ( argc<2 ) return 0;
    
      n = atoi(argv[1]);
    
      if ( nice(n)==-1 ){
        perror("nice epäonnistui");
        return 0;
      }
    
      for ( i=0; i < LIMIT; i++){
        f = f/3.1234;
      }
      printf("prosessi %d nice %d valmis\n", getpid(), n);
    
      return 0;
    }
    
    Skripti run2 suorittaa ohjelmasta 6-2.c yhtäaikaa viisi eri instanssia eri nice-arvoilla. Seuraavassa tulostus, jossa ajetaan skripti (run & eli käynnistetään skriptiä suorittava prosessi taustalle) ja tutkitaan ps-komennolla prosessien prioriteettejä:
    [luuma@telinux1 vko6]$ run &
    [luuma@telinux1 vko6]$ ps l Uluuma
    F   UID   PID  PPID PRI  NI   VSZ  RSS WCHAN  STAT TTY        TIME COMMAND
    4 29188 23716 23713  15   0  4444 1332 schedu S    pts/105    0:00 -bash
    4 29188 23760 23758  15   0  4440 1332 schedu S    pts/115    0:00 -bash
    4 29188 14951 14949  15   0  4444 1320 wait4  S    pts/133    0:00 -bash
    4 29188 14993 14948  16   0  4444 1328 wait4  S    pts/134    0:00 -bash
    0 29188 15039 14951  15   0 11588 5656 schedu S    pts/133    0:00 emacs index.html
    0 29188 15118     1  39  19  1368  256 -      RN   pts/134    0:00 a.out 20
    0 29188 15119     1  39  15  1384  256 -      RN   pts/134    0:02 a.out 15
    0 29188 15120     1  35  10  1388  256 -      RN   pts/134    0:04 a.out 10
    0 29188 15121     1  30   5  1372  256 -      RN   pts/134    0:06 a.out 5
    0 29188 15122     1  25   0  1376  256 -      R    pts/134    0:07 a.out 0
    0 29188 15124 14993  21   0  3292 1220 -      R    pts/134    0:00 ps l Uluuma
    [luuma@telinux1 vko6]$ prosessi 15122 nice 0 valmis
    prosessi 15121 nice 5 valmis
    prosessi 15120 nice 10 valmis
    prosessi 15119 nice 15 valmis
    prosessi 15118 nice 20 valmis
    [luuma@telinux1 vko6]$
    
    Kenttä PRI kertoo prosessin oikean prioriteetin arvon (mitä korkeampi arvo, sitä matalampi prioriteetti), kenttä NI nice-arvon. Kuten näkyy korkeampi nice-arvo saa aikaan korkeamman prioriteettiarvon eli siis matalamman prioriteetin. Koska skriptin suorittamat prosessit ovat laskentapainoitteisia, on niiden prioriteetti kuitenkin melko alhainen verrattuna komentotulkkia ja emacsia suorittaviin prosesseihin joiden cpu:n kulutus on matalampi.

    ps-komennon tulosteen kenttä STAT kertoo prosessin tilan. R tarkoittaa running tai runnable, eli prosessorilla olevaa prosessia ei varsinaisesti erotella. S tarkoittaa sleeping ja Z zombie. RN tarkoittaa running tai runnable -tilassa olevaa matalamman prioriteetin prosessia. Muita mahdollisia merkintöjä ovat:

    SW	sleeping, swapattynä pois muistista
    SWN     matalan prioriteetin prosessi, sleeping ja poissa muistista
    SL	sleeping, lukittuna muistiin
    RL      running tai runnable, lukittuna muistiin
    T	pysäytetty tai debuggauksen alainen
    
    Komennon ps lisäksi suorittavia prosesseja voi tarkkailla konnolla top. man-sivulta (huom. koska kysessä bashin komento, on sivu sektiossa 1):
    TOP(1)                        Linux User's Manual                       TOP(1)
    
    NAME
           top - display top CPU processes
    
    SYNOPSIS
           top [-] [d delay] [p pid] [q] [c] [C] [S] [s] [i] [n iter] [b]
    
    DESCRIPTION
           top  provides  an  ongoing look at processor activity in real time.  It
           displays a listing of the most CPU-intensive tasks on the  system,  and
           can  provide  an  interactive interface for manipulating processes.  It
           can sort the tasks by CPU usage, memory usage and runtime.  Most fea-
           tures can either be selected by an interactive command or by specifying
           the  feature  in  the  personal  or system-wide configuration file. See
           below for more information.
    
    top siis näyttää systeemin statuksen prosessien suhteen siten, että tilanne päivitetään kerran sekunnissa. Haluttaessa topin näkyä voidaan päivittää milloin tahansa painamalla välilyöntiä. top-komennolla on suuri määrä komentoriviparametreja sekä sen toimintaan suoritusaikana vaikuttavia komentoja, ks. man-sivu. Seuraavassa top suoritettu skriptin run2 käynnistyksen jälkeen.
     20:19:59  up 10 days, 21:16, 53 users,  load average: 3,07, 1,30, 0,67
    439 processes: 433 sleeping, 6 running, 0 zombie, 0 stopped
    CPU states:  cpu    user    nice  system    irq  softirq  iowait    idle
               total   34,4%   64,1%    1,4%   0,0%     0,0%    0,0%    0,0%
               cpu00    2,8%   94,3%    2,8%   0,0%     0,0%    0,0%    0,0%
               cpu01   66,0%   33,9%    0,0%   0,0%     0,0%    0,0%    0,0%
    Mem:  1280620k av, 1255676k used,   24944k free,       0k shrd,   82264k buff
                        602952k actv,  114288k in_d,   18376k in_c
    Swap: 2105272k av,  244452k used, 1860820k free                  239460k cached
    
      PID USER     PRI  NI  SIZE  RSS SHARE STAT %CPU %MEM   TIME CPU COMMAND
    18352 luuma     25   0   256  256   212 R    33,1  0,0   0:01   1 6-2
    18351 luuma     29   5   252  252   212 R N  27,9  0,0   0:01   0 6-2
    18350 luuma     34  10   252  252   212 R N  25,0  0,0   0:01   1 6-2
    18349 luuma     39  15   256  256   212 R N   7,5  0,0   0:00   1 6-2
    18348 luuma     39  19   256  256   212 R N   3,7  0,0   0:00   1 6-2
    18353 luuma     20   0  1308 1308   760 R     2,3  0,1   0:00   0 top
        1 root      15   0   496  460   436 S     0,0  0,0   0:15   1 init
        2 root      RT   0     0    0     0 SW    0,0  0,0   0:00   0 migration/0
        3 root      RT   0     0    0     0 SW    0,0  0,0   0:00   1 migration/1
        4 root      15   0     0    0     0 SW    0,0  0,0   0:00   1 keventd
        5 root      34  19     0    0     0 SWN   0,0  0,0   0:00   0 ksoftirqd/0
        6 root      34  19     0    0     0 SWN   0,0  0,0   0:00   1 ksoftirqd/1
        9 root      15   0     0    0     0 SW    0,0  0,0   0:00   0 bdflush