Helsingin yliopisto Tietojenkäsittelytieteen laitos
 

Tietojenkäsittelytieteen laitos

Tietoa laitoksesta:

 
Helsingin yliopisto / Tietojenkäsittelytieteen laitos / Copyright © 2001 Jan Lindström. Tämän oppimateriaalin käyttö on sallittu vain yksityishenkilöille opiskelutarkoituksissa. Materiaalin käyttö muihin tarkoituksiin, kuten kaupallisilla tai muilla kursseilla, on kielletty.

4. Prosessinhallinta


Sisältö:


4 Prosessinhallinta

Prosessinhallinnan systeemikutsut:


4.1 Prosession luonti

Prosessi luodaan funktiolla pid_t *fork(void);, joka palauttaa kutsuvalle prosessille lapsiprosessin numeron ja lapsiprosessille 0. fork()-kutsun tuloksena syntyy uusi prosessi, joka on 'klooni' äitiprosessista.

Kummallakin on kuitenkin:

fork()-kutsun jälkeen ei voi olla varma siitä kumpi prosessi (äiti vai lapsi) jatkaa aiemmin suoritustaan. Jos järjestys tärkeää, on ohjelmoitava itse synkronointi ( Esim 1.

#include    < sys/types.h >                                             
#include    < unistd.h >

int  glob = 6;                      /* external variable in initialized data */
char buf[] = "a write to stdout\n";

int
main(void)
{
      int      var;                       /* automatic variable on the stack */
      pid_t pid;

      var = 88;
      if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
            perror("write error");
      printf("before fork\n");                      /* we don't flush stdout */

      if ( (pid = fork()) < 0)
            perror("fork error");
      else if (pid == 0) {                                          /* child */
            glob++;                                      /* modify variables */
            var++;
      } else
          sleep(2);                                                /* parent */

      printf("pid=%d,glob=%d,var=%d\n",getpid(), glob, var);
      exit(0);
} 

Lapsiprosessi luodaan, kun:

Lapsi perii äidiltä kaikki avoimet tiedostokuvaajat. Sekä äiti että lapsi käyttävät yhteistä avoimet tiedostot taulun alkiota niillä on yhteinen luku / kirjoituspositio.


4.2 Prosession päättyminen

Prosessin suoritus päättyy normaalisti, kun suoritetaan:

exit() kutsuu funktiolla atexit() rekisteröityjä funktioita ja purkaa stdio:n puskurit (vrt. funktio flush()). _exit() hoitelee Unix-spesifiset lopputoimet Prosessin suoritus voi päättyä myös 'epänormaalisti', kun kutsutaan funktiota abort() tai prosessi saa signaalin, johon se ei varautunut tai ei voi varautua. Epänormaaleissa päättymisissä generoi ydin paluuarvon.

Unix-spesifisiin lopputoimiin kuuluu tiedostojen sulkeminen, muistitilan vapauttaminen sekä äitiprosessin signalointi, mutta prosessinkuvaaja jää vielä olemaan ("zombie"). Koska paluuarvon välittäminen äitiprosessille ja laskutustietojen kokoaminen on vielä kesken.

Jos äitiprosessi on päättynyt ennen lapsiprosessia, merkitsee ydin zombien äidiksi prosessin 1 (init). Se kokoaa laskutustiedot ja vapauttaa prosessinkuvaajan.

Kun prosessi päättyy, saa äiti aina signaalin SIGCHLD. Oletusarvo on, että äiti ei välitä tästä signaalista. Äiti voi pysähtyä odottamaan lapsen päättymistä funktioon wait() tai waitpid():

#include < sys/types.h >
#include < sys/wait.h >
      
pid_t wait(int *status);         
pid_t waitpid(pid_t pid, int *status, int options);
                                  Palauttaa: päättyneen prosessin pid, 
                      parametrissa statloc on päättyneen lapsen status 
Jos lapsiprosessi on jo päättynyt, pääsee äitiprosessi heti jatkamaan. Funktiolla waitpid() voi määrätä odotettavaksi jonkin tietyn prosessin päättymistä, kun taas funktiolla wait() odotetaan minkä tahansa lapsen päättymistä.


4.3 Prosessin koodin vaihto

Prosessi vaihtaa suoritettavaa koodia funktiolla exec(). Siitä on kuusi erilaista muotoa, jotka eroavat komentoriviargumenttien ja ympäristömuuttujien välityksessä. Koodia etsitään annetun polkunimen perusteella funktioissa execl(), execv(), execle(), execve() tai tiedostonimen perusteella ympäristömuuttujassa PATH luetelluista hakemistoista fuktioissa execlp() tai execvp();.

Koodille voi välittää komentoriviargumentteja joko listana (execl()) tai vektorina (execv()). Koodille voi välittää edellisten lisäksi myös haluamansa ympäristömuuttujat aina vektorina execle() tai execve() tai äidin ympäristömuuttujat periytyvät lapselle sellaisenaan environ-muuttujasta.


#include < unistd.h >
      
int execl(const char *pathname, const char *arg0, ... /* NULL */);
int execv(const char *pathname, char *const argv[]);

int execle(const char *pathname, const char *arg0, ... /* NULL, char *const envp[] */);
int execve(const char *pathname, char *const argv[], char *const envp[]);

int execlp(const char *filename, const char *arg0, ... /* NULL */)
int execvp(const char *filename, char *const argv[]);


#include    < sys/types.h >                                            
#include    < sys/wait.h >
#include    < unistd.h >

char  *env_init[] = { "USER=unknown", "PATH=/tmp", NULL };

int
main(void)
{
      pid_t pid;

      if ( (pid = fork()) < 0)
             perror("fork error");
      else if (pid == 0) {          /* specify pathname, specify environment */
             if (execle("/bin",
                        "echo", "myarg1", "MY ARG2", (char *) 0,
                         env_init) < 0)
               perror("execle error");
      }
      if (waitpid(pid, NULL, 0) < 0)
          perror("wait error");

      if ( (pid = fork()) < 0)
            perror("fork error");
      else if (pid == 0) {          /* specify filename, inherit environment */
              if (execlp("echo",
                           "echo", "only 1 arg", (char *) 0) < 0)
                   perror("execlp error");
      }
      exit(0);
}


4.4 Prosessin ominaisuudet



4.5 Prosessin tunnistus

Prosessin oikeudet määräävät mitä tiedostoja prosessi saa käyttää ja mihin muihin prosesseihin prosessi voi vaikuttaa. Oikeudet tarkistetaan prosessin euid ja egid tietojen perusteella (e = effective). Prosessi voi toimia sen käynnistäjän oikeuksin, jolloin euid = uid, käynnistäjän ryhmän oikeuksin, jolloin egid = gid (jokin ryhmä johon käyttäjä kuuluu) tai exec()-kutsussa saaduin oikeuksin (setuid, setgid), jolloin euid = kooditiedoston uid ja egid = kooditiedoston gid.

Todellinen (real) käyttäjä uid ja todelliset ryhmät gid ja groups saadaan istuntoa käynnistettäessä salasanatiedostosta. Vain root voi muuttaa näitä. Omistaja- ja ryhmänumeroita voi muuttaa tietyin edellytyksin funktioilla:


#include 
#include 
  
int setuid(uid_t uid);
int setgid(gid_t gid);
  
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);
  
int seteuid(uid_t uid);
int setegid(gid_t gid);

root saa tietenkin käyttää kaikkia uid ja gid arvoja. Prosessin todellinen omistaja voi aina asettaa funktioilla setuid() ja setgid(). Funktiot seteuid() ja setegid() toimivat tavallisilla käyttäjillä kuten setuid() ja setgid(). rootin käytössä setuid() asettaa aina:

Kun taas seteuid() asettaa vain euid = annettu uid. Ryhmäjutut vastaavasti.


4.6 Prosessin kuluttama aika

Prosessi voi kysellä kuluttamaansa reaaliaikaa ja CPU- aikaa funktiolla times():


#include < sys/times.h >
  
struct tms {
     clock_t tms_utime;  käyttäjätilassa
     clock_t tms_stime;  etuoikeutetussa tilassa
     clock_t tms_cutime; summa lasten ajoista
     clock_t tms_cutime; summa lasten ajoista
};
  
clock_t times(struct tms *buf);     
                                      Palauttaa: reaaliaika tiksauksina


Esimerkki (Esim 2 )

#include    < stdio.h >
#include    < sys/times.h >                                           
#include    < unistd.h >

static void pr_times(clock_t, struct tms *, struct tms *);
static void do_cmd(char *);

int main(int argc, char *argv[])
{     int         i;

      for (i = 1; i < argc; i++)
            do_cmd(argv[i]);               /* once for each command-line arg */
      exit(0);
}

static void do_cmd(char *cmd)                  /* execute and time the "cmd" */
{
      struct tms  tmsstart, tmsend;
      clock_t     start, end;
      int         status;

      fprintf(stderr, "\ncommand: %s\n", cmd);

      if ( (start = times(&tmsstart)) == -1)              /* starting values */
            perror("times error");

      if ( (status = system(cmd)) < 0)                    /* execute command */
            perror("system() error");

      if ( (end = times(&tmsend)) == -1)              /* ending values */
            perror("times error");

      pr_times(end-start, &tmsstart, &tmsend);
      exit(status);
} 

static void pr_times(clock_t real,  struct tms *tmsstart, struct tms *tmsend)
{  
    static long clktck = 0;

    if (clktck == 0)              /* fetch clock ticks per second first time */
         if ( (clktck = sysconf(_SC_CLK_TCK)) < 0)
           perror("sysconf error");

    fprintf(stderr, "  real:  %7.2f\n", real / (double) clktck);
    fprintf(stderr, "  user:  %7.2f\n",
         (tmsend->tms_utime - tmsstart->tms_utime) / (double) clktck);
    fprintf(stderr, "  sys:   %7.2f\n",
         (tmsend->tms_stime - tmsstart->tms_stime) / (double) clktck);
    fprintf(stderr, "  child user:  %7.2f\n",
         (tmsend->tms_cutime - tmsstart->tms_cutime) / (double) clktck);
    fprintf(stderr, "  child sys:   %7.2f\n",
         (tmsend->tms_cstime - tmsstart->tms_cstime) / (double) clktck);
}


4.7 Prosessien sukulais- ja omistussuhteet

Init-prosessi on kaikkien prosessin alku ja sen pid == 1. 'Adoptoi' lapsipro- sessin, jos sen oma äiti on kuollut.

  • Äitiprosessi saa fork()-kutsussa lapsiprosessin numeron ja voi odottaa lapsen päättymistä funktioissa wait() ja waitpid(), ja saa sen status-tiedot. Saa signaalin SIGCHLD, kun lapsiprosessi päättyy. Kokoaa laskutustiedot kuolleen lapsen, zombien, prosessikuvaajasta
  • Lapsiprosessi perii syntyessään perustiedot äitiprosessin kuvaajasta. Mm. äitiprosessin tunnus ppid on kirjattu lapsiprosessin kuvaajaan. Kuuluu äitiprosessin kanssa samaan prosessiryhmään ja istuntoon.

    Jokainen prosessi kuuluu johonkin prosessiryhmään. Rymän tunnisteena on ryhmän johtoprosessin pid. Vaikka johtoprosessi päättyisikin, jää prosessiryhmä voimaan. Prosessiryhmän numeroa voi kysyä funktiolla pid_t getpgrp(void); . Prosessi voi asettaa funktiolla pid_t setpgid(pid_t pid, pid_t pgid); itsensä tai lapsiprosessinsa prosessiryhmän tai voi ryhtyä itse uuden prosessiryhmän johtoprosessiksi. Jos parametrit ovat samoja, syntyy uusi prosessiryhmä. Jos pid = 0 tai pgid = 0, käytetään prosessin omaa numeroa. Komentotulkin tasolla voi tappaa koko ryhmän tappamalla työn johtoprosessin, esim:

      
          $ kill %1
    
    Istunto tarkoittaa joukkoa käynnistettyjä prosesseja. Siihen liittyy läheisesti töiden hallinta (jobs(1), fg(1), bg(1), kill(1)). Istunto koostuu yhdestä tai useammasta prosessiryhmästä.

    Prosessi voi julistautua uuden istunnon johtajaksi funktiolla pid_t setsid(void); setsid() perustaa uuden prosessiryhmän, ja kutsunut prosessi on sen johtoprosessi. Samalla menetetään mahdollinen kontrollipääte. Tavallinen käyttötapa: äiti luo lapsiprosessin, kuolee itse pois ja lapsiprosessi julistautuu istunnon johtoprosessiksi.

    Istuntoon voi liittyä kontrollipääte, josta luetaan syötteitä ja jonne voidaan tulostaa. Vain yksi prosessiryhmä voi olla kerrallaan edustalla, ts. voi ottaa vastaan syötteitä näppäimistöltä ja tulostaa näytölle. Muut prosessiryhmät ovat taustalla. Kaikki istunnon prosessiryhmät voivat kuitenkin käyttää istuntoon liittyvää kontrollipäätettä suoraan sen tiedotonimen /dev/tty kautta. Edustaprosessi voi asettaa / selvittää prosessiryhmän funktioilla tcgetpgrp() ja tcsetpgrp():

    #include 
    #include 
      
    pid_t tcgetpgrp(int filedes);
                                   Palauttaa: edustaprosessin prosessiryhmä
    int tcsetpgrp(int filedes, pid_t pgrpid);
    
    

    Ydin tarvitsee näitä mm. selvittääkseen mille prosessille syötteet annetaan ja mille prosessille lähetetään mahdolliset signaalit. filedes osoittaa kontrollipäätteeseen.

    Kontrollipäätemäärittelyjä ei tarvitse yleensä murehtia, sillä ne määrittyy automaattisesti, kun järjestelmään logataan sisään ja kun prosesseja käynnistetään komentotulkista (töidenhallinta).

    Jos taustalla oleva työ haluaa tulostaa näytölle tai lukea näppäimistöltä, se jää odottamaan edustalle pääsyä.



    Jan Lindström (Jan.Lindstrom@cs.Helsinki.FI)