Tentamen Datorteknik och realtidssystem, TSEA81 Datum 2017-08-18 Lokal TER2 Tid 14-18 Kurskod TSEA81 Provkod TEN1 Kursnamn Datorteknik och realtidssystem Institution ISY Antal frågor 5 Antal sidor (inklusive denna 17 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 Gunnel Hässler 013-28 2606 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.) Skriv läsbart! Oläsbar text kan inte bedömas och ger därmed inga poäng. Lycka till! 1
Fråga 1: Periodiska processer(10p) Ett realtidssystem med tre periodiska processer, P1, P2 och P3, ska schemaläggas. Följande krav gäller: P1 ska köras under 1 tidsenhet i tidsintervallet [i4, (i + 1)4], där i är ett heltal och i 0. P2 ska köras under 3 tidsenheter i tidsintervallet [i7, (i + 1)7], där i är ett heltal och i 0. P3 ska köras under n tidsenheter i tidsintervallet [i14, (i + 1)14], där i är ett heltal och i 0. Varje process aktiveras vid startpunkten för respektive tidsintervall. Det innebär att P1 aktiveras vid var 4:e tidsenhet, med start vid tidpunkten 0, P2 aktiveras vid var 7:e tidsenhet, med start vid tidpunkten 0, och P3 aktiveras vid var 14: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 4:e tidsenhet, med början vid tidpunkten 4, P2 har en deadline vid var 7:e tidsenhet, med början vid tidpunkten 7, och P3 har en deadline vid var 14:e tidsenhet, med början vid tidpunkten 14. Tänk på att för varje deluppgift nedan tydligt visa och motivera hur du kommer fram till svaret. Ett sätt kan vara att rita en tidslinje där det framgår när och hur länge respektive process kör och var de har sina deadlines. (a) (5p) Antag att schemaläggningen Rate Monotonic Scheduling (RMS) används. Bestäm största möjliga värde på n, förutsatt att processernas krav uppfylls och att de klarar sina respektive deadlines. Bestäm också utnyttjandegraden U e under samma förutsättningar. (b) (5p) Antag att schemaläggningen Earliest Deadline First (EDF) används. Bestäm största möjliga värde på n, förutsatt att processernas krav uppfylls och att de klarar sina respektive deadlines. Bestäm också utnyttjandegraden U e under samma förutsättningar. 2
Fråga 2: Meddelandehantering(12p) En realtidsapplikation som använder meddelandehantering i Simple OS ska implementeras. Applikationen ska utgöra en timer med alarm. Följande funktioner finns redan: int get_input(void) // Returnerar ett eventuellt inmatat heltal, dock först efter att // användaren tryckt på enter-tangenten. OBSERVERA att eftersom denna // funktion stannar och väntar på inmatning så kommer endast // den process som anropat funktionen kunna vara körande, till dess // att funktionen returnerar förstås. void show_time(int timer_value) // Uppdaterar timerns display, och returnerar sedan void start_alarm(void) // Startar alarm-ljudet på timern, och returnerar sedan // Alarm-ljudet fortsätter, oavsett vad som händer för övrigt, ända // tills stop_alarm aktiverats void stop_alarm(void) // Stoppar alarm-ljudet på timern, och returnerar sedan I Simple OS finns även färdiga funktioner för att skicka och ta emot meddelanden: void si_message_send(const char message[], int length, int receive_pid) // Skickar meddelande, av längden length tecken, till processen med id receive_pid. // Anropande process väntar om meddelandebuffern är full. void si_message_receive(char message[], int *length, int *send_pid) // Tar emot meddelande, med längden *length tecken, från processen med id *send_id. // Anropande process väntar om meddelandebuffern är tom. Samt en lämplig funktion för att vänta en viss tid: void si_wait_n_ms(int n_ms) // Anropande process väntar n_ms millisekunder Följande krav finns: Timern ska använda sig av sekundnoggrannhet (ungefär) Det ska finns fyra processer i lösningen vars huvudsakliga uppgift ska vara följande (dvs du får införa mer funktionalitet, men inte mindre, om du behöver): 1. tick task : hålla reda på när det gått en sekund 2. timer task : hålla reda på återstående timer-tid 3. alarm task : starta och stoppa alarm-ljudet 4. user task : ta emot inmatning från användaren Du får INTE deklarera fler nya funktioner utöver dom som redan finns ovan. Du får förstås använda dig av redan existerande funktioner från C-språket och Simple OS om du vill. 3
Du kan räkna med att nödvändiga header-filer finns inkluderade samt att meddelandestackar för processerna finns deklarerade och initierade Du måste själv bestämma vilka och hur många meddelandetyper som behövs. Obs, glöm inte att visa detta. Du måste själv bestämma hur meddelandestrukturen ser ut. Obs, glöm inte att visa detta. Användaren ska kunna mata in en tid endast då timern står stilla. Så fort användaren matat in en tid ska timern börja räkna tiden. När den inmatade tiden har gått ska alarmet starta Användaren ska, genom att trycka på enter-tangenten, kunna bekräfta (stoppa) alarmet först när alarmet startat. Därefter ska användaren kunna mata in en ny tid. Huvudprogrammet ser ut enligt följande: int main(int argc, char **argv) /* initialise kernel */ si_kernel_init(); /* initialise message handling */ si_message_init(); /* create tasks, id s are set in starting order, starting with 1 */ si_task_create(tick_task, &tick_task_stack[stack_size-1], 10); int Tick_Task_Id = 1; si_task_create(timer_task, &timer_task_stack[stack_size-1], 12); int Timer_Task_Id = 2; si_task_create(alarm_task, &alarm_task_stack[stack_size-1], 14); int Alarm_Task_Id = 3; si_task_create(user_task, &user_task_stack[stack_size-1], 16); int User_Task_Id = 4; /* start the kernel */ si_kernel_start(); /* will never be here */ return 0; Din uppgift är alltså, utöver deklaration av meddelandetyper och meddelandestruktur enligt kraven ovan, att skriva koden för de fyra processerna tick task, timer task, alarm task och user task. 4
Fråga 3: Semaforer och händelsevariabler(10p) Betrakta följande program: #include <simple_os.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define BUFFER_SIZE 1 #define STACK_SIZE 5000 stack_item Producer_Stack[STACK_SIZE]; stack_item Consumer_Stack[STACK_SIZE]; /* data structure for buffer */ typedef struct /* the buffer itself */ char buffer_data[buffer_size]; /* position for next write */ int in_pos; /* position for next read */ int out_pos; /* number of elements in the buffer */ int count; /* semaphore for protection of the buffer */ si_semaphore mutex; /* event to indicate that the buffer has changed state, from full to non-full or from empty to non-empty */ si_condvar change; buffer_data_type; typedef buffer_data_type* buffer_type; buffer_type buffer; /* create buffer: creates a buffer and initialises the created buffer */ buffer_type create_buffer(void) /* reference to the created buffer */ buffer_type buffer; /* allocate memory */ buffer = (buffer_type) malloc(sizeof(buffer_data_type)); 5
/* start writing at first position */ buffer->in_pos = 0; /* start reading at first position */ buffer->out_pos = 0; /* no elements ar stored in the buffer */ buffer->count = 0; /* initialise semaphore and event variable */ si_sem_init(&buffer->mutex, 1); si_cv_init(&buffer->change, &buffer->mutex); return buffer; /* put item: stores an item in buffer */ void put_item(buffer_type buffer, char item) /* reserve buffer */ si_sem_wait(&buffer->mutex); /* check if the buffer is full, and if this is so, wait */ while (buffer->count == BUFFER_SIZE) si_cv_wait(&buffer->change); /* store item in buffer */ buffer->buffer_data[buffer->in_pos] = item; buffer->in_pos++; if (buffer->in_pos == BUFFER_SIZE) buffer->in_pos = 0; buffer->count++; /* notify other processes that a change has occurred */ si_cv_broadcast(&buffer->change); /* release buffer */ si_sem_signal(&buffer->mutex); 6
/* get_item: read an item from the buffer */ char get_item(buffer_type buffer) /* item to read from the buffer */ char item; /* reserve buffer */ si_sem_wait(&buffer->mutex); /* check if buffer is empty, and if this is so, wait */ while (buffer->count == 0) si_cv_wait(&buffer->change); /* read item from buffer */ item = buffer->buffer_data[buffer->out_pos]; buffer->out_pos++; if (buffer->out_pos == BUFFER_SIZE) buffer->out_pos = 0; buffer->count--; /* notify other processes that a change has occurred */ si_cv_broadcast(&buffer->change); /* release buffer */ si_sem_signal(&buffer->mutex); /* return value read */ return item; void producer_task(void) char string_to_send[] = "123"; int i; char item; while(1) for (i=0; i<strlen(string_to_send); i++) item = string_to_send[i]; put_item(buffer, item); printf("p:%c\n",item); si_wait_n_ms(2000); 7
void consumer_task(void) char item; while(1) item = get_item(buffer); printf("c:%c\n",item); /* main */ int main(void) /* initialise kernel */ si_kernel_init(); buffer = create_buffer(); /* create tasks */ si_task_create(producer_task, &Producer_Stack[STACK_SIZE-1], 10); // Highest priority si_task_create(consumer_task, &Consumer_Stack[STACK_SIZE-1], 20); // Lowest priority /* start the kernel */ si_kernel_start(); /* will never be here */ return 0; (a) (2p) Vad skriver programmet ut fram till dess att producer task väntat färdigt i 2000 ms, dvs under första varvet i while-loopen? Det räcker med ett svar som endast visar själv utskriften. (b) (8p) Beskriv varför utskriften blir som den blir i uppgift (a). Var noggrann med att förklara och motivera, gärna stegvis. Tänk på att beskriva, vad som händer och varför med processerna vid relevanta tillfällen, vilka relevanta funktioner som anropas samt bufferns status och innehåll. 8
Fråga 4: Barriär(9p) En synkroniseringsmekanism inom realtidssystem är en så kallad barriär. Denna används för att låta ett flertal processer vänta in varandra vid ett visst skede, t ex efter att varje process utfört ett visst arbete, innan man sedan låter processerna fortsätta med nästa omgång av arbetet. Skriv C-funktionen för en barriär med följande funktionshuvud: void barrier(int N) Funktionen barrier skulle kunna användas för en av t ex fem tänkta processer enligt följande: void *task1(void *unused) while(1) do_work(); barrier(5); Följande krav gäller: Barriär-funktionen barrier ska utgöra en barriär för mer än en process åt gången Ingen process får lämna barriären förrän alla aktuella processer från samma omgång har trätt in i barriären Ingen process får träda in i barriären förrän alla processer från föregående omgång lämnat barriären Eventuella egna variabler och funktioner måste deklareras Du kan valfritt använda realtidsfunktioner från antingen Linux eller Simple OS Programkoden behöver vara tydligt kommenterad för full poäng 9
Fråga 5: Stark/svag semafor(9p) Följande pseudokod utgör wait- och signal-funktionerna för en semafor: void wait(scb *Mutex) om fältet Count i Mutex är större än noll minska fältet Count i Mutex med ett annars flytta körande process PCB från ReadyList till listan WaitList anropa Schedule void signal(scb *mutex) om fältet WaitList i Mutex representerar en tom lista öka fältet Count i Mutex med ett annars flytta ett PCB från listan Waitlist till Readylist anropa Schedule (a) (1p) Är pseudokoden ovan för en stark eller svag semafor? (b) (1p) Vad är huvudsyftet med en stark semafor? (c) (1p) Vad är huvudsyftet med en svag semafor? (d) (6p) Modifiera pseudokoden för wait och signal ovan. Om pseudokoden ovan är en stark semafor, skriv då pseudokod för en svag semafor. Om pseudokoden ovan är en svag semafor, skriv då pseudokod för en stark semafor. 10
Lösningsförslag fråga 1: 1a Med RMS (Rate Monotonic Scheduling) får processerna prioriteter efter periodtid. Ju kortare periodtid, ju högre prioritet. Detta medför att prioriteten för P1 > P2 > P3. Därefter schemaläggs processerna med principen priority based preemptive scheduling. Vid tidpunkten 0 är alla processer samtidigt körklara, vilket händer nästa gång vid tidpunkten 28 då förloppet upprepar sig. Processerna blir körande enligt figur nedan. Deadlines för respektive process är markerade med lodräta streck. P3 P2 3 2 1 2 1 1 P1 1 1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 2 2 1 3 Under tidsintervallet 0-14 kan P3 köra under totalt 4 tidsenheter, och under tidsintervallet 14-28 kan P3 köra under 5 tidsenheter. Dvs n måste då sättas till det lägre alternativet, n = 4. Resursutnyttjandet blir därför U e = 27/28. 2 1b Med EDF (Earliest Deadline First) är det möjligt att uppnå 100 procent utnyttjandegrad, dvs U e = 1. Största värdet på n ges då av följande ekvation: 1 4 + 3 7 + n 14 = 1 vilket ger n = 4,5. Observera att i uppgiften finns ingen förutsättning som säger att tidsavbrott endast kan ske vid hela tidsintervall. Dvs, det är fullt möjligt att bryta vid delar av tidsenheter. En körklar process med kortast tid till sin deadline är den som får köra. Om flera processer har samma tid till deadline och en av dem är körande så låter man den processen fortsätta, för att på så sätt undvika ett processbyte. I annat fall kvittar det vilken process som kör då flera processer har samma tid till sin deadline. Vid tidpunkten 0 är alla processer samtidigt körklara, vilket händer nästa gång vid tidpunkten 28 då förloppet upprepar sig. Processerna blir körande enligt exempel i följande figur (Det kan ju variera något beroende på vilken process man väljer när flera processer är körklara med samma tid till sin deadline). Deadlines för respektive process är markerade med lodräta streck. P3 P2 3 3 3 P1 1 1 1 1 1 1 1 1,5 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 1,5 1,5 1,5 3 3 Lösningsförslag fråga 2: Om man för sig själv börjar med att rita upp en figur över de aktuella processerna med meddelandetyper och kommunikationsvägar så blir jobbet sedan bara att översätta den figuren till kod. 11
tick TICK timer TIME user START alarm START,ACK STOP Processen tick task skickar TICK-meddelanden till timer task en gång i sekunden. Processen user task börjar med inmatningsfunktionen get input, vilket leder till att inga andra processer därefter blir körande förrän användaren tryckt på enter-tangenten och get input returnerar. Då skickar user task ett TIME-meddelande till timer task, som därefter kan börja räkna tiden. När det är dags att starta alarmet så skickar timer task ett START-meddelande till alarm task, som startar alarmet och först därefter informerar user task (via ännu ett START-meddelande) att detta skett. Då kan user task övergå till att bekräfta alarmet via inmatningsfunktionen get input och skicka ett STOP-meddelande till alarm task som stoppar alarmet och sedan bekräftar detta till user task med ett ACK-meddelande. Då kan user task återgå till att ta in ett nytt timer-värde via inmatningsfunktionen get input. /* message types */ #define TICK_MESSAGE 0 #define TIME_MESSAGE 1 #define START_ALARM_MESSAGE 2 #define STOP_ALARM_MESSAGE 3 #define ACK_MESSAGE 4 /* message data type */ typedef struct int type; int timer_value; message_data_type; void tick_task(void) message_data_type message; while(1) si_wait_n_ms(1000); message.type = TICK_MESSAGE; si_message_send((char*) &message, sizeof(message), Timer_Task_Id); void timer_task(void) message_data_type message; 12
int length; int send_task_id; int timer_value = 0; while(1) si_message_receive((char *) &message, &length, &send_task_id); switch(message.type) case TICK_MESSAGE: timer_value--; show_timer(timer_value); if (timer_value == 0) message.type = START_ALARM_MESSAGE; si_message_send((char *) &message, sizeof(message), Alarm_Task_Id); break; case TIME_MESSAGE: timer_value = message.timer_value; show_timer(timer_value); break; void alarm_task(void) message_data_type message; int length; int send_task_id; while(1) si_message_receive((char *) &message, &length, &send_task_id); switch(message.type) case START_ALARM_MESSAGE: start_alarm(); message.type = START_ALARM_MESSAGE; si_message_send((char *) &message, sizeof(message), User_Task_Id); break; case STOP_ALARM_MESSAGE: stop_alarm(); message.type = ACK_MESSAGE; si_message_send((char *) &message, sizeof(message), User_Task_Id); break; 13
void user_task(void) message_data_type message; int length; int send_task_id; while(1) message.timer_value = get_input(); message.type = TIME_MESSAGE; si_message_send((char *) &message, sizeof(message), Timer_Task_Id); si_message_receive((char*) &message, &length, &send_task_id); message.timer_value = get_input(); message.type = STOP_ALARM_MESSAGE; si_message_send((char*) &message, sizeof(message), Alarm_Task_Id); si_message_receive((char*) &message, &length, &send_task_id); Lösningsförslag fråga 3: 3a Programmet skriver ut följande under första varvet i while-loopen för producer task: P:1 P:2 C:1 P:3 C:2 C:3 3b Vi kallar producer task för P, och consumer task för C. 1. P (högst prio) anropar put item med 1 (buffern ledig och tom), lägger in 1 i buffern, si cv broadcast orsakar inget, si sem signal låter P (högst prio) fortsätta. 2. P skriver ut P:1 3. P anropar put item med 2 (buffern ledig och full), P flyttas till väntelistan för händelsevariabeln change, C blir körande. 4. C anropar get item (buffern ledig och full), läser 1, si cv broadcast flyttar P från väntelistan för change till väntelistan för semaforen mutex, si sem signal flyttar P från väntelistan för semaforen till readylistan och P (högst prio) blir körande. 5. P fortsätter i put item (buffern ledig och tom), skriver in 2 i buffern. si cv broadcast orsakar inget, si sem signal låter P (högst prio) fortsätta. 6. P skriver ut P:2 14
7. P anropar put item med 3 (buffern ledig och full), P flyttas till väntelistan för händelsevariabeln change, C blir körande. 8. C returnerar från get item med 1. 9. C skriver ut C:1 10. C anropar get item (buffern ledig och full), läser 2, si cv broadcast flyttar P från väntelistan för change till väntelistan för semaforen mutex, si sem signal flyttar P från väntelistan för semaforen till readylistan och P (högst prio) blir körande. 11. P fortsätter i put item (buffern ledig och tom), skriver in 3 i buffern, si cv broadcast orsakar inget, si sem signal låter P (högst prio) fortsätta. 12. P skriver ut P:3, P ställer sig och väntar 2000 ms, C blir körande 13. C returnerar från get item med 2. 14. C skriver ut C:2 15. C anropar get item (buffern ledig och full), läser 3, si cv broadcast orsakar inget, si sem signal låter C fortsätta (endast C är redo). 16. C skriver ut C:3, anropar get item och blir väntande på händelsevariabeln (buffern tom). Lösningsförlag fråga 4: På något sätt behöver man hantera det faktum att processer från nästa omgång kan vilja gå in i barriären innan alla processer från föregående omgång har lämnat barriären. Detta görs i nedanstående lösning med variabeln barrier state som håller reda på om samma omgång av processer är i tillståndet ENTERING eller EXITING. int num = 0; typedef enum ENTERING, EXITING barrier_state_t; barrier_state_t state = ENTERING; pthread_mutex_t mutex; pthread_cond_t cv; void barrier(int N) /* reserve common resources */ pthread_mutex_lock(&mutex); /* as long as the previous round of tasks are in the process of exiting the barrier, no new tasks may enter the barrier */ while(state == EXITING) /* hold new arriving tasks from the next round */ pthread_cond_wait(&cv, &mutex); /* increase number of tasks in barrier */ num++; /* announce that another task has entered the barrier */ pthread_cond_broadcast(&cv); 15
/* as long as tasks are entering the barrier and until all relevant tasks have entered, hold tasks in a waiting state */ while(state == ENTERING) pthread_cond_wait(&cv, &mutex); /* if all relevant tasks have entered the barrier, it s time to exit */ if (num == N) state = EXITING; /* broadcast here is not really neccessary since it will be done just before exiting the function, but it might be more efficient here*/ pthread_cond_broadcast(&cv); /* decrease number of tasks in barrier */ num--; /* when all relevant tasks have exited the barrier, let the next round of tasks enter by changing state to ENTERING */ if (num == 0) state = ENTERING; /* announce that a task is leaving the barrier */ pthread_cond_broadcast(&cv); /* release common resources */ pthread_mutex_unlock(&mutex); Lösningsförlag fråga 5: 5a Den givna pseudokoden utgör en stark semafor. 5b En stark semafor förhindrar svält, dvs även lågprioriterade processer får möjlighet att exekvera. 5c En svag semafor hindrar inte exekveringen av högprioriterade processer (dvs tillåter svält), vilket kan vara önskvärt i vissa realtidssystem. 5d Pseudokoden för en svag semafor skulle kunna se ut så här: 16
void wait(scb *Mutex) så länge fältet Count i Mutex är lika med noll flytta körande process PCB från ReadyList till WaitList anropa Schedule minska fältet Count i Mutex med ett void signal(scb *Mutex) nollställ flagga för att anropa Schedule om fältet WaitList i Mutex inte representerar en tom lista flytta ett PCB från listan WaitList till ReadyList sätt flagga för att anropa Schedule öka fältet Count i Mutex med ett om flaggan för att anropa Schedule är satt anropa Schedule 17