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.

5. Prosessien välinen vuorovaikutus


Sisältö:


5 Prosessin välinen vuorovaikutus

Prosessit vaikuttavat toisiinsa usealla tavalla:


5.1 Putket

Putki luodaan funktiolla int pipe(int fd[2]);. Putki on erikoistiedosto, jolla on kaksi tiedostokuvaajaa:

pipe()- ja fork()-kutsujen avulla syntyy tiedonsiirtokanava äiti- ja lapsiprosessin välille, sillä myös putken tiedostokuvaajat periytyvät lapsiprosessille. Putkia voi käyttää vain 'saman perheen kesken' Yhtä putkea järkevää käyttää vain yhteen suuntaan. Tarpeettomat putken päät suljetaan sekä mamma- että lapsiprosessissa. Kaksisuuntaiseen kommunikointiin tarvitaan kaksi putkea.

#include < stdio.h >                                               
#define N 3
   
int add_vector(int v[])
{
     int i, sum = 0;
  
     for (i=0; i < N; i++) sum += v[i];
     return sum;
}
  
int main()
{
     int a[N][N] = {{1,1,1},{2, 2, 2},{3, 3, 3}};
     int i, row_sum, sum = 0, fd[2];

     pipe(fd);
 
     for (i = 0; i < N; i++)
       if (fork() == 0) {                                          /* pennut */
         close(fd[0]);
           row_sum = add_vector(a[i]);
           write(fd[1],&row_sum,sizeof(int));
           exit(0);
       }
 
   close(fd[1]);                                                    /* mamma */
     for (i=0; i < N; i++) {
       read(fd[0],&row_sum,sizeof(int));
       printf("   Row sum = %d\n",row_sum);
       sum += row_sum;
     }
  
     printf("Sum of the array = %d\n",sum);
}

Putken on rajoitettu tilavuus. Data viitattavissa indeksisolmun suorilla lohkonumeroilla.

    
limits.h:
     PIPE_MAX                       (esim. 5120 B)
     PIPE_BUF                       (esim. 5120 B)
     _POSIX_PIPE_BUF                (minimi 512 tavua)

Synkronointi ja odotus:

Funktio read, kun write-pää suljettu, lukee putkessa olevat tavut ja seuraava read palauttaa EOF. Funktio write, kun read-pää on suljettu, prosessi saa signaalin SIGPIPE ja errno = EPIPE.

open()- tai fcntl()-kutsussa annettu lipuke O_NONBLOCK aiheuttaa sen, että read() ja write() siirtävät minkä voivat, mutta eivät jää odottamaan. Jos I/O:ta ei voi tehdä (tai jatkaa) odottamatta, read() ja write() palauttavat paluuarvon -1 ja errno == EWOULDBLOCK.

fcntl()- tai ioctl()-kutsussa annettu lipuke O_ASYNC aiheuttaa sen, että prosessi saa signaalin SIGIO (BSD) tai SIGPOLL (sysV), kun tiedostokuvaajaa voi käsitellä s.e read / write ei aiheuta odotusta. Jos useita siirtokanavia, selvitetään signaalin jälkeen rutiineilla select() tai poll() mitä kuvaajaa voi käsitellä ilman odotusta.


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

#define  DEF_PAGER "/usr/bin/more"                  /* default pager program */

int main(int argc, char *argv[])
{
    int  n, fd[2];
    pid_t      pid;
    char line[MAXLINE], *pager, *argv0;
    FILE *fp;

    if (argc != 2)
         perror("usage: a.out ");
    if ( (fp = fopen(argv[1], "r")) == NULL)
         perror("can't open %s", argv[1]);

    if (pipe(fd) < 0)
         perror("pipe error");

    if ( (pid = fork()) < 0)
         perror("fork error");
    else if (pid > 0) {                                            
    /* parent */
         close(fd[0]);                           /* close read end */
               /* parent copies argv[1] to pipe */
         while (fgets(line, MAXLINE, fp) != NULL) {
               n = strlen(line);
               if (write(fd[1], line, n) != n)
                    perror("write error to pipe");
         }
         if (ferror(fp))
               perror("fgets error");

         close(fd[1]);                         /* close write end for reader */
         if (waitpid(pid, NULL, 0) < 0)
               perror("waitpid error");
         exit(0);

    } else {   /* child */
         close(fd[1]);   /* close write end */
         if (fd[0] != STDIN_FILENO) {
               if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO)
                    perror("dup2 error to stdin");
               close(fd[0]);                     /* don't need this after dup2 */
         }

         if ( (pager = getenv("PAGER")) == NULL)
               pager = DEF_PAGER;
         if ( (argv0 = strrchr(pager, '/')) != NULL)
               argv0++;  /* step past rightmost slash */
         else
               argv0 = pager;                    /* no slash in pager */

         if (execl(pager, argv0, (char *) 0) < 0)
               perror("execl error for %s", pager);
    }
}

Stdio-kirjastossa on funktio popen(), jolla myös voi luoda putken ja funktio pclose(), jolla putki suljetaan.

#include 

FILE * popen(const char *cmdstring, const char *type); 
                    Palauttaa: tiedosto-osoittimen, tai NULL, jos virhe
int pclose(FILE *fp);
            Palauttaa suoritetun komennon paluuarvon, tai -1, jos virhe

popen() luo putken, tekee lapsiprosessin, ja suorituttaa sillä komentorivin cmdstring. Jos type = "r", lapsiprosessi ohjaa komentorivin tulosteet putkeen, josta ne on luettavissa. Jos type = "w", lapsiprosessi lukee komentorivin syötteet putkesta.


5.2 Nimetyt putket (FIFO)

Nimettyä putkea edustaa tiedostojärjestelmässä nimetty tiedosto. Se on käyttöoikeuksien rajoissa käytettävissä myös muillakin prosesseilla. Nimettyä putkea voi käyttää 'ei-sukulaisprosessien' välisessä kommunikoinnissa. Kaksisuuntaiseen kommunikointiin tarvitaan kaksi nimettyä putkea. Nimetty putki luodaan tavallisen tiedoston luonnin tapaan, eli kerrotaan tiedostonimi ja annetaan käyttöoikeudet, mutta funktiolla int mkfifo(const char *pathname, mode_t mode);. Kun nimetty putki on luotu, on se vielä erikseen avattava funktiolla open() käyttöä varten. Tämän jälkeen sen käyttö ei poikkea muiden tiedostojen käytöstä (read(), write() yms.). Nimetty putki hävitetään funktiolla unlink() tai remove().

Prosessi A:

  mkfifo(/tmp/putki,S_IRWXU|S_RGRP|S_ROTH);

  fd = open("/tmp/putki",O_WRONLY);
  write(fd,"Halloota ",9);
  write(fd,"siellä!",7);
  close(fd);
  unlink("/tmp/putki");

Prosessi B:

  fd = open("/tmp/putki",O_RDONLY);
  n = read(fd,buf,BUFSIZE);
  printf("%s\n"buf);

Synkronointi ja odotus:

  • Funktio read(), kun write-pää suljettu, lukee putkessa olevat tavut ja seuraava read() palauttaa EOF. Funktiota write() seuraa, kun read-pää suljettu, signaali SIGPIPE ja errno = EPIPE. read() palauttaa 0 (eli EOF), vasta kun putki ei ole enää kenelläkään auki kirjoittamista varten. Estymätöntä ja asynkronista I/O:ta käytettäessä toiminta kuten tavallisilla putkilla.


    5.3 Signaalit

    Signaali on ohjelmallinen keskeytys, joka virittää prosessinkuvaajassa signaalilipukkeen. Signaali voi tulla asynkronisesti KJ:ltä, toiselta prosessilta tai prosessilta itseltään. Kaikille signaaleille on määritelty oletustoiminto, esim. prosessin päättyminen tai että signaalia ei huomioida mitenkään. Prosessi voi asettaa lähes kaikille signaaleille myös oman käsittelyfunktion. Poikkeus: signaali SIGKILL, SIGSTOP.

    #define SIGHUP      1    hangup 
    #define SIGINT      2    interrupt (rubout) 
    #define SIGQUIT     3    quit (ASCII FS) 
    #define SIGILL      4    illegal instruction (not reset when caught) 
    #define SIGTRAP     5    trace trap (not reset when caught) 
    #define SIGIOT      6    IOT instruction 
    #define SIGABRT     6    used by abort, replace SIGIOT in the future 
    #define SIGEMT      7    EMT instruction 
    #define SIGFPE      8    floating point exception 
    #define SIGKILL     9    kill (cannot be caught or ignored) 
    #define SIGBUS      10   bus error 
    #define SIGSEGV     11   segmentation violation 
    #define SIGSYS      12   bad argument to system call 
    #define SIGPIPE     13   write on a pipe with no one to read it 
    #define SIGALRM     14   alarm clock 
    #define SIGTERM     15   software termination signal from kill 
    #define SIGUSR1     16   user defined signal 1 
    #define SIGUSR2     17   user defined signal 2 
    #define SIGCLD      18   child status change 
    #define SIGCHLD     18   child status change alias (POSIX) 
    #define SIGPWR      19   power-fail restart 
    #define SIGWINCH    20   window size change 
    #define SIGURG      21   urgent socket condition 
    #define SIGPOLL     22   pollable event occured 
    #define SIGIO SIGPOLL    socket I/O possible (SIGPOLL alias) 
    #define SIGSTOP     23   stop (cannot be caught or ignored) 
    #define SIGTSTP     24   user stop requested from tty 
    #define SIGCONT     25   stopped process has been continued 
    #define SIGTTIN     26   background tty read attempted 
    #define SIGTTOU     27   background tty write attempted 
    #define SIGVTALRM   28   virtual timer expired 
    #define SIGPROF     29   profiling timer expired 
    #define SIGXCPU     30   exceeded cpu limit 
    #define SIGXFSZ     31   exceeded file size limit 
    #define SIGWAITING  32   process's lwps are blocked 
    #define SIGLWP      33   special signal used by thread library 
    #define SIGFREEZE   34   special signal used by CPR 
    #define SIGTHAW     35   special signal used by CPR 
    #define SIGCANCEL   36   thread cancellation signal  
    
    /* insert new signals here, and move _SIGRTM* appropriately */
    
    #define _SIGRTMIN   37   first (highest-prior) realtime signal
    #define _SIGRTMAX   44   last (lowest-prior) realtime signal
    #define SIGRTMIN _sysconf(_SC_SIGRT_MIN)       first realtime signal
    #define SIGRTMAX _sysconf(_SC_SIGRT_MAX)       last realtime signal
    
    #define NSIG        45    valid signals range from 1 to NSIG-1
    #define MAXSIG      44    size of u_signal[], NSIG -1 <= MAXSIG
    

    Prosessi voi generoida itselleen signaalin funktiolla int raise(int signo);. Funktiolla int kill(pid_t pid, int signo); se voi lähettää signaalin myös toisille prosesseille, jolloin:

    • pid > 0 : prosessille pid
    • pid == 0 : samaan prosessiryhmään kuuluville
    • pid < 0 : prosessiryhmään |pid| kuuluville prosesseille
    • pid == -1 : broadcast ei-systeemiprosesseille (ei POSIXissa)

    root voi lähettää signaaleja rajoituksetta. Muut voi lähettää signaaleja vain prosesseille, joiden uid tai euid on sama kuin itsellä. Signaalin voi lähettää myös komentoriviltä:

    
        $ kill -KILL 1422       
    

    Prosessi voi tilata signaalin SIGALRM funktiolla unsigned int alarm(unsigned int seconds);, joka palauttaa edellisestä ajastuksesta jäljellä oleva aika. Jos prosessi ei hylkää tai käsittele tätä tilaamaansa signaalia, sen suoritus päättyy. alarm()-kutsu kumoaa edellisen tilauksen. Signaalia voi pysähtyä odottamaan funktiolla int pause(void);. Prosessi herää, kun se saa minkä tahansa signaalin. Signaali käsitellään normaalisti käsittelijäasetusten mukaan. Funktio abort() lähettää prosessille signaalin void abort(void); Sitä ei voi hylätä, mutta sille voi asettaa oman käsittelijän lopputoimet.

    #include    < signal.h >                                                
    #include    < unistd.h >
    
    static void sig_alrm(int signo)
    {
        return;   
    }
    
     int main(void)
    {
        int n;
        char    line[MAXLINE];
    
        if (signal(SIGALRM, sig_alrm) == SIG_ERR)
            perror("signal(SIGALRM) error");
        alarm(10);
        if ( (n = read(STDIN_FILENO, line, MAXLINE)) < 0)
            perror("read error");
        alarm(0);
    
        write(STDOUT_FILENO, line, n);
    
        exit(0);
    }
    

    Signaali voi olla:

    • estetty (masked, blocked), jolloin lipuke prosessinkuvaajassa virittyy, mutta signaalia ei toimiteta prosessille (signal pending).
    • sallittu (enabled), jolloin lipuke virittyy ja käsittely mahd. pian

    Kun signaali käsitellään, lipuke laukeaa ja signaali voidaan:

    • unohtaa (SIG_IGN)
    • hoitaa oletuksen mukaisesti (SIG_DFL),
    • esimerkiksi unohtaa, lopettaa prosessin, pysäyttää prosessin
    • käsitellä prosessin omassa funktiossa

    Käsittelijäfunktio asetetaan funktiolla sigaction():

    #include < signal.h >
      
    struct sigaction {
        void (*sa_handler)();          käsittelyfunktio
        sigset_t sa_mask;              estomaski
        int sa_flag;                        lipukkeet
    }
      
    int sigaction(int signo, const struct sigaction
    *act, struct sigaction *oact);
    
    signo = signaali, jonka käsittelijää kysellään tai jolle
            asetetaan käsittelijä.
    
    act   = uudet asetettavat arvot tai NULL, jos kysytään vanhoja asetuksia
    
    oact  = vanhat palautettavat arvot tai NULL, 
             jos vanhoja arvoja ei haluta ottaa talteen
    
    sa_handler
        SIG_IGN     unohda signaali
        SIG_DFL     toimi oletuksen mukaan
        funktio()   kutsuttava käsittelijäfunktio
    
    sa_mask
            Käsittelyn ajaksi estetään aiemmin estetyt signaalit
            signaali signo ja signaalit sa_mask
    
    sa_flags
        SA_RESTART  
            signaali saa katkaista systeemikutsuja
        SA_RESETHAND    
            signaali käsitellään asetetussa käsittelijässä,
            jonka jälkeen sa_hander <- SIG_DFL
        SA_ONSTACK  
            käsittelijäfunktio käyttää omaa pinoa
        SA_NOCHLDSTOP   
            signaali SIGCHLD lähetetään äitiprosessille
            vain prosessin päättyessä, ei sen pysähtyessä
    

    Käsittelijän voi asettaa myös yksinkertaisemmalla funktiolla void (*sigset(int signo, void (*func)(int)))(int); tai void (*signal(int signo, void (*func)(int)))(int);. Molemmat palauttavat edellisen käsittelijän, tai virhetilanteessa SIG_ERR.

    #include    < signal.h >                                               
    #include    < unistd.h >
       
    static void  sig_usr(int signo) 
    {
        if (signo == SIGUSR1)
            printf("received SIGUSR1\n");
        else if (signo == SIGUSR2)
            printf("received SIGUSR2\n");
        else
            perror("received signal %d\n",signo);
        return;
    }
      
    int  main(void)
    {
        if (signal(SIGUSR1, sig_usr) == SIG_ERR)
            perror("can't catch SIGUSR1");
        if (signal(SIGUSR2, sig_usr) == SIG_ERR)
            perror("can't catch SIGUSR2");
    
        for ( ; ; )     
              pause();
    }
    

    Signaali voi katkaista hitaan systeemikutsun suorituksen. Hitaita systeemikutsuja:

    • read(), write(): 'hitaille' tiedostoille (pääte, putki, verkkoyhteydet). Levytiedostoa käsittelevä kutsu ei voi katketa.
    • wait(), pause(): voivat katketa
    • sleep(): voi katketa (sysV), ei voi katketa (BSD)
    • Eräät ioctl() kutsut
    • Eräät prosessien välisen kommunikoinnin systeemikutsut

    Miten huomataan, jos katkeaa? Kutsu palauttaa -1 ja errno==EINTR read() tai write() kertovat käsitelleensä vähemmän tavuja kuin pyydettiin.

    again:
        if ((n=read(fd,buf,SIZE))!= n) {
            if (errno == EINTR)
                goto again;
        }
    

    Jotta prosessi jatkaisi automaattisesti keskeytynyttä systeemikutsua, voi funktiossa sigaction() asettaa lipukkeen SA_RESTART.

    Signaali voi tulla minkä tahansa kahden käskyn välillä. Kesken jääneen funktion uudelleenkutsuminen voi aiheuttaa ongelmia, jos funktio ei ole vapaakäyntinen (re-entrant), ts. ellei se ole toteutettu siten, että useita sen suorituksia voi olla meneillään yhtä aikaa.

    Ohjelman omia muuttujia ja omia vapaakäyntisiä funktioita saa käyttää vapaasti käsittelijässä.

    Ongelmia aiheuttavat:

    • malloc() ja free() sekä kaikki niitä käyttävät funktiot
    • funktiot, jotka tallettavat tuloksen staattiseen muistitilaan (esim. getpwent())
    • stdio-kirjastoon kuuluvat funktiot

    Ohje: mahdollisimman vähän koodia käsittelijään. Vain lipuke pystyyn ja käsittely 'normaalissa' koodissa.

    Signaalijoukko on bittimaski, jonka avulla voi estää tai sallia yhdellä kertaa suuren joukon signaaleja. Sen käsittelyä varten on funktiot

    #include < signal.h >
     
    int sigemptyset(sigset_t *set);
    int sigfillset(sigset_t *set);
    int sigaddset(sigset_t *set, int signo);
    int sigdelset(sigset_t *set, int signo);
    int sigismember(sigset *set, int signo);  
    
    
    sigemptyset() luo tyhjän maski 
    sigfillset()  luo maskin, jossa kaikki signaalit 
    sigaddset()   lisää uuden signaalin maskiin 
    sigdelset()   poistaa signaalin maskista 
    sigismember() tarkistaa onko signaali asetettu maskiin 

    Prosessin kuvaajassa on bittimaski, josta käy ilmi mitkä signaalit on toimitettava prosessille käsiteltäviksi ja mitkä signaalit vain viritetään (ts. merkitään tulleiksi). Maskin voi kysyä ja asettaa funktiolla int sicprocmask(int how, const sigset_t set, sigset_t *oldset);

    Jos oldset != NULL niin
        oldset = nykyinen maski
     
    Jos set != NULL niin
        jos how == SIG_BLOCK niin
            maski = maski + set
        jos how == SIG_UNBLOCK niin
            maski = maski - set
        jos how == SIG_SETMASK niin
            maski = set
    

    Jos uusi asetettava maski sallii signaalin, joka on virittynyt, se käsitellään samantien.

    #include                                                  
    #include    
    #include    "ourhdr.h"
    
    void pr_mask(const char *str)
    {
        sigset_t    sigset;
        int         errno_save;
    
        errno_save = errno;               /* we can be called by signal handlers */
        if (sigprocmask(0, NULL, &sigset) < 0)
            perror("sigprocmask error");
    
        printf("%s", str);
        if (sigismember(&sigset, SIGINT))   printf("SIGINT ");
        if (sigismember(&sigset, SIGQUIT))  printf("SIGQUIT ");
        if (sigismember(&sigset, SIGUSR1))  printf("SIGUSR1 ");
        if (sigismember(&sigset, SIGALRM))  printf("SIGALRM ");
        
        /* remaining signals can go here */
        
         printf("\n");
         errno = errno_save;
    }
    

    Viritettyjä signaaleja voi kysellä funktiolla int sigpending(sigset_t *set);

    #include    < signal.h >                                              
    #include    < unistd.h >
        
    static void sig_quit(int);
     
    int main(void)
    {   sigset_t    newmask, oldmask, pendmask;
     
        if (signal(SIGQUIT, sig_quit) == SIG_ERR)
            perror("can't catch SIGQUIT");
     
        sigemptyset(&newmask);
        sigaddset(&newmask, SIGQUIT);
     
        if (sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0)
            perror("SIG_BLOCK error");
    
        sleep(5);                            /* SIGQUIT here will remain pending */
     
        if (sigpending(&pendmask) < 0)      perror("sigpending error");
    
        if (sigismember(&pendmask, SIGQUIT))
            printf("\nSIGQUIT pending\n");
     
        if (sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0)
            perror("SIG_SETMASK error");
        printf("SIGQUIT unblocked\n");
       
        sleep(5);                  /* SIGQUIT here will terminate with core file */
     
        exit(0);
    }
    
    
    static void sig_quit(int signo)                             
    {   
         printf("caught SIGQUIT\n");
    
        if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
            perror("can't reset SIGQUIT");
    
         return;
    
    }
    

    HUOM prosessi sai tiedon siitä, että signaali on tullut, mutta ei sitä kuinka monta kertaa se on tullut. Funktion pause() käyttö ei aina riitä:

    
      sigset_t newmask, oldmask;
    
      sigemptyset(&newmask);
      sigaddset(&newmask,SIGINT);
    
      if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)
          err_sys("SIG_BLOCK error");
    
        /* kriittinen osa koodia */
    
      if (sigprocmask(SIG_SETMASK,&oldmask,NULL) <0)
          err_sys("SIG_SETMASK error");
    
      pause();                                                                     
    
        /* jatka prosessointia */
    

    Vanhan maskin palauttamisen ja pause()-kutsun välissä tuleva SIGINT jää huomaamatta. Funktiossa sigsuspend() signaalimaskin asettaminen ja prosessin nukuttaminen tapahtuvat atomisesti:

    #include < signal.h >
     
    int sigsuspend(const sigset_t *sigmask);  
                 Palauttaa: -1 ja errno == EINTR
    

    sisgsuspend()-kutsussa annettu maski on voimassa vain ko. funktion aikana. Kun funktio palaa, asettuu kutsua edeltänyt maski takaisin. Oikea ratkaisu eo. tapauksessa on:

    
      sigset_t newmask, oldmask, zeromask;
    
      sigemptyset(&zeromask);
      sigemptyset(&newmask);
      sigaddset(&newmask,SIGINT);
    
      if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)
          perror("SIG_BLOCK error");
    
        /* kriittinen osa koodia */
    
      if (sigsuspend(&zeromask) != -1)
          perror("sigsuspend error");
       
        /* jatka prosessointia */
    


    5.4 Tiedostolukitukset

    Jos useampi prosessi päivittää samaa tiedostoa, voi syntyä sotkua, koska kahdesta tiliviennistä kirjautuu vain myöhempi tai lentokoneen paikka varataan kahdelle jne.

    Lukituksessa prosessi varaa oikeuden tiedoston tai sen osan käsittelemiseen ja muut prosessit jäävät odottamaan tarvittaessa lukon vapautumista eli käsittelyvuoro (lukko) kiertää. UNIX ei huolehdi automaattisesti lukituksista, vaan ne on ohjelmoitava sovellukseen itse. Lukitus voi olla:

    • 'neuvoa antavaa' (advisory lock), joka estää muita lukitsemasta jo lukittua osaa fcntl()-kutsulla, mutta ei estä toisten prosessien read() / write() - kutsuja
      • käyttöoikeudet (mode) sallii tai kieltää
      • prosessi kirjoitettava "hyvätapaiseksi"
    • pakottavaa (mandatory lock), joka estää muita lukitsemasta jo lukittua osaa fcntl()-kutsulla ja estää myös ristiriidassa olevat read() / write() - kutsut. Voi johtaa lukkiutumistilanteisiin (deadlock).

    'Ylimääräinen' tiedosto voi olla 'lipuke' muille vihjeeksi jonkun yhteisen tiedoston käytöstä. Jos lukkotiedosto on olemassa, prosessi jättäytyy odottamaan. Kun varsinaista tiedostoa ei enää tarvita, se vapauttaa lukkotiedoston.

     
    /* varaa */  
    
    do {
         sleep(5); 
        fd=open("tdsto.lock~",O_CREAT | O_EXCL,FILE_MODE);
    } while (fd < 0 && errno == EEXIST);
    
    /* tee varsinaiselle tiedostolle mitä mieli tekee */
    
    /* vapauta */
       
    close(fd);
    unlink("tdsto.lock~");
    

    Lukitseminen fcntl()-kutsulla:

    #include < sys/types.h >
    #include < unistd.h >
    #include < fcntl.h >
     
    struct flock_t {
        short l_type;        lukon tyyppi
        short l_whence;      mistä siirtymä mitataan  
        long  l_start;       siirtymä lukituskohdan alkuun
        long  l_len;         montako tavua?
        pid_t l_pid;         lukon omistajan pid
    } 
     
    int fcntl(int fd, int cmd, flock_t *arg);
     
    
    l_type:         L_RDLCK, L_WRLCK, L_UNLCK
                    - lukulukko, poissulkeva lukko, vapautus
    
    l_whence:   SEEK_SET, SEEK_CUR, SEEK_END
                    - alusta, nykypositiosta, lopusta
    
    l_len:      alueen pituus. 
                    Jos l_len == 0, niin lukitus
                    tiedoston loppuun  (vaikka koko
                    kasvaisikin)
    
    cmd:
        F_GETLK kysy onko alueella lukkoa. 
            Jos alueella on lukko, niin palauttaa estävän
            lukon tiedot rakenteessa arg, 
            muuten arg.l_type = F_UNLCK.
    
        F_SETLK varaa / vapauta lukko.
            Jos varaaminen/vapauttaminen ei onnistu,
            palauttaa -1 ja errno == EAGAIN.
    
        F_SETLKW    varaa / vapauta lukko. 
            Jos ei onnistu, jää odottamaan .
    

    Lukulukko L_RDLCK on jaettu lukko (shared), joka kieltää kaiken kirjoittamisen lukitulla osalla ja sallii monen prosessin lukea. Kirjoituslukko L_WRLCK on poissulkeva lukko (exclusive), joka estää muilta prosesseilta lukitun osan käytön ja lukinnut prosessi saa lukea ja kirjoittaa. Lukko vapautetaan antamalla cmd = F_SETLK ja l_type = F_UNLCK. Esimerkkejä:

    
    /* Lukitse kokonainen tiedosto omaan käyttöön: */
      
        struct flock lukko;
    
        lukko.l_type   = F_WRLCK;
        lukko.l_whence = SEEK_SET;
        lukko.l_start  = 0;
        lukko.l_len    = 0;
        fcntl(fd, F_SETLKW,&lukko);
    
    /* Lukitse 100 seuraavaa tavua siten, että muut eivät
    saa kirjoittaa ko. alueelle: */
      
        lukko.l_type   = F_RDLCK;
        lukko.l_whence = SEEK_CUR;
        lukko.l_start  = 0;
        lukko.l_len    = 100;
        fcntl(fd, F_SETLKW,&lukko);
    
    /* Avaa edellä lukitusta alueesta 50 tavua: */ 
         lukko.l_type = F_UNLCK;
         lukko.l_whence = SEEK_CUR; 
         lukko.l_start = 25; 
         lukko.l_len = 50;
         fcntl(fd, F_SETLKW,&lukko); 
    

    Jaetun lukon saa muuttaa poissulkevaksi ja päinvastoin avaamatta lukkoa välillä. Lukot säilyvät koodinvaihdossa (exec-kutsu), ellei asetettu lipuketta FD_CLOEXEC. Lapsiprosessi ei peri äitiprosessin asettamia lukkoja. Kun tiedosto suljetaan, lukot avautuvat. kun prosessi päättyy, lukot avautuvat jos dup()-kutsun jälkeen suljetaan alkuperäinen tiedostokuvaaja, lukot avautuvat myös kopiossa (kuvaajat osoittavat samaan paikkaan!). Jos tiedosto avataan prosessissa uudestaan, lukot avautuvat.


    5.5 Muistiinkuvatut tiedostot

    Levytiedosto voidaan kuvata (map) keskusmuistipuskuriin siten, että kun haetaan tavuja puskurista, niin saadaan vastaavat tiedoston tavut (Talletus vastaavasti). Prosessi voi tehdä siirräntää ilman funktioita read() ja write(). Perustuu virtuaalimuistin hyödyntämiseen: sivutusalgoritmit huolehtivat muistiinnoudosta ja levylle tallettamisesta. Muistiinkuvaus tehdään funktiolla caddr_t mmap(caddr_t addr, size_t len, int prot, int flag, int filedes, off_t off);, joka palauttaa muistialueen alkuosoiteen. Parametrit:

    
    addr    osoite, johon kohtaan halutaan kuvata
            jos addr == 0 niin KJ valitsee paikan
            - arvon oltava virtuaalimuistin sivun monikerta
    
    filedes kuvattavan tiedoston tiedostokuvaaja (avattu)
    
    len kuvattavan alueen pituus muistissa (tavuja)
    
    off monennestako tiedoston tavusta alkaen 
            - oltava virtuaalimuistin sivun monikerta
    
    prot    käyttötapa  
        PROT_READ   alueelta voi lukea
        PROT_WRITE  alueelle voi kirjoittaa
        PROT_EXEC   alueen voi suorittaa
        PROT_NONE   aluetta ei saa käsitellä
    
        Käyttötavan on vastattava funktiossa open() annettua
        käyttötapaa.
    
    flag    muistiinkuvatun alueen attribuutteja
        MAP_FIXED   kuvattava paikkaan addr
        MAP_SHARED  tallettaminen alueelle muuttaa tiedos-
                    
                    ton sisältöä
        MAP_PRIVATE tallettaminen alueelle generoi uuden
                    tiedoston
    
    

    Tiedostokuvaajan sulkeminen ei vapauta muistiinkuvausta, vaan se on tehtävä funktiolla int munmap(caddr_t addr, size_t len);. Lapsiprosessi perii fork()-kutsussa äitinsä muistiinkuvatut alueet, mutta exec()-kutsussa ne vapautetaan. Muistiinkuvattu I/O on nopeaa, sillä siirrossa ei käytetä KJ:n puskurointi, vaan siirretään suoraan sovelluksen käytettäväksi.

    
    #include    < sys/types.h >
    #include    < sys/stat.h >
    #include    < sys/mman.h >     /* mmap() */
    #include    < fcntl.h >
    #include    < unistd.h >
    
    #ifndef MAP_FILE           /* 44BSD defines this & requires it to mmap files */
    #define MAP_FILE    0           /* to compile under systems other than 44BSD */
    #endif
    
    int main(int argc, char *argv[])
    {
        int     fdin, fdout;
        char        *src, *dst;
        struct stat statbuf;
    
        if (argc != 3)  perror("usage: a.out  ");
    
        if ( (fdin = open(argv[1], O_RDONLY)) < 0)
            perror("can't open %s for reading", argv[1]);
    
        if ( (fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0)
            perror("can't creat %s for writing", argv[1]);
    
        if (fstat(fdin, &statbuf) < 0)  
            perror("fstat error");/* need size of input file */
                
        if (lseek(fdout, statbuf.st_size - 1, SEEK_SET) == -1)/* set size of output file */
            perror("lseek error");
        if (write(fdout, "", 1) != 1)
            perror("write error");
    
        if ( (src = mmap(0, statbuf.st_size, PROT_READ,
                          MAP_FILE | MAP_SHARED, fdin, 0)) == (caddr_t) -1)
            perror("mmap error for input");
    
        if ( (dst = mmap(0, statbuf.st_size, PROT_READ | PROT_WRITE,
                         MAP_FILE | MAP_SHARED, fdout, 0)) == (caddr_t) -1)
            perror("mmap error for output");
    
        memcpy(dst, src, statbuf.st_size);  /* does the file copy */
    
        exit(0);
    }
    



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