Tentamen Datorteknik och realtidssystem, TSEA81 Datum 2018-08-24 Lokal TER1 Tid 14-18 Kurskod TSEA81 Provkod TEN1 Kursnamn Datorteknik och realtidssystem Institution ISY Antal uppgifter 5 Antal sidor 17 (inklusive denna sida) Kursansvarig Anders Nilsson Lärare som besöker skrivsalen Anders Nilsson Telefon under skrivtiden 013-28 2635 Besöker skrivsalen Cirka 15 och 17 Kursadministratör Jeanette Mathisen 013-28 1020 Tillåtna hjälpmedel Betygsgränser Inga Poäng Betyg 41-50 5 31-40 4 21-30 3 0-20 U Viktig information Alla svar ska ha en motivation om inget annat anges. Om du svarar med programkod räknas kommentarer i programkoden som motivation. Svar som ej är motiverade kan leda till poängavdrag Om inget annat anges ska du anta att schemaläggningsmetoden som används är priority based, preemptive, scheduling Om du är osäker på det exakta namnet för en viss funktion, skriv en kommentar om vad funktionen gör så kommer vi troligtvis att förstå vad du menar. (Detsamma gäller syntaxen för programspråket C.) Tänk igenom din lösning NOGGRANT och använd dig av de lösningsprinciper som kursen förevisar. Okonventionella och tvetydiga lösningar ger poängavdrag. Svara ALDRIG med pseudokod, om det inte specifikt efterfrågas. Pseudokod blir lätt tvetydig och därmed inte bedömningsbar. Skriv läsbart! Oläsbar text kan inte bedömas och ger därmed inga poäng. Lycka till! 1
2
Uppgift 1: Teori(8p) (a) (2p) Ibland används ett foreground/background-system som ett alternativ till ett realtidsoperativsystem med processer. Att använda ett realtidsoperativsystem med processer har fördelar och nackdelar jämfört med ett foreground/background-system. Ange minst en fördel och minst en nackdel. (b) (2p) I ett realtidsoperativsystem såsom Simple-OS startas en process/task genom att anropa funktionen si task create med adressen till den funktion som utgör själva processen. Varför kan man inte starta en process genom att bara direkt anropa själva process-funktionen? (c) (2p) Antag att en gemensam resurs används av två processer P1 och P2. Antag att den ena processen, P1, läser från den gemensamma resursen, och den andra processen, P2, skriver till den gemensamma resursen. En semafor, initierad till ett, används av P2 i dess kritiska region när data skrivs. Varför är det viktigt för P1 att använda en semafor även när data läses? Är semaforen nödvändig när data läses av P1 om P1 har högre prioritet än P2? Motivera ditt svar. (d) (2p) När man utför en Await-operation på en händelsevariabel krävs det vanligtvis att Await-operationen utförs inne i en while-sats. Beskriv den typiska situationen och förklara varför det är viktigt att använda en while-sats istället för t ex en if -sats. 3
Uppgift 2: Stark/svag semafor(10p) Betrakta följande program i Simple-OS. Antag att semaforerna fungerar på så sätt som beskrivits under kursens föreläsningar. Antag att prioritetsbaserad påtvingad schemaläggning används. #include <simple_os.h> #include <stdio.h> #define STACK_SIZE 5000 /* define task stack spaces */ stack_item p1_stack[stack_size]; stack_item p2_stack[stack_size]; si_semaphore S1; // define semaphore /* do some dummy work for an unspecified time */ void do_work() int i; volatile int dummy; for(i=0; i < 67000000;i++) dummy=i; /* task p1 */ void p1(void) si_sem_wait(&s1); printf("s\n"); si_wait_n_ms(2000); printf("e\n"); si_sem_signal(&s1); printf("m\n"); do_work(); printf("a\n"); si_sem_wait(&s1); printf("f\n"); si_wait_n_ms(2000); printf("o\n"); si_sem_signal(&s1); printf("r\n"); while(1); // wait on semaphore // sleep for 2000 ms // signal on semaphore // do some work for some time // wait on semaphore // sleep for 2000 ms // signal on semaphore // wait for ever 4
/* task p2 */ void p2(void) si_wait_n_ms(1000); printf("r\n"); si_sem_wait(&s1); printf("t\n"); do_work(); printf("o\n"); si_sem_signal(&s1); printf("s\n"); while(1); // sleep for 1000 ms // wait on semaphore // do some work for some time // signal om semaphore // wait for ever /* main program */ int main(void) /* initialise simple OS kernel */ si_kernel_init(); /* initialise semaphore to 1 */ si_sem_init(&s1, 1); /* create tasks */ si_task_create(p1, &p1_stack[stack_size-1], 10); // high priority si_task_create(p2, &p2_stack[stack_size-1], 20); // low priority /* start the kernel, also starting tasks */ si_kernel_start(); return 0; (a) (8p) Antag att programmet ovan använder sig av starka semaforer. Beskriv steg för steg vad som händer och vad som skrivs ut från det att de båda processerna p1 och p2 är körklara. Var noga med att tala om vilka processer som är körande, vilka listor dom ligger i vid olika tillfällen samt motivera varför olika händelser sker. Listornas exakta namn är inte viktigt, bara det framgår vad deras syfte är. (b) (2p) Antag att programmet ovan använder sig av svaga semaforer. Vad blir den resulterande utskriften när programmet kör? 5
Uppgift 3: Monitor(12p) Man önskar ta fram ett realtidsprogram i C som simulerar det klassiska problemet med ätande filosofer. Filosoferna ägnar sitt liv åt att äta och att filosofera om vartannat. Programmet ska uppfylla följande krav: Filosoferna sitter vid ett runt bord och har var sin tallrik med mat samt var sin gaffel. För att äta måste dock en filosof ha två gafflar, dvs förutom den egna gaffeln, som ligger till vänster, måste en filosof använda gaffeln från sin högra bordsgranne. Det finns totalt N FORKS gafflar. När en filosof ska äta får hen bara ta upp en gaffel åt gången, dvs om filosofen börjar med att ta upp vänster gaffel, så måste någon annan ha möjlighet att ta upp eller lägga ned en gaffel, innan filosofen tar upp den högra gaffeln. Om en filosof bara får tag i en gaffel vid ett försök att börja äta så måste den gaffeln läggas tillbaka innan ett nytt försök görs. Det får inte uppstå deadlock eller svält i systemet. Programmet får inte vara onödigt processorintensivt. En filosof ska alternerande äta och filosofera, men får inte övergå till att filosofera om hen inte först ätit. När filosofen filosoferat ska hen övergå till att äta. En filosof får inte blockera någon annan filosof från att börja äta då det är möjligt. Det tar fyra sekunder för en filosof att äta, samt fyra sekunder för en filosof att filosofera. I övergången från att filosofera till att äta kan det dock ta en obestämd tid beroende på hur snart filosofen får tag på två gafflar. Programmet ska lösas med en monitor, innehållande en semafor och en händelsevariabel. Utöver detta får monitorn förses med nödvända variabler för att lösa uppgfiften. Din uppgift är att skriva filosofprocessen philo task, och deklarera samt initiera monitorn. Det finns ett huvudprogram main som ser ut enligt följande: int main(void) /* initialise kernel */ si_kernel_init(); /* create tasks */ int i; for (i=0; i<n_forks; i++) si_task_create(philo_task, &Philo_stack[i][STACK_SIZE-1], 10+i); /* start the kernel */ si_kernel_start(); 6
/* will never be here */ return 0; Du får inte ta bort något av huvudprogrammet main, men eventuella nödvändiga tillägg får göras. Nödvändiga programbibliotek antas vara inkluderade och nödvändiga definitioner för det givna huvudprogrammet antas vara gjorda. Du får deklarera och lägga till egna funktioner. Följande funktioner i Simple-OS finns tillgängliga: /* signal operation on semaphore sem */ void si_sem_signal(si_semaphore *sem) /* wait operation on semaphore sem */ void si_sem_wait(si_semaphore *sem) /* broadcast operation on cv */ void si_cv_broadcast(si_condvar *cv) /* wait operation on cv */ void si_cv_wait(si_condvar *cv) /* makes the calling process wait n_ms milliseconds */ void si_wait_n_ms(int n_ms) /* initialisation of semaphore sem */ void si_sem_init(si_semaphore *sem, int init_val) /*initialisation of condvar cv */ void si_cv_init(si_condvar *cv, si_semaphore *mutex) Glöm inte att programkoden måste vara kommenterad för full poäng. 7
Uppgift 4: Periodiska processer(10p) Ett realtidssystem med tre periodiska processer, P1, P2 och P3, ska schemaläggas. Följande krav gäller: P1 ska arbeta/köra under 3 tidsenheter i tidsintervallet [i9, (i + 1)9], där i är ett heltal och i 0. P2 ska arbeta/köra under 2 tidsenheter i tidsintervallet [i6, (i + 1)6], där i är ett heltal och i 0. P3 ska arbeta/köra under 1 tidsenheter i tidsintervallet [i3, (i + 1)3], där i är ett heltal och i 0. Varje process aktiveras (är körklar) vid startpunkten för respektive tidsintervall. Det innebär att P1 aktiveras vid var 9:e tidsenhet, med start vid tidpunkten 0 och P2 aktiveras vid var 6:e tidsenhet, med start vid tidpunkten 0, och P3 aktiveras vid var 3:e tidsenhet, med start vid tidpunkten 0. Varje process har en deadline vid slutet av respektive tidsintervall. Det innebär att P1 har en deadline vid var 9:e tidsenhet, med början vid tidpunkten 9 och P2 har en deadline vid var 6:e tidsenhet, med början vid tidpunkten 6 och P3 har en deadline vid var 3:e tidsenhet, med början vid tidpunkten 3. Tänk på att för varje deluppgift nedan tydligt visa och motivera hur du kommer fram till svaret. Rita en tidslinje där det framgår när och hur länge respektive process kör och var de har sina deadlines. För följande deluppgifter, avgör om processerna uppfyller kraven. Om de uppfyller kraven ange var det finns outnyttjad processortid. Om de inte uppfyller kraven ange vid vilka tidpunkter processerna missar sina deadlines. Eventuellt missade deadlines medför att det arbete som inte utförts inom ett tidsintervall förkastas, dvs det ackumuleras inte till kommande tidsintervall. Ange också den faktiska utnyttjandegraden under ovanstående förutsättningar och krav. (a) (5p) Antag att schemaläggningen Earliest Deadline First (EDF) används. (b) (5p) Antag att schemaläggningen Rate monotonic Scheduling (RMS) används. 8
Uppgift 5: Skapa och starta process(10p) Betrakta situationen i figur 1 nedan. Den visar TCB (Task Control Block) för en icke körande process med tillhörande minnesutrymme. Vi kan kalla denna process för P0. Beskriv de olika steg som genomgås för att skapa och sedan starta ytterligare en process P1. Tänk på att i din beskrivning ta med vilka listor som är aktuella, hur minnet påverkas, hur och vilka delar av processorn som används. De olika stegen måste beskrivas i rätt ordning för full poäng. Figur 1: En process, icke körande. 9
Lösningsförslag fråga 1: 1a En fördel med ett RTOS är att med processer blir programmet mer modulärt och lättare att underhålla. En annan fördel är att processorn kan utnyttjas mer effektivt då ett RTOS typiskt växlar till en annan process när en process blir väntande. En nackdel med ett RTOS är att risken för deadlock ökar. En annan nackdel är att systemet blir svårare att analysera och felsöka. 1b Om själva process-funktionen anropas, istället för att startas (med si task create), så kommer process-funktionen bara att köras och inte bli en del av de parallela processerna i systemet mellan vilka en schemaläggare kan växla. 1c När P1 läser kan den bli avbruten, och om läsningen inte föregåtts av en Waitoperation som sätter semaforen till 0, kan den avbrytande processen P2 fortsätta förbi sin Wait-operation, modifiera gemensamma data, vilket kan göra det lästa datat inkonsistent för P1 om läsningen inte är atomär. I ett scenario där den läsande P1 har högre prioritet än den skrivande P2 kan P2 avbrytas. Om läsningen i P1 då inte föregås av en Wait-operation så kan P1 läsa ej färdigskrivna data, och datat kan bli inkonsistent för P1. 1d När en Await-operation utförs på en händelsevariabel (vi kallar den C), så blir den anropande processen väntande på C. Vid ett senare tillfälle kan samma process aktiveras (blir redo att köra) via en Cause-operation på C. När processen sedan blir körande, för att en annan process gjort en Signal-operation (på en semafor associerad med C), så fortsätter processen efter anropet till Await. Eftersom det inte går att veta om det villkor som orsakade Await-operationen har ändrats utan att på nytt utvärdera det så måste en while-sats användas. Om en if -sats används sker ingen ny utvärdering och processen går (kanske) felaktigt in i den kritiska regionen. 10
Lösningsförslag fråga 2: 2a Eftersom en stark semafor förhindrar svält så kan inte p1 får tillgång till semaforen en andra gång (efter A) eftersom p2 då begärt tillgång till semaforen under tiden som p1 hade tillgång till semaforen första gången. Här finns tre tillfällen som kan orsaka ett processbyte. När en process gör sleep (och en annan process är körklar), när en process anropar wait (och semaforens värde är 0) samt när en process kör signal (och en högre prioriterad process är körklar). Tre listor blir aktuella, en time-list för då sleep anropas (T), en wait-list för semaforen (W) och en ready-lista för körklara processer (R). p1 p2 körande counter T W R 1) p1 1 p1,p2 2) wait p1 0 p1,p2 3) sleep(2) sleep(1) - 0 p1,p2-4) wait (p2) 0 p1 p2 (p2) 5) signal p1 0 p1,p2 6) wait p2 0 p1 p2 7) signal p1 0 p1,p2 8) sleep(2) p2 0 p1 p2 9) signal p1 1 p1,p2 1) Från det att båda processerna är körklara blir p1 (högst prioritet) körande. 2) p1 gör wait (W tom) counter räknar ned 3) printf(s), p1 gör sleep(2), p2 blir körande och gör sleep(1) varpå båda ligger i T 4) p2 vaknar först, printf(r), p2 gör wait (counter==0) och läggs i W 5) p1 vaknar, printf(e), kör signal (p2 i W, så counter oförändrad, p2 till R), och p1 med högst prio fortsätter, printf(m), do work, printf(a) 6) p1 gör wait (counter==0) och läggs i W, så p2 blir körande, printf(t), do work, printf(o) 7) p2 gör signal (p1 i W och högst prio), p1 blir körande (counter oförändrad), printf(f) 8) p1 gör sleep(2) (p1 till S), p2 blir körande, printf(s), p2 går in i oändlig while-loop 9) p1 vaknar, har högst prio och blir körande, printf(o), gör signal (W tom så counter räknar upp), printf(r), p1 går in i oändlig while-loop Utskriften blir alltså: S R E M A T O F S O R 2b Urskriften blir: S R E M A F O R Eftersom en svag semafor tillåter svält så kan p1 få tillgång till semaforen direkt även den andra gången. Processbyte till p2 kommer endast att ske när p1 gör sleep (efter S), därefter kör p1 hela tiden fram till den oändliga while-loopen varefter inga fler processbyten sker. Dvs, p2 fastnar för evigt vid wait. 11
Lösningsförslag fråga 3: Nedan redovisas hela programmet. #include <simple_os.h> #include <stdio.h> #include <stdlib.h> #include <string.h> /* any number of forks more than 1 should work */ #define N_FORKS 5 #define STACK_SIZE 5000 stack_item Philosopher_stack[N_FORKS][STACK_SIZE]; /* data structure for dinner table */ typedef struct /* the forks, 0=unused, 1=used */ int fork[n_forks]; /* philosopher id */ int id; /* semaphore for protection of the dinner table */ si_semaphore mutex; /* event variable to indicate if a fork has been put down or lifted up */ si_condvar change; dinner_table_type; typedef dinner_table_type* table_type; table_type table; /* create table: creates a table and initialises the created table */ table_type create_table(void) /* reference to the created table */ table_type table; /* allocate memory */ table = (table_type) malloc(sizeof(dinner_table_type)); /* no forks initially used */ int i; for (i=0; i<n_forks; i++) 12
table->fork[i] = 0; /* first philospher id */ table->id = 0; /* initialise semaphore and event variable */ si_sem_init(&table->mutex, 1); si_cv_init(&table->change, &table->mutex); return table; void philosopher_eat(table_type table, int philosopher_id) int left_fork = philosopher_id; int right_fork = (philosopher_id + 1) % N_FORKS; int finished_eating = 0; /* reserve table */ si_sem_wait(&table->mutex); while (!finished_eating) /* left fork available? */ if (table->fork[left_fork] == 0) /* pick up left fork */ table->fork[left_fork] = 1; /* give someone else a chance so pick up or put down a fork */ si_sem_signal(&table->mutex); /* wait a randomised time to avoid deadlock */ si_wait_n_ms((rand() % 2000) + 500); /* reserve table */ si_sem_wait(&table->mutex); /* right for available? */ if (table->fork[right_fork] == 0) /* pick up right fork */ table->fork[right_fork] = 1; /* don t block others while eating */ si_sem_signal(&table->mutex); printf("philosopher %d eating\n", philosopher_id); /* simulate eating */ 13
si_wait_n_ms(4000); /* reserve table */ si_sem_wait(&table->mutex); finished_eating = 1; /* put down right fork */ table->fork[right_fork] = 0; /* put down left fork */ table->fork[left_fork] = 0; /* tell others table has changed */ si_cv_broadcast(&table->change); else /* right fork not available, so put down left fork to give someone else a chance to eat */ table->fork[left_fork] = 0; /* tell others table has changed */ si_cv_broadcast(&table->change); if (!finished_eating) /* wait for a change, a chance to eat */ si_cv_wait(&table->change); /* release table */ si_sem_signal(&table->mutex); void philosopher_think(int philosopher_id) printf("philosopher %d thinking\n", philosopher_id); /* simulate thinking */ si_wait_n_ms(4000); void philosopher_task(void) /* reserve table */ si_sem_wait(&table->mutex); /* generate unique id */ 14
int id = table->id++; /* release table */ si_sem_signal(&table->mutex); while(1) /* eat */ philosopher_eat(table, id); /* think */ philosopher_think(id); /* main */ int main(void) /* initialise kernel */ si_kernel_init(); /* set the table */ table = create_table(); /* create philosopher tasks */ int i; for (i=0; i<n_forks; i++) si_task_create(philosopher_task, &Philosopher_stack[i][STACK_SIZE-1], 10+i); /* start the kernel */ si_kernel_start(); /* will never be here */ return 0; 15
Lösningsförslag fråga 4: 4a Med EDF (Earliest Deadline First) är det möjligt att uppnå 100 procent utnyttjandegrad, vilket också blir fallet här, dvs U e = 1. Alla processer klarar sina deadlines. P3 1 1 1 1 1 1 P2 P1 2 2 2 3 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 2 4b Med RMS får processerna prioritet utefter hur ofta de ska köras. Kortare periodtid ger högre prioritet. Dvs, P3 > P2 > P1. P2 och P3 klarar sina deadlines, men P1 missar sin deadline vid tidpunkten t=9. EFtersom icke utfört arbete ej ackumuleras (förkastas) så leder det till att det finns outnyttjad processortid under tidsintervallet t=17-18. Den faktiska utnyttjandegraden blir därför U e = 17/18. P3 1 1 1 1 1 1 P2 P1 2 2 2 2 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 2 1 16
Lösningsförslag fråga 5: Att skapa process P1 sker i följande ordning: 1. Skapa ett TCB för P1, fyll i värden för prioritet och tid (typiskt 0). 2. Skapa ett stackutrymme för P1 i minnet. 3. Skriv värdet för PC, dvs startadressen för den funktion som utgör processen, i stacken tillhörande P1. 4. Skriv värden för önskat starttillstånd hos registeruppsättningen, i stacken. 5. Stackpekaren SP pekar nu på toppen av stacken, så spara SP i TCB för P1. 6. Process P1 är nu redo att starta, lägg dess TCB i ReadyList. Att starta process P1 sker i följande ordning: 1. Markera process P1 med en pekare kallad Running. 2. Läs SP från TCB för processen markerad med Running (dvs P1:s TCB) in till processorns stackpekare. 3. Läs in register från P1:s stack till processorn register 4. Läs in PC från P1:s stack till processorn PC. Därmed hoppar exekveringen till processens adress och processen startar. 17