Linköpings Tekniska Högskola Institutionen för Datavetanskap (IDA), Software and Systems (SaS) (c) Klas Arvidsson Introduktion Det första du behöver veta om Pintos är att det är ett riktigt operativsystem, det är inte ett labskelett (även om vissa kommentarer är tillagda i 2009 års version). Varje rad i Pintos har ett funktionellt syfte även i versionen du startar med. Det finns inga spår av den rätta lösningen (däremot finns kod studenter ofta tror är sådana spår och använder fel för att de inte tar reda på vilken funktion koden egentligen har.) Du kan alltså inte ta en snabb titt på koden och dra slutsatser som det är nog här vi skall lägga till vår kod. Den informationen är helt enkelt inte där, och det finns inte en rätt lösning, det finns många, och de är alla olika. Meningen med laborationerna är att du skall komplettera Pintos med kod som gör att systemanropen fungerar, och korrigera en del kod som är avsiktligt dåligt implementerad. Du kommer mest att lägga till kod som måste samarbeta med resten av systemet, men även ta bort kod som du ersätter med bättre, eller gör onödig. För att din kod skall samarbeta bra med resten av systemet krävs att du förstår hur de centrala bitarna i Pintos fungerar. Det värsta du kan göra är att gissa. En ändring kan kräva att du granskar koden i fem andra filer för att säkerställa att du inte bryter mod intentionerna i resten av systemet. Om du bryter mot intentionerna i resten av systemet blir det ofta jobbiga fel. Pintos är skrivet modulärt. Varje fil innehåller funktioner som implementerar ett delsystem, och koden är relativt välkommenterad. Ytterst är det kommentarerna och koden som är den fullständigt korrekta dokumentationen av hur det verkligen fungerar. Bläddra i koden för att hitta de funktioner som du behöver för att lösa uppgifterna, och läs på hur den kod du tänker dig att använda fungerar. Ibland tillåter vi att du gör saker på dåliga sätt (det är första gången du kodar ett operativsystem), men många hack är förbjudna. Känner du dig osäker på om lösningen du tänker dig är OK, rådfråga assistenten. Det är alltid absolut förbjudet är att använda busy-wait, läcka minne, och att stänga av interrupts när det går bra att lösa problemet med lås. Förord OBS! VIKTIGT! Som sagt kommer denna lab-instruktion utvecklas parallellt med att lab-serien framskrider. Denna kommer även att skrivas på svenska. Du som vill veta vad som skall göras och förberedas i god tid rekommenderas att följa förra årets instruktioner. Du som gillar det senaste, inte har problem med barnsjukdomar, och vill kunna påverka rekommenderas att följa denna 2011 års lab-instruktion. Du kan redan från start se ungefär i vilka steg laborationerna är uppdelade och planera därefter. Du kan naturligtvis referera till 2009 års instruktion när du vill även om du följer denna. Oavsett vilken instruktion du följer skall slutresultatet vara att alla systemanrop fungerar stabilt under en längre tids körning. Det som räknas från kursens sida är inte att eventuella test fungerar, utan att koden är skriven på ett tydligt och korrekt sätt. (Testfall kan fungera trots felaktig kod för obskyra fall. Korrekt kod kommer alltid klara alla testfall.) Page 1
För den otålige Eftersom många säkert börjar läsa här ger jag först rådet att läsa introduktionen ovan. Inför utsikten att behöva läsa en lång instruktion för att utföra en uppgift kommer de flesta att lägga instruktionen åt sidan och börja med att fråga vad de skall göra och hur det skall göras. Så här kommer den korta versionen. Du skall implementera följande systemanrop i Pintos. halt - stänger av datorn Pintos kör på exec - startar en ny process från given kommandorad exit - avslutar en process wait - väntar på att en vis process blir klar create - skapar en ny fil med angivet namn och storlek open - öppnar en fil givet ett namn read - läser från öppnad fil eller tangentbordet (stdin) write - skriver till öppnad fil eller skärmen (stdout) close - stänger en öppnad fil remove - tar bort en fil givet ett namn seek - sätter läs och skrivposition i öppnad fil tell - hämtar läs och skrivposition i öppnad fil filesize - hämtar storleken på öppnad fil Det är allt. Om du nu frågar hur skall jag göra? eller var skall jag skriva koden? så är det precis det du skall lära dig i kursen. Läs resten av denna instruktion, läs originaldokumentationen till Pintos, läs kommentarerna i koden, och läs koden. Förhoppningen är att denna instruktion så småningom skall innehålla det du behöver bör att lista ut ett sätt att lösa uppgifterna. Tycker du någon sektion är tvetydig, otydlig eller oklar på något sätt är det bäst att diskutera med assistent innan du gör missförstår eller gör fel, och mail med förslag till förbättringar är alltid välkomna. TODO STUB anger delar som kommer kompletteras. Not. anger potentiella tillägg. emacs setup för Pintos Behändiga makron DUMP(intvar) printf( %s = %d\n, #intvar, var) C-Primer via LiU-bibl, fulltext online? Förväntad tidsåtgång Tydliggör uppgift: uppgift, saker som kan vara bra att veta Figurer, grafer Page 2
Introduktion till C med Pintos-perspektiv Många av de problem föregående års studenter råkar ut för härstammar från misstag och bristande förståelse av programspråket C. Det vanligaste tankesättet är det kompilerar, alltså är det rätt eller det fungerar, alltså är det rätt. Tyvärr är inget av detta sant, och många gånger blir den upptäckten som en kniv i ryggen efter många timmars felsökning. Missuppfattningen det kompilerar, alltså är det rätt C är ett språk som tillåter dig som programmerare att göra i princip vilka knasigheter som helst utan att generera fel vid kompilering. Om du som programmerare inte skrivit koden helt explicit genereras en varning (om du har tur), men det är i regel allt du kan hoppas på. Skriver du koden explicit, t.ex. med explicita (kanske felaktiga) typomvandlingar får du inte ens en varning. Du är gud över koden och därmed kompilatorn. Du bestämmer vad som är rätt. Är du minsta osäker på någon detalj gäller det alltså att kontrollera hur det skall vara. Bättre spendera en timme med att ta reda på hur det skall vara, än spendera åtta timmar med att felsöka. Det gäller även att vara mycket uppmärksam på kompileringsvarningar. Ofta är de oviktiga, men lika ofta signalerar de direkta felaktigeter. Grundregeln är att om du inte förstår varför en varning uppstår skall du ta reda på det och rätta koden. Rekommendationen är att koda bort alla varningar. Annars är det lätt hänt att missa en viktig varning bland en lång lista på oviktiga. Missuppfattningen det fungerar, alltså är det rätt När du skriver vanliga program fungerar operativsystemet bara i viss mån som en sandlåda. Du har inte tillgång till privilegierade instruktioner. Du kan inte läsa och skriva var du vill i minnet. Om du gör grova fel med någon pekare får du segmentation fault. Men gör du små (men allvarliga) fel, t.ex. indexerar utanför en array eller använder avallokerat minne så kommer programmet verka fungera tills minnet precis efter arrayen används av någon annan variabel, eller det avallokerade minnet återanvänds till något annat. Symtomen på att det är fel kan alltså visa sig långt senare. I Pintos skriver du dessutom operativsystemet. Där har du tillgång till allt (existerande) minne. Skriver du till fel adress är alltså chansen större att allt kommer verka fungera. Tills det som den adressen egentligen används till behövs. Om du inte helt förstår hur koden du skriver samarbetar med resten av Pintos är risken att allt till en början verkar fungera. Men ett par dagar senare, när du skrivit flera dussin nya rader och startar ett nytt testfall, då krashar Pintos. Och inte på grund av de två dussin nya rader du just skrev, utan på grund av det du skev i förrgår och trodde var rätt. Testa ofta och noga Se till att testa din kod ofta och noga. Försök skapa testfall var varje möjlig exekveringssekvens. Ibland är det lättare att testa delsystem utanför Pintos med ett eget testprogram. Felsök systematiskt. Skriv ut värdet på alla variabler som kan påverka, även de som du vet är rätt. Skriv noggranna felmeddelanden med mycket information, kalle1, kalle2 osv är inte bra felutskrifter. Det bör framgå av varje utskrift vilken tråd som exekverar, vilken källkodfil, funktion och rad det är, samt namn och värde på de närmast använda variablerna. Det gäller att hitta den information som inte är som förväntat. Var förutsättningslös. Blir det fel, oavsett hur omöjligt felet är, så är det fel, och det är i din kod felet finns även om symtomen uppstår djupt inne i någon befintlig funktion. Använd backtrace för att spåra sådant tillbaka till din kod. Uppgifterna som följer har flera syften. Dels introducerande övningar i hur en del vanliga saker görs i C. Dels få lite bakgrundsförståelse inför de senare uppgifterna. Dels ha skrivit och testat kod som kan återanvändas som delsystem i Pintos. Page 3
Deluppgift 1 Viktigaste grunderna i C Saker som kan vara bra att känna till För att kompilera använder du gcc med följande flaggor: gcc -Wall -Wextra -std=c99 assignment1.c Programmet kommer heta a.out om du inte döper om det med flaggan -o Uppgift I denna uppgift kommer du att använda flera av de vanligaste satserna, typerna och operatorerna. Du skall skriva ett program som utför följande: 1) Deklarerar ett heltal, ett flyttal, ett tecken, en text-sträng och en array med 5 heltal. Initiera alla variabler direkt vid deklaration. En text-sträng, eller C-sträng, är en pekare till en sekvens av tecken. Det är inte givet på förhand hur lång sekvensen är. Istället är det givet att sekvensen är minst ett tecken, där det sista tecknet alltid måste vara det så kallade noll-tecknet ( \0 ). En tom sträng innehåller alltså endast ett nolltecken. Nolltecknet brukar inte räknas med när längden på en sträng beräknas. En tom sträng har alltså längden 0, även fast sekvensen för att lagra den är ett tecken lång. [Not: #define SIZE 5] 2) Använd funktionen printf från standardbiblioteket (man -s3c printf) för att skriva ut namn, adress, och värde på varje variabel. Adresserna skall skrivas ut hexadecimalt, alltid med åtta siffror (0x00010a08). Skriv ut innehållet i arrayvariablerna med formatet %p (dvs skriv ut adressen till första värdet i arrayen, pekaren som anger var sekvensen värden startar, inte hela arrayen). Strängen skriver du även ut med formatet %s som hanterar text-strängar speciellt. Detta har direkt relevans för debugutskrifter i Pintos. [Not: printf exempel, array vs pekare exempel, &array, array, &array[i], array[i], C-sträng] 3) Använd en while-loop och pekarstegring för att skriva ut varje tecken i strängen. Först skall adressen där tecknet är lagrat skrivas ut, sedan själva tecknet, ett tecken per rad. Med pekarstegring avses att du varje var i loopen ökar en startpekare att peka till nästa position i en array. Innehållet på adressen hämtas som vanligt med * operatorn. 4) Använd en for-loop och indexering för att skriva ut varje tecken i heltals-arrayen på samma format som tecken arrayen. Med indexering avses att du använder indexoperatorn []. 5) Skapa ett funktion, du kan kalla den getline, som tar emot en array av tecken samt ett heltal som anger arrayens storlek. Funktionen skall läsa in en rad med tecken från tangentbordet med funktionen getchar från standardbiblioteket (man -s3c getchar). Rader avslutas med tecknet för nyrad ( \n ). Tecknen skall lagras i arrayen. Om arrayen är för liten avbryter du inläsningen av raden i förtid. Avsluta i alla lägen arrayen med ett nolltecken. Denna del har direkt relevans i ett av de systemanrop du senare kommer implementera. [Not: array som parameter] 6) Använd funktionen från föregående steg till att läsa in en sträng, och använd funktionen atoi från standardbiblioteket (man -s3c atoi) för att konvertera den inlästa strängen till ett heltal. Avrunda sedan heltalet till närmaste högre heltal som är jämnt delbart med fyra. Modulusoperatorn (%) för att få resten vid division kan vara användbar, men tänk till så du inte avrundar i onödan eller avrundar nedåt. Avrundningen har direkt relevans för senare stackhantering. Page 4
Deluppgift 2 Gränssnittet till main Saker som kan vara bra att känna till I C har funktionen main enligt standard två inparametrar. Den första är ett heltal, och den andra en array där varje position är en teckensträng (pekare till en sekvens med tecken). Arrayen skall enligt standard avslutas med en NULL-pekare. Detta gör att första parametern som anger längden på arrayen (exklusive avslutande NULL-pekare) inte nödvändigtvis behöver användas för att stega igenom hela arrayen. Innehållet i arrayen bestäms av vad som skrivs på kommandoraden i terminalen. Varje ord, inklusive namnet på programmet, kommer ta upp ett index i arrayen. Det betyder att varje program har ett argument, programmets namn, på position 0 i arrayen. Enligt C standard skall main också returnera ett heltal. Heltalet noll (0) signalerar att main avslutade normalt utan fel. Övriga returvärden är upp till programmeraren att ge en betydelse. För att från terminalen se vilket varde som returnerades från main (eller gavs som parameter till exit (man -s3c exit)) skriver du kommandot echo $?. Observera att inga andra kommandon kan köras emellan, eftersom miljövariablen $? endast visar returvärdet från senast körda kommando. Observera också att returvärdet representeras med endast 8 bitar (0-255). Uppgift Skriv ett program som summerar alla tecken i kommandoraden som gavs till main. Skriv ut varje parameter och den accumulerade summan enlig nedan exempel. Skriv även ut summan modulus 256 för att kunna jämföra med echo $?. OBS: accumulerade totala summan. [Not: Otydligt?] Körexempel: > a.out testing one two three a.out : 487 testing : 1253 one : 1575 two : 1921 three : 2457 sum = 2457 (153) > echo $? 153 Page 5
Deluppgift 3 Starta nya processer Det givna programmet fork-wait.c som finns på kurshemsidan innehåller kod för att starta ett nytt program i Solaris (det går till på detta sätt i alla UNIX varianter), samt vänta på att det skall köra färdigt. Ni kommer att implementera motsvarande funktionalitet i Pintos senare i form av systemanropen exec, exit och wait. I det givna programmet behövs två funktioner, fork och execv, för att starta en ny process. Ni kommer att spara lite jobb genom att implementera kombinationen av båda direkt i systemanropet exec. Studera koden. Kompilera och kör det givna programmet. Följ instruktionerna som skrivs ut: > gcc -Wall -Wextra -std=c99 -g fork-wait.c -o fork-wait > fork-wait a.out testing one two three Prova istället starta en terminal: > fork-wait /usr/openwin/bin/xterm Redovisa svar på följande frågor: 1) Kör ptree $USER i den terminalen som startades av fork-wait. Vad hände med den andra fork-wait som blev barn till den första? (Tips: Jämför processnumren.) 2) När du kör fork-wait exemplet med programmet från föregående uppgift kommer barnprocessen normalt att avsluta före innan föräldern (fork-wait) ens har börjat vänta. Men trots att barnet redan avslutats och inte finns mer så skrivs summan som returnerades ut korrekt. Ge förslag på hur operativsystemet (som anropas av waitpid) kan tänkas göra så att det fungerar. 3) När du kör fork-wait med en xterm så avlutas inte barn-processen (xterm) förrän du skriver exit i fönstret. Det sker oftast långt efter att föräldern (fork-wait) är klar med sitt. Trots det skrivs inte resultatet från barnet ut förrän barnet (xterm) avslutats. Ge förslag på hur operativsystemet (som anropas av waitpid) kan tänkas veta när resultatet från angivet processid är tillgängligt. 4) Kör fork-wait med en xterm, men avbryt fork-wait genom att trycka Control-C innan fork-wait anropar waitpid, och kör sedan kommandot pwait -v <pid> för att vänta på att xtermen avslutar (ersätt <pid> med barnets processnummer). Gör samma sak igen, men avsluta xtermen innan du kör pwait. Denna gång får du ett felmeddelande. I bägge fallen kommer föräldern aldrig att vänta på att barnet blir klart (avbryts innan waitpid). Ge förslag på hur operativsystemet kan bära sig åt för att veta hur länge waitpid skall ge rätt resultat. Glöm inte att ta hänsyn hur det fungerade i föregående två frågor, speciellt fråga 2). [Not. Skriv om. Vänta 4s*5 i förälder. Hur länge behålla exit värde? Dokumentera resultat för varje steg: vilken ordning skedde följande: barn avslutades, förälder anropar wait, förälder fortsätter efter wait, förälder avslutar, gav wait rätt resultat?] Page 6
Deluppgift 4 Använda den dubbellänkade listan i Pintos I Pintos finns en länkad lista (src/lib/kernel/list.h). Det är bra att veta hur den används. Den finns tillgänglig att använda utanför pintos via kurshemsidan. I nedan uppgift skall du använda denna lista till att implementera Erathostenes primtalssåll. Kod för att implementera sållet finns given nedan så du kan koncentrera dig på att skapa den struct som behövs, hur du använder malloc och free (man - s3c malloc), samt hur listan fungerar. Det är bättre att göra fel i ett litet program utanför Pintos än att göra fel i Pintos. Saker som kan vara bra att känna till Listan du skall använda är lite speciell. I grunden är det en dubbellänkad lista. För att undvika specialfall i början används ett dummy header-helement, och för att undvika motsvarande specialfall i slutet används ett dummy tail-element. Funktioner finns färdiga för att initiera listan, sätta in element först, sist, sorterat eller unikt, iterera genom listan, ta bort element osv. Både headerfilen och C-filen innehåller användbara kommentarer. Det som är speciellt är listelementen som bygger upp listan. De är deklarerade som: struct list_elem { struct list_elem *prev; /* Previous list element. */ struct list_elem *next; /* Next list element. */ }; Som du ser finns alltså en next-pekare och en prev-pekare. Men det finns ingen plats för data. Listan lagrar ingenting. Nu tycker du kanske det är ganska puckat, att implementera en lista som inte lagrar någon data. I själva verket är det ett genidrag. Listan är nu helt generell. Istället för att bara kunna lagra ett fördefinerat data kan den lagra vad som helst. Utan att listfunktionerna behöver anpassas. De ser ju överhuvudtaget inga data. Det man gör för att lagra något i listan är följande: OBS! Var försiktig med att klippa klistra kod. En del tecken konverteras fel. 1) Skapa och initiera en lista struct list my_list; list_init(&my_list); 2) Bestäm vad du vill lagra (i detta exempel C-strängar, char*) 3) Skapa en struct som innehåller din data och ett listelement. struct string_elem { char* string; struct list_elem list_pointers; }; 4) Allokera och initiera ett av dina listelement. Page 7
struct string_elem* my_string_elem = (struct string_elem*)malloc(sizeof(struct string_elem)); my_string_elem->string = I rule the Pintos list 5) När du vill sätta in ditt element i listan skall du istället sätta in medlemmen med listpekarna (ovan kallat list_pointers). list_push_front(&my_list, &my_string_elem->list_pointers); 6) När du hämtar ut data från listan kommer du att få en pekare till det du satte in, dvs en pekare till medlemmen med listpekarna. Denna kan konverteras till din struct-typ (här struct string_elem) via funktions-makrot list_entry. Makrot list_entry tar emot den pekare till struct list_elem som skall konverteras, datatypen på det vi vill konverteras till, och vilket variabelnamn struct list_elem har i den struct vi vill konvertera till. Sedan utförs några magiska beräkningar för att hitta adressen till den önskade datatypen. En typisk kodsekvens ser ut som följer: struct list_elem* have = NULL; struct string_elem* want = NULL; have = list_pop_front(&my_list); /* get and remove from list */ want = list_entry(have, struct string_elem, list_pointers); /*... */ free(want); /* want was malloced before insertion */ 7) När du tar bort saker ur listan, var noga med att de är bortlänkade ur listan innan du avallokerar minne. Om du tar bort saker inuti en loop som itererar över listan, var noga med att ta tillvara på resultatet från list_remove, nästa iteration måste utgå från det värdet eftersom det borttagna inte är giltigt att använda i list_next. Följande kod tar bort vissa element ur en lista: for (e = list_begin(&my_list); e!= list_end(&my_list); ) { struct string_elem *s; s = list_entry (e, struct string_elem, list_pointers); } if (strcmp(s->string, to remove ) == 0) { e = list_remove(e); free(s); } else { e = list_next(e); } Page 8
8) Vill du använda funktionen för att sätta in sorterat behöver du skapa en funktion för att jämföra två listelement a och b. Funktionen skall returnera sant om a är mindre än b. Genom att bara skriva namnet på funktionen kan man skicka en pekare till funktionen den som parameter. (Via pekaren anropas den sedan internt i list_insert_ordered). En tredje parameter (aux) kan användas på vilket sätt man vill vid jämförelsen, eller inte alls (det är parametern aux till list_insert_ordered som skickas vidare till jämförelsefunktionen). bool less(struct list_elem* a, struct list_elem* b, void* aux) { struct string_elem* sa; struct string_elem* sb; sa = list_entry (a, struct string_elem, list_pointers); sb = list_entry (b, struct string_elem, list_pointers); return (strcmp(sa->string, sb->string) < 0); } list_insert_ordered(&my_list, &to_insert->list_ponters, less, 0); Uppgift [Not. Ge kod nedan som fil. Skriv om kompilering av separat fil] Algoritmen du skall implementera (för att göra något med listan) är som följer. Fokus i denna uppgift ligger på att öva listhantering, pekarhantering och minneshantering. Ersätt kommentarerna nedan med kod: /* skapa och initera en lista */ for (i = 2; i < N; ++i) { /* sätt in talet i sorterat listan */ } for (i = 2; i < N; ++i) { for (j = i*2; j < N; j += i) { struct list_elem *e; /* Stega igenom listan och ta bort varje element som är jämt delbart med j. Glöm inte avallokera minne. */ } } /* skriv ut alla tal i listan */ /* töm listan och avallokera alla element */ Page 9
Deluppgift 5 Accesskontroll Sceanrio Advokaterna inom en advokatfirma delar på ett dokumentarkiv. Arkivet består av 100 arkivlådor. Varje låda rymmer 100 dokument. Varje dokument är unikt numrerat efter var det lagras. All numrering startar på 0. Arkivlåda noll (0) innehåller dokument numrerade 000-099, arkivlåda 1 dokumenten 100-199 och så vidare. 50:e dokumentet i arkivlådan med nummer 31 blir alltså dokument nummer 3150. En advokat har hand om många olika fall, och varje fall består av många olika dokument. Ett problem är att fallen är konfidentiella, vilket betyder att endast den advokat som har hand om fallet skall ha tillträde till dokumenten. För att förenkla administrationen av arkivet är det organiserat på sådant sätt att varje arkivlåda endast innehåller dokument hanterade av en advokat. För att en advokat skall få tillgång till en uppsättning dokument måste denne skicka en begäran som innehåller numret på det första dokument i ett fall, samt hur många dokument fallet innehåller. Alla dokument som tillhör ett fall förutsätts ligga sekventiellt. Om till exempel 7 dokument med start på 005 begärs så skall dokumenten 005, 006, 007, 008, 009, 010, och 011 levereras (notera att 005+7=012 inte skall vara med). Om advokaten inte minns hur många dokument som ingår i fallet kan han begära att få alla efterföljande dokument, några i taget, tills han hittar det sista dokumentet. Om något av de dokument en advokat begär inte tillhör denna så skall inga av dokumenten i begäran dokument levereras, och begäran skall loggas. Advokatbyron avser införa ett mekaniserat, datorstyrt, system för accesskontroll och dokumentleverans. Det är givet att följande funktioner existerar: /* Return true if the document may be accessed. Note that this is a very slow function. Call it as few times as you possibly can. */ bool belongs_to(int lawyer_index[], int document_number); /* Return the number of the first document in the same cabinet as docyument_number */ int first_document_in_cabinet(int document_number); /* Returns true if the lawyer say that this was the last document he needed (for requests with only starting document) */ bool is_end_of_request(int document_number); /* The number of documents in each cabinet */ #define CABINET_SIZE 100 /* Evaluates how good your algorithm is */ void start_evaluate_algorithm(int start, int size); void evaluate(bool result); void end_evaluate_algorithm(); Page 10
Uppgift Implementera funktionerna för accesskontroll. Detta har direkt relevans för Pintos. Fundera på hur. Det är givet att du får använda ovan funktioner. Eftersom vi bara testar funktionerna i simulerad miljö kan du använda NULL som argument till lawyer_index. De två funktioner du behöver skriva har följande deklaration: bool verify_fix_length(int start, int length); bool verify_variable_length(int start); Infoga anrop av dem på angiven plats i den givna koden för att testa din lösning. Överkurs: Om du är finurlig kan du även skriva dem som en funktion som tar som parameter en pekare till en funktion som returnerar om slutet av intervallet hittats. [Not. Vad gör belongs_to?, uppdatera given kod, hur kompilera?, testningen är simulerad] Page 11
Deluppgift 6 Enkel associativ kontainer (Objekt -> Id, Id -> Objekt) Uppgift Du skall implementera en program-modul som fungerar som en associativ data-container där data kan stoppas in för lagring. Varje data som stoppas in associeras med ett id, eller en nyckel, unikt för det datat. Nyckeln används sedan för att kunna hämta ut det datat ur containern. Denna container kan senare användas för att hålla reda på filer och processer. De funktioner och datatyper som behövs (skall användas) för att implementera modulen beskrivs nedan. En program-modul består av två filer. En header-fil (här map.h) som specificerar (deklarerar) de datatyper och funktioner modulen tillhandahåller, och en implementationsfil (här map.c) som definerar varje funktion. #define PANIC() exit(1) En pre-processor definition som kan användas om något går allvarligt fel. Denna finns i Pintos (men avslutar lite annorlunda). /* datatype of one object */ #define OBJECT void För att enkelt kunna byta ut den datatyp som lagras defineras en symbol OBJECT för att representera den typ som verkligen kommer att lagras. Endast pekare till denna datatyp skall lagras. Funktionerna nedan hanterar som du ser OBJECT*. struct map; /* stores all objects in list or array */ En datatyp för att representera hela containern (med alla object). Måste alltid skickas med som parameter till funktionerna som manipulerar containern. För att representera containern internt har du huvudsakligen två aternativ. Alternativ 1: Du kan definiera en array som lagrar OBJECT-pekare. Här kan nyckeln (id) sättas till indexet i arrayen för att direkt kunna komma åt ett objekt. Tomma positioner kan representeras med NULL. Fördelen är att den är relativt enkel att implementera, och går snabbt att indexera (jämför med hashtabell). Nackdelar är den begränsade storleken och att oanvända element slösar utrymme (dock endast pekare i detta fall). #define SIZE 128 struct map { /* array av storlek SIZE som lagrar pekare till * OBJECT (läs deklarationen baklänges) */ OBJECT* content[size]; }; Page 12
Alternativ 2: Du kan låta struct map bestå av en länkad lista, t.ex. Pintos lista, genom att definiera en lista i structen, och ett listelement med OBJECT* och id. Varje OBJECT* som lagras tilldelas ett id från en räknare och läggs in i listan. /* struct to store each item in a linked list */ struct internal_element_array { int id; /* nyckel för data i detta element */ OBJECT* data; /* pekare till datat */ /* list-element för att ha att sätta in i listan */ struct list_elem elem; }; struct map { /* listan med alla lagrade data */ struct list content; /* nästa aldrig använda id räknas upp vid insättning */ int next_id; }; /* Initialize the map to be empty */ void map_init(struct map* m); Oavsett representation måste alla datamedlemmar i struct map initiearas till kända värden som ger en tom container. /* Insert one object in the map and return an integer that * can be used to uniquely identifying (retrieve) it later */ int map_insert(struct map* m, OBJECT* o); Vid insättning anges objekt-pekare som skall sättas in, och ett id (nyckel) som unikt representerar objekt-pekaren returneras. Returnerat id används sedan för att hitta objektet vid sökning och borttagning. /* Find (and return) the object identified by id in the map * The object will remain in the map. */ OBJECT* map_find(struct map* m, int id); Vid sökning används ett id för att hitta och returnera objektet-pekaren som knutits till det id t. Objekt-pekaren ligger kvar i containern. /* Remove (and return) the object identified by id from the map * The corresponding id will not be mapped to any object now Page 13
*/ OBJECT* map_remove(struct map* m, int id); Vid borttagning används ett id för att hitta och returnera objektet-pekaren som knutits till det id t, och objekt-pekaren tas bort ur containern. Att objekt-pekaren returneras gör att minnet kan återlämnas med free om så behövs. /* Execute function exec for each object in map. */ void map_for_each(struct map* m, void (*exec)(object*, int aux), int aux); En funktion som går igenom alla objekt i containern, och för varje (giltigt) objektpekare O anropar funktionen exec enligt exec(o, aux). Funktionen exec skrivs av programmeraren som anropar map_for_each (använder modulen, containern) och skickas med som parameter vid anrop av map_for_each. Funktionen exec skall ha två parametrar, en objekt-pekare och ett heltal. Heltalsparametern aux kan implementatöre av exec använda helt efter behov. T.ex. för att söka endast vissa objekt, eller för att öka eller dra ifrån ett tal från alla objekt. Det är inte strikt nödvändigt att implementera map_for_each, men om du förstår att använda funktionspekare effektivt kan du ha god nytta av det i Pintos. /* Make the map empty. */ void map_make_empty(struct map* m); En funktion som tömmer containern. Om minnet för de lagrade pekarna behöver frigöras kan detta göras via ett anrop av map_for_each innan anrop av map_make_empty. Tänk på att kontrollera att givna argument till funktionerna är giltiga. Anges felaktiga indata, t.ex. ett id som inte finns, till någon funktion betraktas det som fel och PANIC() anropas. Utöver ovanstående funktioner behöver du även skriva ett testprogram för din container (map_test.c) som inkluderar header-filen för de moduler det använder. Vid kompilering anges implementationsfilerna för både test-programmet och de moduler det använder: gcc -Wall -Wextra -std=c99 -pedantic map_test.c map.c Testprogram Ett exempel på testprogram delvis skrivet med pseudo-kod (dvs ej komplett): #define OBJECT int /* ändras i map.h */ #include <stdio.h> #include map.h void do_free(int* o, int aux) { free(o); } void print_less(int* o, int aux) Page 14
{ } if (*o < aux) printf( %d, *o); int main() { struct map container; int* obj; int id; map_init(&container); printf( insert values: ); while (... ) { obj = malloc( sizeof(int) ); scanf( %d, obj); id = map_insert(&container, obj); } while (... ) { printf( enter id to find value for: ); scanf( %d, &id); obj = map_find(&container, id); printf( found: %d\n, *obj); } while (... ) { printf( enter id to remove value for: ); scanf( %d, &id); obj = map_find(&container, id); printf( removed: %d\n, *obj); free(obj); } map_for_each(&container, print_less, 10); map_for_each(&container, do_free, 0); map_make_empty(&container); } return 0; Page 15
Deluppgift 7 Felsökning med debugger Uppgift Observera att du behöver standardversionen 3.4.6 av gcc. Om du i tidigare kurs installerat en nyare eller äldre version måste du återställa till standardversionen: module list 1) unix/local 2) unix/std... 9) prog/gcc/4.2.0 10) unix/sfw module rm prog/gcc/4.2.0 module add prog/gcc Notera även att debuggerrn inte klarar hantera brytpunkter i implementationsfiler vars namn innehåller mellanslag. Byt ut mellanslag mot understrykningstecken (_). Följande program finns bland de givna filerna på hemsidan. Programmet innehåller dock ett allvarligt fel. Felsökning kommer bli en stor del av Pintos-laborationerna. Börja med att kompilera programmet: gcc -Wall -Wextra -std=c99 -pedantic -g debug.c Starta sedan programmet med hjälp av debuggern gdb. Det finns en grafisk front-end ddd, men effektivast är att använda text-mode gdb: gdb a.out Debuggern startar, läser in programmet a.out och presenterar en kommandoprompt (gdb). Skriv run (istället för a.out) för att starta programmet. (Om main förväntar sig argument kan de anges som vanligt efter run.) Programmet kommer nu att krascha. Skriv bt eller backtrace för att ser hela kedjan av funktionsanrop som ledde fram till krashen. Med större program är denna information ovärderlig, men i detta fall finns bara main. Vill du nu undersöka värdet på olika variabler kan du använda kommandot display. Några exempel: (gdb) display *bufi 1: *bufi = 0x0 (gdb) display bufi 2: bufi = (char **) 0xffbfe25c (gdb) display bufend 3: bufend = (char **) 0xffbfe25c (gdb) display *(bufend-1) 4: *(bufend - 1) = 0xffbfe268 "sihtgubed" Page 16
Som du ser kan man skriva komplicerade uttryck för att undersöka innehållet av pekare. Skriv nu kommandot break 12 för att stoppa programmet på rad 12 vid nästa körning. Starta om programmet genom att skriva run och bekräfta. Programmet stannar vid rad 12, och visar alla displayuttryck. Skriv undisplay för att inte visa uttrycken igen. För att fortsätta programkörningen kan du nu skriva next, nexti, step, stepi eller continue. Om du bara trycker enter upprepas föregående kommando. Använd help för att se skillnaden på de olika stegningarna (help next). Rita exekveringen med alla arrayer och pekare på papper. Försök använd debuggern för att verifiera sådant du är osäker på. När du hittat felet, fundera på vad som skrivs ut. Rätta felet och prova. int main() { char str[] = "sihtgubed"; char *stri = &str[8]; char *buf[9]; char **bufi, **bufend; bufi = buf; bufend = &buf[9]; while (bufi!= bufend){ *bufi = stri; bufi++; stri--; } while (bufi!= buf){ *(*bufi) -= 32; bufi--; } } while (bufi!= bufend){ printf("%c", **bufi); bufi++; } För att använda debuggern med Pintos krävs några ytterligare steg för att komma igång. Eftersom Pintos exekverar i en emulator (qemu) måste en anslutning upprättas mellan emulatorn och debuggern. Detta återkommer vi till när Pintos är installerat. Page 17
Deluppgift 8 Lägga upp argument på given adress Saker som kan vara bra att känna till När ett program anropar en funktion sker ofta överföring av information till och från funktionen, så kallad parameteröverföring samt returvärde. Parametrar lagras vanligen på en stack. Varje process (eller tråd om processen är flertrådad) har en stack, i Pintos är det ett minnesutrymme som startar på adressen PHYS_BASE och växer nedåt (mot lägre minnesadress) i minnet varefter mer plats behövs. En pekare, stackpekaren, håller reda på var i minnet stacken slutar just nu. I Pintos finns den i (x86) processorregistret ESP. Ett funktionsanrop går till som så att först kopieras sista argumentet till den adress stackpekaren anger, stackpekaren räknas sedan ned, näst sista argumentet kopieras dit, och så vidare tills första argumented kopierats. Första argumentet hamnar alltså på lägsta adressen av alla argument. Härnäst räknas stackpekaren ned ytterligare en gång och dit kopieras återhoppsadressen (var exekveringen skall fortsätta då funktionen är klar). Returvärden från funktioner placeras i (x86) processorns EAX register. (Kompilatorn ser till att EAX är ledigt när funktionen anropas.) Denna procedur gäller för alla funktionsanrop, inklusive systemanrop och anrop av programmets "startfunktion" _start. Startfunktionen läggs i Pintos in automatiskt av kompilartorn och fungerar som en "wrapper" till main och ser ut som följer (i Pintos, se lib/user/entry.c): _start(int argc, char* argv[]) { exit(main(argc, argv)) } Observera två saker: 1) De båda parametrarna till _start; argc och argv, kommer alltid att läsas (för att kopieras som argument till main). Detta sker alltså oavsett om main använder sina parametrar eller ej. Om stacken är tom när detta sker kommer processen försöka läsa parametrarna "ovanför" sin stack och ett page fault erhålls. 2) _start kommer aldrig att returnera, eftersom systemanropet exit alltid körs innan dess. Detta betyder att återhoppsadressen normalt aldrig kommer användas. Dock är det bra om returadressen är NULL ur felsäkerhetssynpunkt (ett specialdesignat eller felaktigt program skulle kunna nå dit). Funktionen _start är speciell på så sätt att den startas av operativsystemet. Eftersom _start tar emot två argument (samma som main) måste operativsystemet se till att dessa är korrekt initierade. Detta är lite svårare än det först verkar. Den andra parametern till main, ofta kallad argv, är en pekare till en sekvens med teckenpekare. argv måste naturligtvis peka till en giltig sekvens av teckenpekare, som vardera i sin tur måste peka på en giltig sekvens med tecken. Alla dessa teckenpekare och tecken måste lagras någonstans. De kan teoretiskt sett lagras var som helst i processens minne, men mest praktiskt brukar vara att lagra även dessa på stacken. Observera att det är förbjudet att läsa 32-bitars tal eller pekare från en adress som inte är jämnt delbar med fyra. Detta ger vid försök i Solaris "bus error". Det betyder att man måste vara noga med att placera sådana data på jämnt delbara adresser när de läggs på stacken. Page 18
Data att lagra i argv hämtas från kommandoraden. Det första ordet på kommandoraden (programmets namn) lagras i argv[0], det andra ordet i argv[1], o.s.v. Orden separeras med blanka tecken. Antalet ord lagras slutligen i argc. Standardfunktionen för att beräkna längden av C-strängar (pekare till konstanta tecken där sista tecknet är noll-teckenet) heter strlen och finns i <string.h>. Notera att strlen inte räknar med det avslutande noll-tecknet, men att en extra byte lagringsutrymme krävs för detta i alla C-strängar. Uppgift Du skall skriva ett program som läser in en "kommandorad", allokerar minne för en teoretisk stack, och placerar kommandoraden på denna stack så som funktionen main förväntar sig. Kopiera filen setup-argv.c från kurshemsidan för att få mer ledning. (Denna kod förs senare över till Pintos.) Som exempel på hur stacken skall se ut kan du studera följande exempel: Kommandorad: " this will be arguments to main " (34 + 1 byte) Punkt används här för att ange noll-tecknet.. => \0 Adress och innehåll anges hexadecimalt. Adress Innehåll Datatyp -------- -------- --------- C0000000 <PHYS_BASE i pintos kernel space, oläsbar> BFFFFFFC n... char (x4) BFFFFFF8 mai char (x4) BFFFFFF4 to. char (x4) BFFFFFF0 nts. char (x4) BFFFFFEC gume char (x4) BFFFFFE8 e.ar char (x4) BFFFFFE4 ll.b char (x4) BFFFFFE0 s.wi char (x4) BFFFFFDC thi char (x4) <rad STR> BFFFFFD8 00000000 (char*) BFFFFFD4 BFFFFFF9 (char*) BFFFFFD0 BFFFFFF5 (char*) BFFFFFCC BFFFFFEA (char*) BFFFFFC8 BFFFFFE7 (char*) BFFFFFC4 BFFFFFE2 (char*) BFFFFFC0 BFFFFFDD (char*) <pekar till t på rad STR> BFFFFFBC BFFFFFC0 (char**) <pekar till raden ovanför> BFFFFFB8 6 (int) BFFFFFB4 00000000 (void (*)(void)) <funktionspekare> Överkurs: Den som vill vara lite mer avancerad kan ta bort dubblerade blanksteg i kommandoraden för att spara ett par byte på stacken. Page 19
Deluppgift 9 --- Installera och använda PINTOS --- Uppgift Läs först följande avsnitt och utför sedan de kommandon som krävs för att du skall installera Pintos och bekanta dig med hur det används och ser ut. Lägga till de verktyg som behövs för pintos För att få tillgång till diverse script och verktyg som behövs för att kompilera, starta och hantera pintos behöver du installera modulen /home/tddi04/labs/modules/pintos. Så här långt kommen i din utbildning bör du vara van att hantera modulsystemet, men för säkerhets skull kommer det här igen. För att lägga till modulen, använd kommandot: module add /home/tddi04/labs/modules/pintos För att lättare hantera olika kodversioner och kunna spara arbetet efter varje delsteg behöver du subversion: module add prog/subversion Observera att dessa module-kommandon måste göras i varje terminal (kommandoskal) där du vill ha åtkomst till pintos. För att få systemet att automatiskt lägga till modulen i varje terminal efter återinloggning använder du som vanligt initadd istället för add. Observera att om du gör initadd på en felaktigt specificerad modul är det stor risk att du saboterar filen.login i din hemkatalog. Kontrollera därför alltid att add fungerar som förväntat innan du gör initadd. Bläddra i Pintos källkod Den version av Pintos som laborationerna utgår från finns tillgänglig på kursens hemsida i katalogen: /home/tddi04/labs/skel/pintos/src/ Denna version är read-only. Du har alltså alltid ursprungsversionen tillgänglig att jämföra med även efter att du tagit bort eller ersatt kod i din version. För att enkelt kunna använda emacs och bläddra i koden är en fil TAGS skapad i src katalogen ovan. Det innebär att du genom att placera markören vid ett funktionsanrop i koden och trycka M-. (Meta-punkt) i emacs kan hoppa direkt till definitionen av motsvarande funtion. Första gången du använder detta måste du hjälpa emacs att lokalisera TAGS-filen. Kopiera källkoden Kör först följande rader. För att automatisera dem för senare inloggningar kan du lägga in dem i filen.cshrc.private i din hemkatalog: setenv PINTOSURI file://$home/svnpintos setenv PINTOSSKEL /home/tddi04/labs/skel/pintos alias make gmake setenv PAGER less Detta är i korthet de kommandon som behövs för att skapa ett eget svn-repository med Pintos: Page 20
svnadmin create $HOME/svnpintos svn import $PINTOSSKEL $PINTOSURI -m "original pintos source" chmod -R o+rx $HOME/svnpintos setfacl -s u::rwx,g::---,o:--- $HOME/svnpintos setfacl -r -m g:tddi04:r-x $HOME/svnpintos setfacl -r -m u:$user:r-x $HOME/svnpintos Den sista raden kan upprepas med olika användare som skall ha tillträde till ditt repository. I vanliga fall är det bara din labpartner. För att sedan skapa en lokal arbetskopia av pintos gör du följande: svn checkout $PINTOSURI/src $HOME/pintos chmod og-rwx $HOME/pintos cd $HOME/pintos set-svn-ignore Arkivera ändringar och klara uppgifter Nu skall du har en pintos-katalog i din hemfolder där du skall göra alla ändringar. Du har även en svnpintos-katalog som du inte skall röra. När du är färdig med en uppgift arkiverar du den i repositoryt med kommandon: cd $HOME/pintos svn -m "uppgift 10 klar" commit Generera en ny TAGS fil För att kunna använda M-. i emacs krävs en TAGS-fil som är uppdaterad med senaste ändringar. För att generera en ny TAGS-fil kan du använda följande kommandon: cd $HOME/pintos find. -name *.c -o -name *.h etags - Ytterligare subversion-kommandon Följande kommandon utgår från att du står i arbetskatalogen $HOME/pintos. För att se vilken revision du är på eller vilka revisioner som finns, prova: svn up svn log För att lista alla ändringar efter revisionen REV, prova: svn diff -x -wup -r REV För att lära dig mer om olika svn-kommandon, prova: svn help [kommando] Page 21
Ytterligar information och tutorials finns tillgängliga på Internet. Förbered testprogram Öppna programmet examples/sumargv.c och kontrollera vad det gör. Lägg till följande if-sats efter deklarationerna om den inte redan finns: /* if we do not have a valid argv, exit with error code 111 */ if (argv == 0) return 111; Kompilera och kör Pintos Följande kommandon utgår från att du följde ovan instruktioner till punkt och pricka. Om du inte gjorde det kan du behöva justera kommandon eller sökvägar nedan. Observera att sista kommandot nedan är radbrutet här, men skall skrivas på en rad i terminalen. cd $HOME/pintos gmake -C examples gmake -C userprog cd $HOME/pintos/userprog/build pintos-mkdisk fs.dsk 2 pintos -v -p../../examples/sumargv -a sumargv -- -f -q run sumargv När du kör Pintos enligt ovan kommer mycket status-information att skrivas ut. Var alltid mycket uppmärksam på eventuella felmeddelanden. Nedan är denna information uppbruten i delar med några korta kommentarer. Följande text beskriver status för uppsättningen av emuator och initial disk: Copying../../examples/sumargv into /tmp/mdkezta8gz.dsk... Writing command line to /tmp/lrhpdzcy5z.dsk... qemu -hda /tmp/lrhpdzcy5z.dsk -hdb fs.dsk -hdc /tmp/mdkezta8gz.dsk -p 1234 -m 4 -net none -nographic -monitor null Därefter kommer Pintos boot-meddelanden: Kernel command line: -f -q put sumargv run sumargv Pintos booting with 4,096 kb RAM... 375 pages available in kernel pool. 375 pages available in user pool. Calibrating timer... 2,454,400 loops/s. hd0:0: detected 121 sector (60 kb) disk, model "QEMU HARDDISK", serial "QM00001" hd0:1: detected 4,032 sector (1 MB) disk, model "QEMU HARDDISK", serial "QM00002" Page 22
hd1:0: detected 79 sector (39 kb) disk, model "QEMU HARDDISK", serial "QM00003" Filsystemet formateras när flaggan -f anges, och filer kopieras in då flaggor -p, -a angavs: Formatting file system...done. Boot complete. Putting sumargv into the file system... Så följer starten av den första processen (run sumargv). Eftersom Pintos inte är fullt funktionellt ännu kommer inte så mycket att utföras: Executing sumargv : Executing a system call! Stack top + 0: 1 Stack top + 1: 111 sumargv: exit(0) Execution of sumargv complete. De två översta värdena på user-stacken skrivs ut. Kan du lista ut var det ena värdet kommer från? Kan du lista ut vad det andra är? (Studera koden för sumargv och fundera på vilket systemanrop som utförs då main returnerar. Det bör du kunna svara på sedan tidigare uppgifter.) Förutsatt att funktionen för att vänta på att en process avslutar är korrekt implementerad kommer Pintos vänta på att första procesen returnerar. Därefter skrivs avslutande statusinformation ut: Timer: 91 ticks Thread: 0 idle ticks, 87 kernel ticks, 5 user ticks hd0:0: 0 reads, 0 writes hd0:1: 53 reads, 166 writes hd1:0: 79 reads, 0 writes Console: 806 characters output Keyboard: 0 keys pressed Exception: 0 page faults Om flagga -q angavs kommer Pintos nu stänga av emulatorn (datorn). Observera att detta inte är önskvärt om funktionen för att vänta på en process inte fungerar, eftersom emulatorn (datorn) då stängs av så fort första processen lagts på ready-kön (skapats och är redo att starta). Powering off... För att få mer information om vilka flaggor som kan användas och hur de fungerar kan du skriva: pintos --help Page 23
Det är även användbart att känna till lite om emulatorn qemu som är den emulator vi använder. Här följer några tangentbordskombinationer som kan vara användbara. Ytterligare information kan den intresserade hitta på http://www.qemu.org/. <Ctrl-Alt> i det grafiska fönstret tar eller släpper kontroll över tangentbord och mus <Ctrl-a x> Avslutar emulatorn. Kan behöva tryckas flitigt för att ge effekt. Felsökning med debugger Att använda debugger kräver ungefär samma kommandon som att köra utan. I en terminal startar du Pintos: cd $HOME/pintos/userprog/build pintos-mkdisk fs.dsk 2 debugpintos -v -p../../examples/sumargv -a sumargv -- -f -q run sumargv I en annan terminal startar du debuggern: cd $HOME/pintos/userprog/build pintos-gdb kernel.o Du får upp en hel del text när debuggern startar, följt av en prompt där du skriver debugpintos: GNU gdb 6.6 Copyright (C) 2006 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "--host=sparc-sun-solaris2.10 -- target=i386-elf"... (gdb) debugpintos Debuggern skriver ut lite frågetecken. Det är OK, då har den anslutit till emulatorn. Skriv break main för att skapa en initial brytpunkt: 0x0000fff0 in?? () (gdb) break main Debuggern bekräftar att den är med på noterna. Fortsätt programkörningen med continue: Breakpoint 1 at 0xc0100008: file../../threads/init.c, line 68. (gdb) continue Page 24
Pintos kommer nu köras fram till brytpunkten. Nu kan du sätta valfria brytpunker eller använda valfria kommandon i debuggern för att utföra felsökning. I detta exempel nöjer vi oss med att fortsätta tills Pintos avslutar, och därefter stänga av debuggern. Continuing. Breakpoint 1, main () at../../threads/init.c:68 68 { (gdb) continue Continuing. Watchdog has expired. Target detached. (gdb) quit Du kan själv undersöka vilka felsökningsmöjligheter som finns. Här följer några av de vanligaste kommandona du kan använda i debuggern: help help bt help next backtrace bt next nexti step stepi break clear delete display undisplay Page 25
Deluppgift 10 Starta den första processen Saker som kan vara bra att känna till Ett operativsystem startat med något som brukar kallas boot-sekvens. Det är en benämning på allt som händer när operativsystemt startar. Kod för operativsystemest skall läsas från disk och startas, systemets olika moduler skall initieras (trådhantering, minneshantering, diskhantering etc.) och tillgänglig hårdvara skall detekteras och initieras. I Pintos sker dessa initieringssteg i filen threads/ init.c. När du vill lägga till egna datastrukturer eller medlemmar i befintliga datastrukturer är det bra att känna till var och i vilken ordning olika moduler initieras. Du kan till exempel inte allokera minne med malloc förrän minneshanteringsmodulen är initierad. När systemet är initierat är det dags att starta den första processen. Detta sker genom anrop av funktionen process_execute som finns i filen userprog/process.c. Denna fil innehåller allt som har med processhantering att göra. I Pintos är en process nästan synonym med en kerneltråd, beroende av att varje process drivs av en kernel-tråd. Varje process har alltså exekt en kernel-tråd. Däremot är inte alla kerneltrådar proceser. Funktioner för trådhantering finns i filen threads/thread.c och motsvarande header-fil. Trådmodulen är den viktigaste delen i Pintos. Pintos från start till slut, grov översikt Detta är huvuddragen av hur Pintos exekverar i ordning från start till slut. Mycket är naturligtvis utelämnat: thread_init (); /* initiera trådsystem */ malloc_init (); /* dynamisk minnesallokering */ timer_init (); /* generera regelbundna avbrott */ intr_init (); /* avbrottshanterare */ kbd_init (); /* tangentbord */ syscall_init (); /* systemanrop */ thread_start (); /* skapa idle-tråd */ disk_init (); /* diskhantering */ /* starta och vänta på första processen * den ny processen läggs på ready-kön * kan börja exekvera vilket ögonblick som helst * kan också dröja länge innan den startar exekveringen */ pid = process_execute (task); /* vänta på att ovan process blir klar * om denna inte fungerar går Pintos direkt vidare och avslutar */ process_wait (pid); /* om flagga -q användes, stäng av datorn (emulatorn) */ if (power_off_when_done) power_off (); Page 26