Dagens tema (kapittel 15 i Englander-boken) Prosesser Hvordan starte prosesser Å vente på prosesser Å utføre programmer Hva gjør en kommandotolker? Signaler Obligatoriske oppgave 2 Prosesser Hva er en prosess? En prosess er et program under utførelse. Hvordan lage nye prosesser? I Unix finnes kun én måte å lage nye prosesser på: pid = fork(); Følgende skjer: ❶ fork lager en nøyaktig kopi av prosessen. Returverdien i foreldreprosessen er barnets PID («process identification number»). Barneprosessen får 0 ved retur. ❷ Begge prosessene fortsetter å kjøre i parallell. Ark 1 av 25 Ark 2 av 25 Å vente på andre prosesser Systemkallet wait benyttes til å vente på at prosessens barn skal bli ferdige. Kallet Hvordan avslutte prosesser? En prosess kan avslutte normalt på tre forskjellige måter: Prosessen terminerer (med ukjent status) når programmet avsluttes. Hvis hovedprogrammet (main) avsluttes med en return-setning, vil dette være prosessens statusverdi. Systemkallet exit vil terminere prosessen, samt angi statusverdi. Statusverdi 0 angir «status OK»; andre verdier angir en feilsituasjon. OSet kan bruke statusverdien; i mange kommandotolkere kan man for eksempel skrive cc fil.c -o fil &&./fil #include <sys/wait.h> pid_t n; int status;. n = wait(&status); vil gjøre følgende: Hvis det ikke finnes flere barneprosesser, returnerer wait øyeblikkelig med n = 1. Når det finnes barn som er ferdige, returnerer wait med følgende parametre: n er PID til barnet. status (nest nederste byte) inneholder barnets statusverdi. status (nederste byte) inneholder data om hvorledes barnet ble avsluttet; 0 angir nomal stopp. Hvis det bare finnes levende barn, blir prosessen som kaller wait blokkert til ett avbarnaerferdig. Ark 3 av 25 Ark 4 av 25
Eksempel Filen fork3.c #include <stdlib.h> #include <sys/wait.h> #include <unistd.h> pid_t pid, n; int status; pid = fork(); if (pid) /* Samme som (pid!= 0) */ printf("opphavet har PID=%d, ", (int)getpid()); printf("barnet har PID=%d\n", (int)pid); printf("nå venter opphavet...\n"); n = wait(&status); printf("n=%d, status=0x%x\n", (int)n, status); return 0; else printf("barnet har PID=%d\n", (int)getpid()); printf("barnet får status=99\n"); exit(99); $fork3 Barnet har PID=20818 Barnet får status=99 Opphavet har PID=20817, barnet har PID=20818 Nå venter opphavet... n=20818, status=0x6300 Hvordan utføre programmer? Systemkallet execve benyttes til å utføre programmer. Kallet execve("/usr/bin/pwd", param, env); vil gjøre følgende: Alt innholdet av prosessen som utfører kallet, vil bli erstattet av innholdet av filen /usr/bin/pwd. Parametre og omgivelsen overføres. Den gamle prosessen med det nye innholdet kjøres fra programstart. Det er altså ingen retur fra en execve med mindre noe går galt! (Det finnes mange varianter av execve. Forskjellen mellom de forskjellige dreier seg for det meste om hvorledes man kan sløyfe parametre eller gi disse på en alternativ måte. I dette kurset vil vi kun benytte execve.) Ark 5 av 25 Ark 6 av 25 Eksempel Programmet /usr/bin/pwd skriver ut hvor i fil-treet vi befinner oss (mao hvor vi flyttet med siste cd-kommando). Det har ingen parametre. Filen pwdx.c inneholder vår versjon av pwd: #include <unistd.h> char *empty[1] = NULL; execve("/usr/bin/pwd", empty, empty); perror("pwdx"); exit(1); Parametre til execve Andre og tredje parameter til execve angir parametre til kommandoen og omgivelsen kommandoen skal utføres i. Begge parametre er adresser til vektorer av pekere til tekster, og begge vektorene må avsluttes med en NULL-peker. 0 ls -acf Omgivelsen er de variable som overføres mellom forskjellige utgaver av kommandotolkeren. I (ba)sh angis disse variablene som export, i(t)csh med direktivet setenv. $ pwdx /ifi/ganglot/a03/dag/inf103/forelesninger/kode Ark 7 av 25 Ark 8 av 25
. Program-parametre Parametrene og omgivelsesvariablene kommer som parametre til hovedprogrammet (main) og den globale pekeren environ. Filen lsx.c #include <unistd.h> extern char **environ; char *par[] = "ls", "-acf", NULL ; execve("/bin/ls", par, environ); perror("lsx"); exit(1); Eksempel på kjøring: $lsx./../ lsx* lsx.c test-a@ Eksempel extern char **environ; Filen pars.c int main(int argc, char *argv[]) char **ep; int i; for (i = 0; i < argc ; i++) printf("argv[%d]= %s \n", i, argv[i]); ep = environ; while (*ep) printf("%s\n", *ep); ++ep; return 0; Antallet parametre er gitt i argc, mens listen av omgivelsesvariable avsluttes med en NULL-peker. Tidligere brukte man en tredje parameter til main for å få tak i omgivelsesvariablene. Ark 9 av 25 Ark 10 av 25 Ark 11 av 25 Hva skjer i en kommandotolker? En kommandotolker (som (ba)sh) gjør følgende når den skal utføre vanlige kommandoer: Kjøring $ pars -la 122 /bin/cp argv[0]= pars argv[1]= -la argv[2]= 122 argv[3]= /bin/cp PWD=/home/ansatte/03/dag/IN147/Forelesninger/Kode PAGER=less HZ=100 REMOTEHOST=vidi.ifi.uio.no HOSTNAME=virfir.ifi.uio.no while (1) read_command(command, params); if (fork()!= 0) /* Foreldreprosessen venter mens... */ wait(&status); else /*... barneprosessen kjører programmet. */ execve(command, params, env); (Dette bildet er litt forenklet!) Ark 12 av 25
Omgivelsesvariabelen PATH Når brukeren oppgir kommandoen pwd, hvor er da den tilhørende programfilen? I Unix angir PATH hvilke filområder det skal letes i. Det første programmet med riktig navn som finnes, blir brukt. > echo $PATH /usr/bin:/bin:/local/bin:. Programmet which kan brukes til å sjekke hvilket program som velges: > which pwd /usr/bin/pwd Forgrunnen og bakgrunnen En prosess kan kjøres i to ulike modi: Forgrunnen Prosessen har kontakt med tastatur og skjerm. Brukeren får ikke nytt klarsignal før prosessen er ferdig. Bakgrunnen Prosessen kan skrive på skjermen men ikke lese fra tastaturet. Brukeren får nytt klarsignal med en gang. Oftest brukes en & sist på linjen til å markere bakgrunnskjøring. Ark 13 av 25 Ark 14 av 25 Ark 16 av 25 Signaler Et signal er Unix-utgaven av et avbrudd. Det sendes fra kjernen i operativsystemet til de enkelte prosesser og er en «beskjed» om at noe spesielt har skjedd. Hvis prosessen har sagt fra om at den vil håndtere signalet, startes den spesielle signal-rutinen. Hvis ikke, drepes prosessen. I noen tilfelle skrives dump av lageret (core-fil). Ark 15 av 25 Listen over alle signalene i Linux finnes på filen /usr/include/bits/signum.h: #define SIGHUP 1 /* Hangup (POSIX). */ #define SIGINT 2 /* Interrupt (ANSI). */ #define SIGQUIT 3 /* Quit (POSIX). */ #define SIGILL 4 /* Illegal instruction (ANSI). */ #define SIGTRAP 5 /* Trace trap (POSIX). */ #define SIGABRT 6 /* Abort (ANSI). */ #define SIGIOT 6 /* IOT trap (4.2 BSD). */ #define SIGBUS 7 /* BUS error (4.2 BSD). */ #define SIGFPE 8 /* Floating-point exception (ANSI). */ #define SIGKILL 9 /* Kill, unblockable (POSIX). */ #define SIGUSR1 10 /* User-defined signal 1 (POSIX). */ #define SIGSEGV 11 /* Segmentation violation (ANSI). */ #define SIGUSR2 12 /* User-defined signal 2 (POSIX). */ #define SIGPIPE 13 /* Broken pipe (POSIX). */ #define SIGALRM 14 /* Alarm clock (POSIX). */ #define SIGTERM 15 /* Termination (ANSI). */
Ark 17 av 25 #define SIGSTKFLT 16 /* Stack fault. */ #define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */ #define SIGCHLD 17 /* Child status has changed (POSIX). */ #define SIGCONT 18 /* Continue (POSIX). */ #define SIGSTOP 19 /* Stop, unblockable (POSIX). */ #define SIGTSTP 20 /* Keyboard stop (POSIX). */ #define SIGTTIN 21 /* Background read from tty (POSIX). */ #define SIGTTOU 22 /* Background write to tty (POSIX). */ #define SIGURG 23 /* Urgent condition on socket (4.2 BSD). */ #define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */ #define SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */ #define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */ #define SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */ #define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */ #define SIGPOLL SIGIO /* Pollable event occurred (System V). */ #define SIGIO 29 /* I/O now possible (4.2 BSD). */ #define SIGPWR 30 /* Power failure restart (System V). */ #define SIGSYS 31 /* Bad system call. */ #define SIGUNUSED 31 Under Solaris kan man benytte kommandoen man 5 signal. De viktigste signalene SIGINT gir et middels sterkt ønske om terminering. SIGQUIT er som SIGINT men gir core-dump. SIGKILL er en drapsordre. SIGALRM er en vekkerklokke. SIGTERM er et vennlig «klapp på skulderen». Ark 18 av 25 Hvordan fange opp et signal? Systemkallet signal brukes til å fortelle systemet hvilket signal som skal fanges opp og hvilken rutine som skal håndtere det. #include <signal.h> #include <stdlib.h> Hvordan sende et signal? Et signal kan oppstå på flere måter: Fra kjernen av systemet (ved feilsituasjon). Fra kjernen av systemet (vekkerklokken). Fra tastaturet ( Ctrl + C eller Ctrl + \ ). Fra systemkallet kill. Fra kommandoen kill. $ kill 123 # Send SIGTERM $ kill -9 123 # Send SIGKILL void catch_int(int sig) printf("\net SIGINT-signal (nr %d) fanget opp.\n", sig); exit(1); int i, j, k; signal(sigint, catch_int); /* Gjør et eller annet i evig løkke... */ for (i = 1; 1; i++) for (j = 1; j <= 1000000; j++) k = k*3; printf("%5d", i); if (i % 10 == 0) putchar( \n ); > signal0 1 2 3 4 5 6 7 8 9 10 11 Ctrl + C Et SIGINT-signal (nr 2) fanget opp. Ark 19 av 25 Ark 20 av 25
Bruk av signal Dette programmet prøver å beskytte seg: #include <signal.h> Følgende er verdt å merke seg: I noen Unix-versjoner er det slik at når et signal først er fanget opp, må signal kalles på nytt for det signalet. I filen /usr/include/signal.h finnes prototypen til signal. Navnet SIGINT (og de andre signalnavnene) er indirekte definert der. Noen signaler ignoreres om brukeren ikke har angitt signalhåndteringsrutine; andre dreper prosessen. Signalet SIGKILL kan ikke fanges opp av en brukerprosess. Systemkallet pause vil la en prosess sove til den mottar et signal. void catch_int(int sig) printf("ha, ha! SIGINT vekker ikke meg!\n"); signal(sigint, catch_int); pause(); printf("nå sover jeg...\n"); signal(sigint, catch_int); pause(); Det hjelper ikke om man vet litt om signaler: > sov Nå sover jeg... Ctrl + C Ha, ha. SIGINT vekker ikke meg! Ctrl + C Ha, ha. SIGINT vekker ikke meg! Ctrl + \ Quit - core dumped > sov & [1] 28703 Nå sover jeg... > kill -2 28703 Ha, ha! SIGINT vekker ikke meg! > kill 28703 [1] Terminated sov Ark 21 av 25 Ark 22 av 25 Vekkerklokker Vekkerklokker er en spesiell type signaler som gjør det mulig å la prosesser vente et gitt antall sekunder og så bli vekket av en alarm. Kallet alarm(10); vil medføre at det (omtrent) 10 sekunder senere kommer et SIGALRM-signal til prosessen. #include <signal.h> void catch_alrm(int x) printf("nå er tiden ute.\n"); printf("nå tar vi 10 sekunder pause...\n"); signal(sigalrm, catch_alrm); alarm(10); pause(); printf("da er programmet ferdig.\n"); return 0; > sov10 Nå tar vi 10 sekunder pause... Nå er tiden ute. Da er programmet ferdig. Obligatorisk oppgave 2 Denne oppgaven går ut på å skrive en kommandotolker kalt ifish. Den skal ❶Skrive et klarsignal («prompt»). ❷ Lese en linje. ❸ Splitte opp linjen i kommandonavn og parametre. ❹ Om kommandoen er exit, skal ifish avslutte. ❺ Ellers skal ifish starte en barneprosess. ❻ Barneprosessen skal utføre execve på kommandoen. Dette gjøres ved å prøve med alle stier i omgivelsesvariabelen PATH foran. ❼ Om kommandolinjen ikke slutter med &, skal ifish vente til barneprosessen er ferdig. ❽ Gå til punkt 1 igjen. For å gjøre det enklere å løse oppgaven, er den delt i tre. Ytterligere detaljer finnes i oppgavetekstene. Ark 23 av 25 Ark 24 av 25
Regn med at du vil gjøre feil under programmeringen. Legg inn masse testutskrifter! #define DEBUG 1 : #if DEBUG printf(...); #endif Sjekk spesielt alt som har med tekster å gjøre. Bruk anførselstegn i testutskriftene printf("kommandoen er %s.\n", kommando); for å oppdage overfødige blanke. Sjekk hvert punkt grundig før du tar fatt på neste punkt. Sjekk at programmet ikke har minnelekkasje. Sjekk feilmeldinger fra systemkall som safefork og malloc. Skriv kommentarer fra starten av. Er det vanskelig å beskrive, er det sikkert dårlig programmert. Det er alltid en feil til! Ark 25 av 25