Nyttige forklaringer Sikker opprettelse av nye prosesser Hva er en kommandotolker? Dagens tema: Parallellstyring Generelt Synkronisering med filer Synkronisering med rør Synkronisering med felles lager Ark 1 av 26 Forelesning 5.4.2000
Hvordan kvele et UNIX-system? Selv om UNIX-implementasjoner er godt sikret, finnes det kode som kveler de aller fleste: while(1) {... fork();... hvor det ikke er tilstrekkelig mange kall på wait. Som en løsning har jeg skrevet safefork som kun tillater seks prosesser mer; forøvrig oppfører den seg akkurat som fork. Koden ligger på ~in147/kode/safefork.c. Alle IN147-studenter anmodes intenst om å bruke safefork i stedet for fork. Kopiér filen til området der programmet ditt ligger. Forelesning 5.4.2000 Ark 2 av 26
Kommandotolkeren En kommandotolker (ofte kalt «shell» på engelsk) er et program som leser brukerens kommandolinjer og kaller de riktige programmene. De vanligste kommandotolkerne heter sh (Bourne shell), csh (C shell) og bash («Bourne-again shell»). Snart kommer ifish (Ifi shell). En kommandotolker fungerer grovt sett slik: while (1) { skriv klarsignal ( prompt ) les brukerens kommandolinje splitt linjen i kommando og parametre if ( tolkerkommando som exit eller cd ) { utfør kommandoen else{ if(safefork()==0){ finn programfilen til kommandoen execve( programfil, parametre, omgivelse ); else{ vent til barnet er ferdig Forelesning 5.4.2000 Ark 3 av 26
Hvordan finne programmene? Når brukeren gir en kommando som tail, hvilken programfil menes da? Det finnes (foreløbig) 24 åpne filer som heter tail på systemet. Omgivelsesvariabelen PATH forteller hvilke filområder som kommandotolkeren skal lete i: maskin navn> printenv PATH /home/ansatte/03/dag/bin:/local/x11r5/bin:/local/x11r5/bin/pbm:/local/java/bin: /local/bin:/local/ssh/bin:/local/bin/msdos:/opt/sunwspro/bin:/usr/openwin/bin: /usr/ccs/bin:/usr/ucb:/usr/bin:/local/gnu/bin:/local/qt/bin:.: /hom/dag/public:/local/drift/bin:/local/metamail/bin De aktuelle områdene er angitt med kolon som skilletegn. Hvis man lurer på hvilken programfil som velges, kan man skrive maskin navn> which tail /usr/ucb/tail Forelesning 5.4.2000 Ark 4 av 26
Bakgrunn om parallellstyring Fraktalprogrammet tar 26 sekunder å produsere på SGI-maskinen modsognir når vi er alene der. Hvorledes kan det gjøre raskere? Forbedre algoritmen? Lite å hente her. (Jeg bruker allerede cc -O.) Stjele prosessortid fra andre? Ingen god løsning. Parallellisering? Mulighet med størst potensiale. Forelesning 5.4.2000 Ark 5 av 26
Parallellisering En naturlig parallellversjon av fraktalprogrammet består av én eller flere prosesser som beregner punkter i fraktalbildet, og én prosess som tegner opp punktene. De evige spørsmål Når det er snakk om parallellprogrammering, er det alltid to spørsmål som må besvares: Hvordan skal data overføres? Hvordan skal arbeidet synkroniseres? Begrepet parallellstyring omfatter disse to tingene. Forelesning 5.4.2000 Ark 6 av 26
Polling og avbrudd Når vi har flere parallelle prosesser, vil de gå med ulik hastighet. Vi ønsker at disse kan kommunisere med hverandre for å synkronisere kjøringen, overbringe data eller fordi en feil har oppstått. Hvordan skal så disse prosessene kommunisere? På laveste nivå finnes det tre generelle muligheter: polling, avbrudd, og flaggavbrudd. Forelesning 5.4.2000 Ark 7 av 26
En analogi Anta at du skal på restaurant; din venn/venninne Inge bestiller bord. Polling Du ringer ofte, f.eks. hvert 5. minutt. +: Enkelt å avtale : Kaster bort mye tid. : Kan ringe for sent. Avbrudd Inge ringer deg. +: Kaster ikke bort tid. +: Øyeblikkelig respons. : Avbrudd på ubeleilig tidspunkt. Flaggavbrudd Du skaffer deg en automatisk telefonsvarer. +: Kaster ikke bort mye tid. +: Ikke avbudd på ubeleilig tidspunkt. : Ikke øyeblikkelig respons. Forelesning 5.4.2000 Ark 8 av 26
Parallellitet i UNIX UNIX benytter mer høynivå mekanismer for parallellstyring. Disse er laget for «aktiv» men «grovkornet» parallellitet: Det er ganske billig å opprette nye prosesser. Det antas at prosesser normalt kommuniserer lite eller ingenting. Opprettelse av nye prosesser Som tidligere nevnt, opprettes nye prosesser ved kall på fork. Om ønsket, kan prosessens innhold endres ved å kalle på execve. Forelesning 5.4.2000 Ark 9 av 26
Parallellstyring med filer Hovedprogrammet main Hovedprogrammet starter n barneprosesser til å gjøre hver sin del av jobben; når de er ferdige kan den lese filene og tegne opp vinduet. int main(int argc, char *argv[]) { time_t start_tid = time(null); int n_proc, status, ix; char f_name[200]; n_proc = argc <= 1? 1 : atoi(argv[1]); for (ix = 1; ix <= n_proc; ++ix) { if(safefork()==0){ write_file(ix, (ix-1)*nx/n_proc, ix*nx/n_proc-1); exit(0); for (ix = 1; ix <= n_proc; ++ix) wait(&status); x147open(nx, NY); for (ix = 1; ix <= n_proc; ++ix) { plot_file(ix); sprintf(f_name, "/tmp/frak-%d.data", ix); unlink(f_name); printf("det hele tok %d sekunder.\n", time(null)-start_tid); x147pause(); return 0; Forelesning 5.4.2000 Ark 10 av 26
Funksjonen write file Denne funksjonen lager filen /tmp/frak-p num.data for ix 1 ix ix 2. void write_file(int p_num, int ix1, int ix2) { FILE *f; char f_name[200]; float x, y; int ix, iy; sprintf(f_name, "/tmp/frak-%d.data", p_num); f = fopen(f_name,"w"); for (ix = ix1; ix <= ix2; ++ix) { x = x1+ix*(x2-x1)/(nx-1); for (iy = 0; iy < NY; ++iy) { y = y1+iy*(y2-y1)/(ny-1); fprintf(f, "%d %d %d\n", ix, iy, fraktal(x,y,255.0,max)); fclose(f); Forelesning 5.4.2000 Ark 11 av 26
Funksjonen plot file Denne funksjonen tegner opp data fra filen frak-p num.data. void plot_file(int p_num) { FILE *f; char f_name[200]; int ix, iy, fval; sprintf(f_name, "/tmp/frak-%d.data", p_num); f = fopen(f_name,"r"); while (fscanf(f, "%d%d%d", &ix, &iy, &fval)>0) { x147plot(ix, iy, fval); if (iy == 0) { x147sync(); if (x147done) exit(0); fclose(f); Forelesning 5.4.2000 Ark 12 av 26
Resultater Dette forsøket på parallellisering falt ikke heldig ut: Ikke-parallell tegning 26s frakfil 1 41s frakfil 2 27s frakfil 3 23s frakfil 4 22s frakfil 5 22s frakfil 6 21s Vurdering + Enkelt å programmere. Åpning, skriving, lukking, lesing og fjerning av filer tar ganske mye tid. Hovedprogrammet kan ikke tegne noe før alle barna er ferdige med beregningene. Forelesning 5.4.2000 Ark 13 av 26
Rør i UNIX UNIX-rør brukes når barneprosesser vil kommunisere med søsken eller sitt opphav. Rør brukes ofte på kommandolinjen for å koble standard ut i det første programmet til standard inn i det andre: maskin navn> grep dag logg-fil sort more Rør opprettes med systemkallet pipe: int pip[2];. pipe(pip); Resultatet er et rør med to filnumre: pip[0] benyttes til lesing. pip[1] benyttes til skriving. Forelesning 5.4.2000 Ark 14 av 26
Å skrive til et rør Man kan sende n byte med data (lagret i buffer) til et rør med systemkallet w_bytes = write(pip[1], buffer, n); Returverdien w bytes forteller hvor mange byte som faktisk ble skrevet (og det vil vanligvis være n). Å lese fra et rør Man kan lese fra et rør med kommandoen r_bytes = read(pip[0], buf2, n); Data skal plasseres i buf2 hvor det er plass til n byte. Returverdien r bytes forteller hvor mange byte som faktisk ble mottatt; det vil ofte være mindre enn n. Synkronisering Man oppnår synkronisering ved at read om nødvendig blir blokkert til det er mer data å lese, og write må vente når røret er fullt. Forelesning 5.4.2000 Ark 15 av 26
Eksempel Bruk av rør IN 147 Program og maskinvare #include <stdlib.h> #include <stdio.h> #include <unistd.h> extern pid_t safefork(); int main(void) { int pip[2]; pipe(pip); if (safefork()) { /* Foreldren */ char *melding = "Hei på deg!"; write(pip[1], melding, strlen(melding)+1); else { /* Barnet */ char buffer[20]; int n; n = read(pip[0], buffer, 20); printf("mottatt %d tegn: %s \n", n, buffer); exit(0); return 0; Kjøring Mottatt 12 tegn: Hei på deg! Dette fungerer fordi selve røret eksisterer «utenfor» prosessen; følgelig blir ikke røret kopiert av safefork, bare referansene i pip. Forelesning 5.4.2000 Ark 16 av 26
Fraktalprogrammet med rør Hovedprogrammet starter barneprosessene og henter så kolonner fra røret for å tegne dem ut. int main(int argc, char *argv[]) { int pip[2]; int n_done=0, n_proc, n, iy; time_t start_tid = time(null); col_data b; n_proc= argc <= 1? 1 : atoi(argv[1]); pipe(pip); for (n = 1; n <= n_proc; ++n) { if (safefork()==0) { gen_area(pip, NX*(n-1)/n_proc, NX*n/n_proc-1); exit(0); x147open(nx, NY); while (n_done < n_proc) { n = read(pip[0], &b, sizeof(b)); if (b.xpos >= 0) { for (iy = 0; iy < NY; ++iy) x147plot(b.xpos, iy, b.ycol[iy]); else ++n_done; x147sync(); if (x147done) exit(0); printf("det hele tok %d sekunder.\n", time(null)-start_tid); x147pause(); Forelesning 5.4.2000 Ark 17 av 26
For å redusere lesetiden sendes én og én kolonne med data over røret. typedef struct { int xpos; /* -1="finished" */ unsigned char ycol[ny]; col_data; Funksjonen gen area regner ut kolonnene i buf og skriver dem til røret etterpå. void gen_area(int pip[], int xstart, int xend) { float x, y; int ix, iy; col_data buf; for (ix = xstart; ix <= xend; ++ix) { buf.xpos = ix; x = x1 + ix*(x2-x1)/(nx-1); for (iy = 0; iy < NY; ++iy) { y = y1 + iy*(y2-y1)/(ny-1); buf.ycol[iy] = fraktal(x, y, 255.0, MAX); write(pip[1], &buf, sizeof(buf)); buf.xpos = -1; write(pip[1], &buf, sizeof(buf)); Når prosessen er ferdig, sendes en kolonne med xpos= 1for å markere dette. Forelesning 5.4.2000 Ark 18 av 26
Kjøretid Ikke-parallell tegning 26s frakpipe 1 23s frakpipe 2 12s frakpipe 3 10s frakpipe 4 10s frakpipe 6 9s Vurdering av rør Dataoverføring skjer gjennom rørene, en col data hver gang. Synkronisering skjer ved at tegneprogrammet blokkeres inntil det er data i røret, og så leser data derfra. + Enkel programmering. + Sikker synkronisering. Kallene på read og write koster litt (men mye mindre enn lesing og skriving av filer). Forusetter at alle prosessene startes av samme urprosess. Forelesning 5.4.2000 Ark 19 av 26
Flere mekanismer I «klassisk UNIX» erfilerogrørdeeneste kommunikasjonsmulighetene. Dette er ofte ikke nok. Utviklerne av System V-varianten av UNIX laget derfor en utvidelse kalt SVIPC («System V IPC»). Denne utvidelsen omfatter felles lager (omtales i dag) semaforer (omtales neste uke) meldinger Felles lager Et felles lager kan brukes av flere prosesser fordi det ligger utenfor de vanlige prosessene. Det fjernes heller ikke automatisk når prosessene er ferdige. IPC = «interprocess communication». Forelesning 5.4.2000 Ark 20 av 26
Opprettelse av felles lager Et felles lager ligger tilsynelatende i alle prosesser som har tilgang til det. Følgende operasjoner må til for å opprette et felles lager med plass til 2 int-verdier: #include <sys/ipc.h> #include <sys/shm.h> int sh_mem_id, *sh_mem; sh_mem_id = shmget(ipc_private, /* Nøkkel */ 2*sizeof(int), /* Størrelse */ 0700); /* Beskyttelse */ sh_mem = shmat(sh_mem_id,0,0); Verdiene er nå tilgjengelige som sh mem[0] og sh mem[1]. Forelesning 5.4.2000 Ark 21 av 26
Fjerning av et felles lager Et felles lager fjernes ikke automatisk når en prosess dør. Følgende kall må til: shmctl(sh_mem_id, IPC_RMID, 0); Sjekk det godt Hvis et program feilet, kan man sjekke om det finnes et felles lager som ikke ble fjernet: maskin navn> ipcs -m Shared Memory: T ID KEY MODE OWNER GROUP m 6901 0x00000000 --rw------ dag dag Da kan lageret fjernes med kommandoen maskin navn> ipcrm -m 6901 Husk: Felles lager er en meget begrenset ressurs! Forelesning 5.4.2000 Ark 22 av 26
En løsning med felles lager Hovedprogrammet oppretter det felles lageret og barneprosessene. Det leser også data fra lageret og tegner det ut. int main(int argc, char *argv[]) { int n_proc, n_done = 0, nc, iy; time_t start_tid = time(null); n_proc= argc <= 1? 1 : atoi(argv[1]); sh_mem_id = shmget(ipc_private,sizeof(col_data),0700); sh_mem = shmat(sh_mem_id,0,0); sh_mem->xpos = -2; x147open(nx, NY); for (nc = 1; nc <= n_proc; ++nc) { if (safefork()==0) { gen_area(nx*(nc-1)/n_proc, NX*nc/n_proc-1); exit(0); while (n_done < n_proc) { if (sh_mem->xpos >= 0) { for (iy = 0; iy < NY; ++iy) x147plot(sh_mem->xpos, iy, sh_mem->ycol[iy]); x147sync(); if (x147done) { shmctl(sh_mem_id, IPC_RMID, 0); exit(0); else if (sh_mem->xpos == -1) ++n_done; printf("det hele tok %d sekunder.\n", time(null)-start_tid); x147pause(); shmctl(sh_mem_id, IPC_RMID, 0); return 0; Forelesning 5.4.2000 Ark 23 av 26
Funksjonen gen area beregner farger og bygger opp buf. Når den er full, legges den i felles lager. void gen_area(int xstart, int xend) { float x, y; int ix, iy; col_data buf; for (ix = xstart; ix <= xend; ++ix) { buf.xpos = ix; x = x1 + ix*(x2-x1)/(nx-1); for (iy = 0; iy < NY; ++iy) { y = y1 + iy*(y2-y1)/(ny-1); buf.ycol[iy] = fraktal(x, y, 255.0, MAX); *sh_mem = buf; buf.xpos = -1; *sh_mem = buf; Forelesning 5.4.2000 Ark 24 av 26
Kjøring Resultatet blir imidlertid ikke pent, og verre jo flere prosesser vi har. Kjøringene avsluttes også oftest med x147 error: Illegal coordinates (-1,2). Dette skyldes manglende synkronisering mellom prosessene! Forelesning 5.4.2000 Ark 25 av 26
Vurdering Dataoverføring over felles lager fungerer fint. Synkronisering er ved hjelp av en variabel (sh mem->xpos) og det er for primitivt. Data går tapt! + Rask dataoverføring. Produsentene av data er ikke synkronisert med konsumenten! Konsumenten benytter aktiv venting! Mye tid kastes bort. Felles lager er fint til dataoverføringen, men vi trenger en synkroniseringsmekanisme. Den kommer neste uke! Forelesning 5.4.2000 Ark 26 av 26