Del II: Systemprogrammering och Inter-Process Communication.

Relevanta dokument
HI1025 Operativsystem, KTH Haninge, VT2012

Möte 7: Uppföljning av föreläsningen med Peer Instruction - (PI)

Introduktion till processer

Möte 9: Relationer mellan körande processer - (PI)

Grundprogrammen. combine

... Funktionsanrop. Vad händer när man kör ett program?

Programmering i C, 7,5 hp

Om pekare och minneshantering i C, relaterat till operativsystem och särskilt konstruktionen fork() execvp().

Möte 10: Kommunikation mellan processer - (PI)

Introduktion till programmering, hösten 2011

Vad händer när man kör ett program? Program och processer. Funktionsanrop. Avsluta programmet

(2) Skriv ett program som skapar ett antal processer vars relationer beskrivs av nedanstående tidsdiagram a

2... uppvisa förmåga att skapa och/eller hantera flerprocessiga program där de ingående processerna ges

Programmering, grundkurs, 8.0 hp, Elektro, KTH, hösten Programmering: att instruera en maskin att utföra en uppgift, kräver olika språk:

Övningar för del I av kursen

Föreläsning 3. Programmering, C och programmeringsmiljö

*Pekarvärden *Pekarvariabler & *

printenv sort pager printenv grep args sort pager

Uttryck och villkor. Föreläsning 2

Programmeringsteknik med C och Matlab

Föreläsning 3. Programmering, C och programmeringsmiljö

Föreläsning 6 pekare och pekare tillsammans med arrayer

Operatorer Tilldelning Kodblock { } if satsen Logiska uttryck Att programmera

EDAA20 Programmering och databaser. Mål komprimerat se kursplanen för detaljer. Checklista. Föreläsning 1-2 Innehåll. Programmering.

Deluppgift 17 Processhantering: exec, sleep, exit, plist

Allmänt om programvaror och filer i Windows.

Instruktioner för att kunna programmera på skolans datorer

Enkla datatyper minne

En kort text om programmering i C.

UNIX Introduktion UNIX. Datorerfarenhet. Vad menar man med operativsystem

Programmering, grundkurs, 8.0 hp HI1024, HI1900 etc., Tentamen TEN1. Måndagen den 10 januari 2011,

Planering Programmering grundkurs HI1024 HT TIDAA

Alla filer som bearbetar PHP script ska avslutas med ändelsen.php, exempelvis ska en indexsida till en hemsida heta index.php

Boken?!?! Vad är ett program? Kompilerande-Interpreterande Programmeringsmiljö Hello World! Att programmera och ett enkelt program Variabler printf

Programmera i C Varför programmera i C när det finns språk som Simula och Pascal??

Kompilering och exekvering. Föreläsning 1 Objektorienterad programmering DD1332. En kompilerbar och körbar java-kod. Kompilering och exekvering

C++ Slumptalsfunktioner + switch-satsen

HI1024, Programmering, grundkurs, 8hp KTH STH TENTAMEN. HI1024:TEN2 - Praktisk tentamen Tid: Fredagen den 21 oktober 2011,

Planering Programmering grundkurs HI1024 HT data

Att komma igång. Föreläsning 1

read Systemanrop i UNIX Exempel med read & write write int antal_tkn, fd, count; char buffer[size]; antal_tkn = read(fd, buffer, count);

Instruktion för laboration 1

Programmering, grundkurs, 8.0 hp, Elektro, KTH, hösten 2010

Objektorienterad programmering i Java I. Uppgifter: 2 Beräknad tid: 5-8 timmar (OBS! Endast ett labbtillfälle) Att läsa: kapitel 5 6

Användbara systemanrop och biblioteksfunktioner. 2G1520 Operativsystem

Föreläsning 5: Introduktion av pekare

Johan Karlsson Datavetenskap för teknisk kemi, 10p, moment 1 Datavetenskap Umeå Universitet. Tentamen

Planering Programmering grundkurs HI1024 HT 2014

Att komma igång. Föreläsning 1

Föreläsning 2. Variabler, tilldelning och kodblock{} if-satsen Logiska operatorer Andra operatorer Att programmera

Uttryck och villkor. Föreläsning 2

Agenda. Arrayer deklaration, åtkomst Makron Flerdimensionella arrayer Initiering Strängar Funktioner och arrayer. Övningar nu och då

Programmering med Java. Grunderna. Programspråket Java. Programmering med Java. Källkodsexempel. Java API-exempel In- och utmatning.

Datorer och datoranvändning Föreläsningar 2014/15. Föreläsning 1 Unix. Operativsystem. Unix

NetBeans 7. Avsikt. Projektfönster

Eclipse. Avsikt. Nu ska ett fönster liknande figuren till höger synas.

Laboration: Grunderna i MATLAB

Funktioner och programstruktur. Föreläsning 5

Programmering, grundkurs, 8.0 hp HI1024, extra tentamen, TEN1, för TIDAA1. Fredagen den 11 mars 2011,

Instruktion för laboration 1

Datorteknik ERIK LARSSON

Terminal vad det är vad man kan göra med den

Operativsystem. Innehåll. Operativsystemets funktion. Vad är ett OS? Vart hittar men ett OS? OS hanterar processorns resurser

Föreläsning 10. Pekare (Pointers)

Systemnära programmering Tentamen. Systemnära programmering, 7.5hp 5 november 2012

Välkomna till NADA. Datalogi, grundkurs 1, 15hp. (Numerisk Analys och DAtalogi) och kursen. DA2001 (Föreläsning 1) Datalogi 1 Hösten / 28

Föreläsning 2. Operativsystem och programmering

EDAA20 Programmering och databaser. Mål komprimerat se kursplanen för detaljer. Om att lära sig programmera. Föreläsning 1-2 Innehåll.

Operativsystem och användargränssnitt

Labora&on 2 Funk&oner, if och loop övningar/uppgi:er

KTH STH TENTAMEN. HI1024:TEN2 - Praktisk tentamen Tid: 8-13, den 18 februari 2012

Loopar och datatyper. Föreläsning 3

Arrays (indicerade variabler) Föreläsning 6

Operativsystem ID1200/06 och ID2200/06 Tentamen TENA 6 hp :00-18:00

Programmering, grundkurs, 8.0 hp HI1024, omtentamen, TEN1. Tisdagen den 7 juni 2011,

Arrays (indicerade variabler) Föreläsning 4

Föreläsning 4. Val, boolska värden, läsbarhet, osv

BINÄRA TRÄD. (X = pekarvärdet NULL): struct int_bt_node *pivot, *ny; X X X 12 X X 12 X X -3 X X

Projekt i programmering 1 (ver 2)... 2 Projektidé... 2 Planering... 2 Genomförande... 2 Testning och buggar... 3 Utvärdering... 3 Planering...

Tips och tricks. Några bra tips. Lägg till sökvägar

Operativsystem (ID2200/06) XX XX:00-XX:00

Några inbyggda funktioner (med resultat!) Introduktion till programmering D0009E. Föreläsning 4: Villkor och rekursion. Modulus-operatorn.

Inledande matematik för I1. MVE011 läsperiod Matlab vecka 2 övningsuppgifter

Konsolfönster i Windows Momentet ingår i kursen PDA DTR1206 Lab 1 DOS Konsolfönstret

Operativsystem (IS1350) :00-12:00

Programmering i C++ En manual för kursen Datavetenskaplig introduktionskurs 5p

Introduktion till programmering SMD180. Föreläsning 4: Villkor och rekursion

Laboration 3 HI1024, Programmering, grundkurs, 8.0 hp

Flera processer. Minneshantering. Trashing kan uppstå ändå. Ersätta globalt

TDDIU81. Processer och trådar. Andreas Dahlberg, Jonathan Doherty, Tony Magnusson, Patrik Ottosson, Rasmus Siljedahl

Program. Kapitel make Program Interpreterande och kompilerande program

NetBeans 5.5. Avsikt. Projektfönster

Lab5 för prgmedcl04 Grafik

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Muddy cards. Slingor

Bankkonto - övning. Övning 2 Skriv en metod, geträntan, som returnerar räntan.

Klassdeklaration. Metoddeklaration. Parameteröverföring

Övning Installation av Gentoo och/eller Arch Linux

HI1024, Programmering, grundkurs, 8hp KTH STH TENTAMEN. HI1024:TEN1 - Teoretisk tentamen Tid: Torsdagen den 20 oktober 2011,

SF1672, Linjär Algebra med Matlab för F1 Lab0

Instruktion för användande av Citrix MetaFrame

Transkript:

Möte 6 (Föreläsning 2) HI1025 Operativsystem 7.0 hp, KTH Haninge, VT2017 Del II: Systemprogrammering och Inter-Process Communication. Vi repeterar ett antal preciseringar: * En fil är en abstraktion som döljer krångliga detaljer om maskinvaran. En fil refererar ofta till ett fysiskt lagringsutrymme på någon form av lagringsmedium (sekundärminne såsom hårddisk, CD, DVD, Magnetband, floppy etc.) Men en fil kan också ligga i datorns primärminne då den behandlas. Förtydligande: Inom UNIX är ovanstående alldeles för smalt. En fil i UNIX refererar till något som man hittar i filhierarkin och det kan vara många många olika saker. Mycket, väldigt mycket av ett UNIX-system är åtkomligt via filhierarkin. UNIX filhierarki är, som ni kanske minns, ett träd med roten / högst upp, under den finns katalogerna /bin/ som innehåller körbara systemprogram som ls etc. Katalogen /home/ innehåller hemkatalogerna för systemets användare. Troligtvis har ni redan något hum om detta från InfoMet. Några exempel som illustrerar att filhierarkin är stor och omfattande är: /proc/: Denna katalog innehåller underkataloger som är numrerade, vi ser till exempel katalogerna 1000, 1001 och 1002, dessa kataloger ligger inte på någon hårddisk utan är genererad information direkt från kärnan som beskriver attribut och egenskaper hos processerna med nummer 1000, 1001 och 1002. På samma sätt har varje process en underkatalog i /proc/ som beskriver dessa attribut och egenskaper. /dev/: Denna katalog innehåller device-nodes, alltså enhetsnoder som möjliggör kontakt med de anslutna enheterna till systemet, här hittar vi periferienheter som nätverkskort, hårddiskspartitioner serieportar etc. Med kommandot man hier får man fullständig information om var filhierarkin innehåller. Som vi ser så är begreppet "fil" i UNIX mycket vidare än begreppet "fil" i Windows. I våra laborationer kommer vi att stifta bekantskap med UNIX filbegrepp och se att vi kan få mycket information från det. Kommandot ps (som visar information om processer) gräver direkt i filstrukturen under /proc/. På senare år har fokus mer hamnat på katalogen /sys/ eftersom så mycket bara skyfflats in i /proc/ så blev den till slut stor och ostrukturerad. Det kan hända att vi kommer att undersöka detta i kursens systemprogrammeringsdel. Vi går vidare till nästa begrepp: * En process är (ungefär) ett program som körs. Program finns ofta på datorns hårddisk i filer och man kan starta program genom att tex dubbelklicka på ikoner osv, men när ett program ligger på hårddisken är det inte en process. Det är först när programmet kör som man kan tala om en process. En process är alltså något som pågår. En aktivitet. (Vidare kan ett program starta flera processer, men mer om det senare.) johnnyp@kth.se 1(9)

Ytterligare förtydligande: En process pågår, ja, det är sant. Men det finns också många olika sorters processer, vi kommer att ge mening åt begreppen, demon-process, zombie-process, barn-process, föräldra-process, system-process, användar-process etc. Det finns en även en populär uppfattning om UNIX att "allt är filer", till och med processer är filer, ja, det är en sanning med modifikation, vi kan komma åt allt i ett datorsystem som som kör UNIX via noder i filhierarkin, alltså filerna, men det betyder inte att vi kan identifiera allt i ett UNIX-system som filer. Ett tydligt exempel på detta är processerna, ja, de finns alla representerade som filer under /proc/ men jag tycker att vi mer får anse det som en representation av processerna, det är inte processerna själv. En mer precis fomrulering av "Allt är filer i UNIX" skulle kunna vara att "man kan komma åt och påverka det mesta i ett UNIX-system via filhierarkin". Den sista termen var vad ett operativsystem var, vi avstår dock från precisering just nu (hela kursen kan ju uppfattas som en precisering av vad ett operativsystem är): * Ett operativsystem (OS) är ett grundprogram som startar då datorn slås på. OS administrerar alla resurser till datorsystemet. (Resurser: mus, skärm, minne, CPU, ja allt.) Alla program som använder någon av datorsystemets resurser måste ha en pågående dialog/korrespondens med OS. Systemprogrammering Relevanta manualsidor: fork(), wait(), exit(), getpid(), getppid(), execl(), execvp(). Då kommer vi in på kärnan i första delen av kursen: Systemprogrammering. Det som skiljer systemprogrammering från grundläggande programmering (som illustrerades i C-kursen) är att vi måste bli mycket mer medvetna om det omgivande systemet vi programmerar ju saker som har väldigt mycket med operativsystemet att göra. 1. Parallella processer Ett datorsystem är aktivt genom sina processer, program = fil på en disk = dött, som vi sagt tidigare. process = (ungefär) körande program i datorns minne = aktivt = lever. När en ny process ska skapas i UNIX används alltid ett systemanrop som heter fork() utom då init skapas. Det är bara genom fork() som man kan skapa en ny process i UNIX. (Ja, utom just init då ja.) Eftersom allt som händer i UNIX händer i processer så måste också anropet ske via en process, det vill säga, för att skapa en process måste en annan process anropa fork(). Den som gör anropet till fork() kallas då förälder (parent process) och den process som skapas kallas barn (child process). Det har vi hört tidigare, men här kommer något nytt som gäller UNIX: Barnprocessen är en kopia av föräldraprocessen i det att barnprocessens processbild är exakt likadan som föräldraprocessens processbild (process image). För att få barnet att göra något annat måste barnets processbild bytas ut, vilket vanligtvis sker, via ett annat mycket viktigt systemanrop som heter exec. En vanlig situation då en ny process ska skapas är då man via ett skal kör ett kommando, till exempel skalet bash ska köra kommandot ls -l. Det kan illustreras i fyra steg: johnnyp@kth.se 2(9)

I steg 1 ger användaren ett kommandot till skalet. Steg 2 är att skalet skapar en barnprocess med fork() som alltså är en kopia av skalet själv. I steg 3 byts barnprocessens processbild ut mot processbilden som hör till ls och sedan kör den. Skalet inväntar att barnprocessen (som förvandlats till ls) kört klart (en föräldraprocess behöver dock inte invänta en barnprocess). I steg 4 har barnprocessen kört klart och föräldraprocessen (skalet) är redo att ta emot ett nytt kommando. (Resutatet av kommandot ls -l är, som ni vet, en lista på filer och det resultatet har förstås presenterats på något sätt, men den presentationen är inte illustrerad ovan.) 2. Struktur på körande processer Som sagt kallas en skapad process för barn och en skapande process kallas förälder. Alla processer har ett processidentifikationsnummer och alla processer har en förälder, utom en som är urföräldern och det är process nr 1 som kallas init. Med kommandot ps kan man få ett utdrag över hur det ser ut bland de körande processerna, det kan se ut så här: $ ps -elfa F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 4 S root 1 0 0 75 0-517 - 2007? 00:00:10 init [2] 1 S root 2 1 0-40 - - 0 migrat 2007? 00:00:02 [migration/0] 1 S root 5 1 0-40 - - 0 migrat 2007? 00:00:08 [migration/1] 1 S root 6 1 0 94 19-0 ksofti 2007? 00:00:05 [ksoftirqd/1] PID = Process IDentification. PPID = Parent Process IDentification. Lägg märke till att init är förälder till många barn. Den har själv ingen förälder utan dess PPID sätts till 0. Processen init är den första process som startar då systemet startar. (Läs mer om detta i DeBlanches bok.) 3. Programmering av parallella processer i C Vi ska se på hur fork() med mera fungerar i C genom att studera en rad programexempel. fork1.c: #include <stdio.h> #include <stdlib.h> 0 #include <sys/types.h> 24427 #include <unistd.h> #include <sys/wait.h> int main(void) int pid; printf("%d\n", pid); (I de kommande programexemplena kommer alla #include-satser att utelämnas. Namnen på programmen kommer också att förekomma som kommentarer, typ /* fork2.c */ osv.) Vad gör ovanstående program? En variabel (pid) skapas, fork() anropas, returvärdet läggs i pid och sedan skrivs värdet på pid ut sedan avslutas programmet. Men hur ser körningen ut? Det är en utskrift av TVÅ olika värden? Hur kan det komma sig? Och vad betyder dessa värden? Jo, då fork() anropas så skapas som sagt en barnprocess. Efter anropet till fork() finns det alltså två parallella processer körandes, föräldern och barnet, båda dessa processer kör alltså printf()- satsen. De skiljs åt genom returvärdet från fork(), 0 returneras till barnet och till föräldern returneras det processid som barnet får. Det är alltså barnprocessen som skriver ut nollan och johnnyp@kth.se 3(9)

föräldern som skriver ut 24427 som alltså är barnets PID. Vi kan illustrera det med ett tidsdiagram: Vi tänker oss tidsaxeln som gåendes från vänster till höger. Då ser vi att printf() utförs av både föräldern och barnet. Här har vi även illustrerat de värden som kommer ut från de båda utskrifterna undertill. (24427 respektive 0.) En viktig observation: Variabeln pid finns i båda processerna, men den har olika värden i de olika processerna! I barnet gäller pid == 0 och i föräldern gäller pid == 24427! Så är det med alla variabler när man skapar ytterligare processer med fork(), varje variabel har ett eget förvaringsutrymme i varje process och varje barnprocess kan ha ett eget värde på de variabler som förekommer i programmet. Ett mer precist sätt att säga detta är att processerna har olika adressrymder. Adressrymderna är delar av processbilderna och eftersom barnet och föräldern är två olika processer med två olika processbilder så har vi alltså två olika adressrymder också. Det är genom returvärdet på fork() som vi kan skilja barn från förälder åt i C-koden och vi ska snart se hur det går till, men först ska vi titta på ytterligare ett exempel. Vi byter nu också ut deklarationen av processid:n till typen pid_t, som egentligen bara är ett annat namn för int. (Kan variera.) int main(void) /* fork2.c */ printf("%d\n", pid); printf("%d\n", pid); 0 0 24402 24403 0 24404 Detta exempel liknar det föregående fast vi upprepar anropet till fork() och gör ytterligare en utskrift av värdet på pid. Man skulle då kunna tänka sig att vi får 2x2 = 4 utskrifter, men vi har 6 stycken utskrifter som syns i programkörningen och tre är nollor, det skapas alltså tre barnprocesser. Hur är det möjligt? Vi gör ju bara två anrop till fork()då bör vi ju bara ha två barn... eller? Låt oss studera ett tidsdiagram för ovanstående program. johnnyp@kth.se 4(9)

För att inse att det verkligen blir tre barn som skapas ska vi minnas att efter det första anropet till fork() så skapas barn 1. Efter detta första anrop till fork() kör både föräldern och barn 1 vidare och skriver ut 24402 respektive 0. Sedan kör både föräldern och barn 1 nästa anrop till fork() vilket resulterar i att förälder skapar barn 2 och barn 1 skapar ett eget barn som alltså kan tolkas som barnbarn till föräldern. Härav har vi tre barn och alltså tre nollor i utskriften. Det sista exemplet är ganska rörigt, vi behöver finna ett sätt att programmera så att ett kodavsnitt i vårt program endast körs av en process. Vi kan åstadkomma detta på flera sätt, vi studerar först det enklaste som där vi använder att pid (returvärdet från fork()) är 0 för barnprocesserna och skilt från noll (=lika barnprocessens PID) i föräldrarna. Vi ser på exemplet nu int main(void) /* fork3.c */ if(pid == 0) printf("child: %d\n", pid); else printf("parent: child pid:%d\n", pid); Child: 0 Parent: child pid: 24478 if-satsen möjliggör den uppdelning i koden av "föräldrakod" och "barnkod" som vi beskrev tidigare. Nu är det lätt att se vad som sker i vilken process. Vi ser på ytterligare ett exempel: int main(void) /* fork4.c */ int tal = 1; if(pid == 0) printf("child: %d\n", pid); tal = 99; printf("tal [child]: %d\n", tal); else printf("parent: child pid:%d\n", pid); printf("tal [parent]: %d\n", tal); Child: 0 Tal [child]: 99 Tal [parent/child]: 99 Parent: child pid:15424 Tal [parent]: 1 Tal [parent/child]:1 printf("tal [parent/child]: %d\n", tal); Vi har här följt uppdelningen ganska strikt, det finns egentligen bara en kodrad som både förälder och barn kör och det är den näst sista (printf("tal [parent/child]: %d\n", tal);). Observera att den körs två gånger och första gången körs den innan kodrader i barnet körts klart. Så är det med parallella processer, vi kan inte säkert veta i vilken ordning satser hörande till olika processer ska köras. (Det är inte ens säkert att vi kan avgöra det genom att studera utskrifterna.) Vi ska nu studera ett exempel där vi är mycket noggranna med att dela upp körningen i barnkod och föräldrakod. Vi väljer att avsluta barnet innan vi ens kommit ut ur den if-sats som testar på returvärdet från fork(): johnnyp@kth.se 5(9)

int main(void) /* fork5.c */ if(pid == 0) printf("child: %d\n", pid); sleep (2); exit (0); //Här avslutas //barnprocessen helt. else printf("parent: child pid:%d\n", pid); wait(0); //Här inväntar //föräldern barnets avslut printf("child finished.\n\n"); Child: 0 Parent: child pid:17386 <paus i 2 sekunder> Child finished //Endast föräldern gör detta anrop Det syns inte i körningen ovan men barnet gör här en paus i 2 sekunder (sleep(2);) Det kan symbolisera ett stort beräkningsarbete som man väljer att lägga i en barnprocess. Det mest centrala här är två nya systemanrop: wait() och exit(). Med dessa anrop kan vi låta föräldern invänta barnets avslut och barnet gör avslut på ett kontrollerat sätt med exit(). Nytt tidsdiagram.: Varje process som kör kan liknas vid en "tråd" som vi lägger ut och genom att använda exit() (i barnprocessen) och wait() (i föräldern) knyter vi ihop de två trådarna till en igen. Detta symboliserar att vi har så att säga delegerat en uppgift till en barnprocess, inväntat ett färdigt resultat och stängt ner barnprocessen i god ordning när uppgiften (att sova i 2 sekunder) var fullbordad. Från och med nu ska vi alltid göra på det här viset: en barprocess som startats ska avslutas och inväntas (och/eller kommuniceras med), det är endast så som en uppföljning kan ske och uppföljning bör ofta ske, föräldraprocessen har ju ett syfte med att starta en barnprocess och behöver veta om syftet är uppfyllt eller inte. (Utan wait() blir barnet en zombie/defunct.) I nästa exempel ska vi införa en ännu mer formell programmeringsstil som vi hädanefter varmt rekommenderar. Alla anrop speciellt fork() och wait(), (egentligen också, exit(), printf() och sleep()) är så kallade systemanrop. Vi begär något av operativsystemet. Ibland kanske inte OS:et kan leverera det vi begär och då måste vi hantera felet. Alla systemanrop returnerar ett värde som indikerar om det har gått bra eller ej. Om fork() returnerar -1 så har den misslyckats och man ska, vid korrekt systemprogrammering, (som ni ska lära er i denna kurs), ta hänsyn till det fall då ett systemanrop misslyckas. i vår fullt utbyggda formella stil ser programmet ut så här: johnnyp@kth.se 6(9)

int main(void) /* fork6.c */ switch(pid) case -1: fprintf(stderr,"fork failed"); exit (1); I am the child with pid = 20886 My parent has pid = 20885 I am the parent, my child's pid = 20886 My pid = 20885. case 0: printf("i am the child with pid = %d\n", getpid()); printf("my parent has pid = %d\n", getppid()); sleep (1); exit(0); default: wait (0); printf("i am the parent, my child's pid = %d\n", pid); printf("my pid = %d.\n\n", getpid()); //Endast föräldern gör detta anrop Här har en switch-sats använts för att skapa väldig klarhet kring vilken kod som hör till barn eller förälder. Vidare ser vi felhanteringen överst där programmet bryter med exitkod 1 (=parametern till exit()). 4. Ordning och reda i systemprogrammeringen I fortsättningen kommer vi att kräva att ni kontrollerar resultatet av vissa systemanrop. Vi kommer inte att kräva att ni kontrollerar och felhanterar alla systemanrop (som tex printf()) men definitivt fork(). Detta är mycket viktigt av flera skäl. För det första måste ni klart kunna skilja ut vad som är ett systemanrop, vad det betyder och vad som inte är ett systemanrop. För det andra kräver felhanteringen av systemanrop att man vet hur det gick, då måste man ha testat returvärdet. Felhantering av systemanrop är en ny typ av felhantering som är mycket viktig att behärska vid systemprogrammering. Vi ska nu se på ett avslutande exempel som visar ett av UNIX exekveringskommandon, execl(). Det finns flera kommandon av denna sort, execv(), execlp(), etc, men alla har egentligen samma underliggande exec-systemanrop. Se manualsidorna för en fullständig förteckning. Vi ska återknyta lite till början av denna föreläsning för att beskriva vad det är som exec-kommandona egentligen gör. Som tidigare nämnt, när UNIX skapar en ny process genom skalet så skapas den först som en kopia av det körande skalet. Därefter ersätts den skapade skalprocessen med den process som ska köras. Man brukar säga att operativsystemet "replaces current process image with a new process image" (citat från Advanced Linux Programming) vilket betyder ungefär att systemet byter ut den skapade skalprocessen i minnet med den nya processen som ska köras och lägger in den nya, som ska köras, i minnet istället för den som skapade den nya processen. Detta är också fallet när en annan process (än ett skal) skapar en ny process och det är precis det här "utbytandet av processbild" som exec-kommandona gör. Vi ser på ett program som kör ls i en barnprocess. Barnprocessen (som skapats med hjälp av fork() och som alltså är en kopia av C-programmet själv) byts alltså ut mot ls. Här är koden: johnnyp@kth.se 7(9)

int main(void) /* fork7.c */ switch(pid) case -1: fprintf(stderr,"fork failed"); exit (1); Child with pid = 6410 I'm running ls: FL4fork7.c a.out Parent, my child's pid = 6410 My pid = 6409. case 0: printf("child with pid = %d\n", getpid()); printf("i'm running ls:\n"); execl("/bin/ls", "ls", NULL); sleep (1); //Fråga: kommer sleep att utföras? exit(0); default: wait (0); printf("parent, my child's pid = %d\n", pid); printf("my pid = %d.\n\n", getpid()); //Endast föräldern gör detta anrop Kommentar: execl() är ett systemanrop som alltså behöver kontrolleras... det blir din uppgift på övningen att göra detta. Ni får även undersöka detta mer noggrant på övningen. Vi ser på ett tidsdiagram som beskriver detta program: Vi ser lite olika utskrifter. Barnet (som är kopia av föräldern) utplånas helt från minnet och ersätts av processen ls. Allt som händer sedan är att ls kör och ger en listning av filerna. Listningen är delen FL4fork7.c a.out av körningen som visas till höger om källkoden ovan, två filer finns tydligen, dels ett C-program och dels ett körbart program (det är programmet själv respektive programmets källkod). OBS: Föräldern väntar i alla fall på att ls ska bli klar. Utskrift 1 kommer allra sist. (execl() visas i grått och ls i en ruta för att symbolisera att ls tar över platsen helt där tidigare barnprocessen fanns.) Hur är det med satserna sleep(); och exit(0);? (I tidsschemat ska det vara bara ls, inte ls -l.) 5. Rester efter körande barnprocesser: Zombier En process måste alltid avslutas på ett medvetet sätt. Detta är av administrationsskäl: det måste finnas en punkt då en process anses ha kört klart så att operativsystemet kan avallokera administrationsresurserna (processkontrollblock etc.) på ett solitt sätt. En process kan avsluta sig själv genom att göra exit(), det har vi sett ovan, eller. En process avslutas alltid med en så kallad "exitkod", ett värde som fångas upp som en rapport om utfallet av körningen. I korthet brukar "0" i exitkod betyda att processen lyckats och värden skilda från 0 indikerar någon johnnyp@kth.se 8(9)

form av fel. En barnprocess som avslutats ska normalt inväntas av sin förälder, föräldern ska göra wait() eller motsvarande. Det blir en signal till operativsystemet att städa upp efter en avslutad process. MEN det finns tillfällen då det här inte sker. Det kan vara av flera skäl: 1. Föräldraprocessen avslutas abrupt/oväntat, kanske genom ett segmenteringsfel (pekarfel, kanske scanf() med glömd adressoperator (alltså &-tecknet)). 2. Den som programmerat föräldraprocessen har glömt att göra wait(). 3. Den som programmerat föräldraprocessen har skrivit ett program som visserligen gör wait() men på grund av något annat programmeringsfel så utförs inte wait(). Icke-inväntade barn som kört klart, alltså hunnit göra exit() (eller return eller liknande) som ligger kvar i minnet kan inte längre utföra någonting. Vidare är de inte inväntade vilket betyder att OS inte rensar efter dem. De är alltså varken döda eller levande, man brukar kalla processer i detta tillstånd för zombier eller defunct-processer. Dessa tar upp utrymme och detta ska undvikas. Om ett program genererar mycket zombier måste det skrivas om och göras bättre. Vi ser på ett exempel: zombiecreator.c int main(void) switch(pid) case -1: fprintf(stderr,"fork failed"); exit (1); break; case 0: printf("i am doing exit(), so I become a Zombie.\n"); exit(0); default: printf("while I'm sleeping, my child [%d] is a Zombie.\n", pid); sleep(20); wait(0); printf("now my child is a Zombie no more.\n"); printf("but I'm back for some more sleep...\n"); sleep(20); Vi kommer att undersöka detta program på övningen och rita ett tidsdiagram då. Vi kan bara nöja oss med att konstatera att då barnet gör exit() blir det en zombie under de 20 sekunder som föräldern inte inväntar det. johnnyp@kth.se 9(9)