TDDIU81 Processer och trådar Andreas Dahlberg, Jonathan Doherty, Tony Magnusson, Patrik Ottosson, Rasmus Siljedahl
Sammanfattning Den här rapporten innehåller en kort genomgång av allmän process och trådhantering i operativsystem. Sen beskrivs hur detta är implementerat i operativsystemet Pintos. Processer När en process skapas av parentprocess så får den nya childprocessen ett unikt process identifier(pid), för att underlätta identifieringen av alla processer. När den nuvarande childprocessen sedan skapar nya childprocesser blir den själv en parentprocess. När de körts klart så avslutas dom och tas bort med systemanropet exit(). Då avallokers allting som den processen använt av operativsystemet [1]. Det är lite skillnad på vad som händer när en process skapas i Unix jämfört mot Windows. I Unix skapas en ny childprocess med systemanropet fork(). För att parentprocessen lättare ska kunna kommunicera med den nya processen så innehåller den nya processen en kopia av adressutrymmet hos föräldern. Efter fork() så fortsätter båda två att exekveras [1]. I Windows skapas en ny process med funktionen CreateProcess() vilket påminner om fork() på det sätt att en ny process skapas av en parentprocess. Skillnaden mellan är att CreateProcess() måste ladda in ett program till adressutrymmet av childprocessen istället för att ärva adressutrymmet av sin förälder [1]. För att starta en process i Pintos så måste först en ny tråd skapas, det görs genom att kalla på funktionen thread_create(). Thread_create() skapar i sin tur en kernel tråd och en struct thread som sedan läggs till i queuen. Struct thread innehåller en page för thread stacken som är 4kb stor, samt relevant data. Då en process består av denna struct thread betyder det att struct thread är processens process control block (PCB). Det betyder även att struct thread representerar thread control block (TCB) på samma gång. Därav har Pintos en 1:1 mappning mellan user trådar och kernal trådar och PCB och TCB är representerade i samma data struktur. Skapningen av tråden fortsätter i funktionen start_process där en userprocess laddas och körs. Precis som i boken så har processen ett id men i Pintos är det samma som trådens id. En av de viktigaste funktionerna för att hantera processer är process_execute som finns i pintos, denna funktion exekverar det som står på command line. Detta gör den genom att skapa en ny process. När den ska skapar en ny process gör den det genom att skapa en ny thread och schemalägger att funktionen start_process ska köras. Funktionen start_process är i pintos en thread funktion som laddar användarens process och kör den. När en process ska avslutas så ska process_exit() köras i pintos och returnera statuskoden. Jämfört med boken Operating system concept då UNIX kör exit() utför den samma som pintos att returnera status men också ser till att tömma allt minne. I pintos utförs detta av en egen funktion process_cleanup. När exit() körs i UNIX, kan parent processen använda sig av wait() för att vänta på att child processen ska avslutas. I pintos finns motsvarande funktion process_wait() som väntar på att child processen ska dö och returnera exit status [2]. Funktionen process_cleanup är viktig när en process är klar och ska stängas ner eller om en process har kraschat. Den har till uppgift att städa upp den nuvarande processens resurser genom att befria minnet som den tar upp. 1
Varje gång en process ska utföra en context switch kallas funktionen process_activate, den har som uppgift att ställa in cpu till att köra user code, den laddar in page directory till cpu s page directory bas register. Som de beskriver i boken Operating system concept är det interrupts som orsakar en context switch. När detta sker utförs en state save som sparar den nuvarande processens värden för att senare kunna aktivera processen igen. När den aktiveras igen så kör den state restore och detta är motsvarigheten till process_activate i pintos [3]. Det sista som sker i process_activate är att den ställer tillbaka interrupt så att det återigen kan ske. Schemaläggning I Pintos initieras ready-kön i början av filen thread/thread.c. De funktionerna som används för att schemalägga proccesser är: thread_unblock() lägger den inmatade tråden i ready-kön och sätter trådens läge till THREAD_READY thread_block() låter den inmatade tråden vänta tills thread_unblock() anropas. Trådens läge sätts till THREAD_BLOCKED. Sen schemaläggs en ny tråd för körning. schedule() använder de två ovanstående funktionerna och byter till den nya tråden. schedule_tail() anropas i slutet av schedule() och sätter den nuvarande trådens läge till THREAD_RUNNING. Sen nollställs thread_ticks och proccessen aktiveras. Om den föregående tråden hade läget THREAD_DYING så frigörs dess minne. thread_yield() lägger den tråden som körs på ready-kön och sätter dess läge till THREAD_READY. Sen anropas schedule() för att starta en ny tråd. thread_tick() anropas via en interupt timer. Funktionen räknar upp en variabel som håller reda på hur många ticks tråden körts. User, idle och kerneltrådarna har varsin räknare. Om tråden har kört sina maximala tics anroppas thread_yield(). Antalet tics varje tråd får köra i ett sträck bestäms av konstanten TIME_SLICE. running_thread() hämtar den tråden som körs för tillfället. För att ta reda på vilken tråd som körs så tar man reda på vilken sida stackpekaren är i. Längst upp på sidan finns då struct thread som ger den information man vill ha. next_thread_to_run() hämtar nästa tråd att köras från ready-kön. 2
Bilden nedan visar vilka funktioner som flyttar trådar mellan olika lägen. Schemaläggningsalgoritmen som används är Round-robin. Round-robin schemaläggning fungerar som FIFO-schemaläggning fast med preemption. Varje tråd körs i ordningen som de läggs på ready-kön. Men varje tråd får bara köra under en förutbestämd tid åt gången. När tiden gått ut körs nästa tråd istället. När alla trådar körts börjar man om från början igen. Om en tråd blir färdig innan sitt tidsfönster har gått ut så släpper den CPUn och nästa tråd schemaläggs direkt. Round-robin är en simpel schemaläggningsalgoritm som är enkel att implementera men saknar vissa funktioner såsom prioritering. Det är viktigt att välja rätt storlek på tidsfönstret då det ha en stor inverkan på algoritmens prestanda. Ett för litet värde ger mycket overhead då byten mellan trådar sker väldigt ofta. Med ett för stort värde blir algoritmen i princip en FCFS istället. Trådhantering De states(waiting, running etc) som finns i pintos för trådar är liknande andra operativ som finns. Läser man boken så ser man att nästan alla operativ har likadana states, och det skiljer sig väldigt lite mellan olika operativsystem. Däremot finns det stora skillnader mellan hantering av dessa trådar. Kerneltrådar och usertrådar hanteras inte på samma sätt i olika operativsystem. I Pintos används en one-to-one model. Structen kernal_thread anropas i funktionen thread_create vilket ger att för varje process som startas, så startasen kernal thread som senare blir kopplad till en user thread som senare kör processen. Denna metod är en av de enklaste metoderna att implementera och är använt av till exempel win32 och linux. Nackdel är att det finns ett begränsat antal kernel threads. Det kan innebära att man inte kan starta upp vissa andra user threads, eftersom alla kernels är upptagna. I Pintos nuvarande implementation skapas en ny kerneltråd varje gång man skapar en ny tråd. Genom att modifiera nuvarande struct thread som i nuläget representerar både PCB och TCB och skapa en ny datastruktur för Process Control Block(PCB), kan man skapa nya Thread Control Blocks och lägga till dem till sin process(pcb) genom pekare. Då en ny user thread skapas lägger man till referensen till Process Control Blocket placerar den på kön, sedan när det är denna threads tur att exekvera använder den länken till sin Process Control Block för att få tillgång till processens resurser. Process Control Block bör flyttas till filen där processer hanteras och innehålla följande data: 3
--- PCB --- Tillgängligt minne Trådar Öppna filer Schemaläggn. Info Prioritet ID Referenslista 1. Abraham Silberschatz, Greg Gagne, Peter 3.3.1 Process Creation 90-95. 2. Abraham Silberschatz, Greg Gagne, Peter 3.3.2 Process Termination 95. 3. Abraham Silberschatz, Greg Gagne, Peter 3.2.3 Context Switch 89-90. Thread Control Block kan vara kvar i nuvarande struct thread med följande data: TCB Stack "fil.txt" MINNE FÖR RESULTAT LÄS FIL CPU Context/ Sparade reg. 4