5. Prosessien välinen vuorovaikutus
Sisältö:
5 Prosessin välinen vuorovaikutus
Prosessit vaikuttavat toisiinsa usealla tavalla:
- kilpailu resursseista (CPU, muisti, siirräntä)
- fork() / wait()
- yhteiset levytiedostot tai niiden olemassaolo
- muistiinkuvattu tiedosto
- tiedostolukot
- signaalit
- putket, FIFOt
- pseudopääte
- sanomavälitys
- yhteiskäytössä oleva muisti
- semaforit
5.1 Putket
Putki luodaan funktiolla int pipe(int fd[2]);. Putki on erikoistiedosto, jolla on kaksi tiedostokuvaajaa:
- fd[1] tänne voi kirjoittaa
- fd[0] täältä voi lukea mitä putkeen kirjoitettiin FIFO, "first-in-first-out"
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:
- read + tyhjä putki: odottaa kunnes tulee luettavaa
- write + täysi putki: odottaa kunnes putkessa on tilaa (ts. kunnes joku lukee putkesta)
- write +osa sopii: kirjoittaa sen mikä sopii ja odottaa kunnes loputkin sopivat. write() on atominen, jos kirjoitettava määrä <= PIPE_BUF
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.
#includeFILE * 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:
- open() + O_WRONLY : odottaa, kunnes toinen prosessi avaa lukemista varten
- open() + O_RDONLY : odottaa, kunnes toinen prosessi avaa kirjoittamista varten
- read() + tyhjä putki : odottaa kunnes tulee luettavaa
- write() + täysi putki : odottaa kunnes putkessa on tilaa (ts. kunnes joku lukee putkesta)
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)