Kompletterande kompendium till kursen
Realtidsprogrammering
Mathias Broxvall, Lars Karlsson Örebro Universitet Januari 2010 v2010.1
1
2
Innehåll Innehåll....................................................................................................................................................................................1 Processer i VxWorks................................................................................................................................................................2 Multitasking ........................................................................................................................................................................2 Ett tasks olika tillstånd ........................................................................................................................................................3 Schemaläggning och tidshantering i VxWorks....................................................................................................................4 Delad kod.............................................................................................................................................................................5 Hårdvaruavbrott i VxWorks................................................................................................................................................5 Andra sätt för tasks att kommunicera..................................................................................................................................6 Monitorer.................................................................................................................................................................................7 Bankers algoritm....................................................................................................................................................................10 Bakgrund...........................................................................................................................................................................10 Data....................................................................................................................................................................................10 Algoritmen.........................................................................................................................................................................10 Exempel.............................................................................................................................................................................11 Övning................................................................................................................................................................................12 Upptäcka dödlåsning..............................................................................................................................................................13 Bakgrund............................................................................................................................................................................13 Data....................................................................................................................................................................................13 Algoritmen.........................................................................................................................................................................13 Exempel.............................................................................................................................................................................14 Övning................................................................................................................................................................................15 Schemaläggning.....................................................................................................................................................................16 Ingredienser........................................................................................................................................................................16 När går det att schemalägga en grupp processer?..............................................................................................................17 Schemaläggningsalgoritmer..............................................................................................................................................19 Sammanfattning.................................................................................................................................................................22 Övningar............................................................................................................................................................................22 Petri-nät..................................................................................................................................................................................23 Inledning............................................................................................................................................................................23 Ett Petri-näts uppbyggnad..................................................................................................................................................23 Ett Petri-näts beteende.......................................................................................................................................................24 Ömsesidig uteslutning med Petri-nät.................................................................................................................................26 Petri-nät med flera tecken per plats...................................................................................................................................31 Egenskaper hos Petri-nät....................................................................................................................................................33 Mjukvara för analys av petrinät.........................................................................................................................................35 Analys av mer avancerade petrinät....................................................................................................................................36 Utökningar av petrinät.......................................................................................................................................................37 Realtidsprogrammering – instuderingsfrågor........................................................................................................................43 Blandade övningar.................................................................................................................................................................44 Lösningar till övningar...........................................................................................................................................................49
1
Processer i VxWorks
Multitasking I VxWorks är det realtidskärnan, wind, som hanterar multipla processer. Vilken process som kör när bestäms av realtidskärnans schemaläggare. Genom att låta schemaläggaren ofta byta mellan processerna kan det nästan se ut som att de olika processerna verkligen körs samtidigt. I VxWorks kallas varje oberoende process för ett task (ni få ursäkta den klumpiga försvenskningen av den engelska termen) . Dessa olika task delar på samma minnesutrymme och kan därigenom använda samma variabler och kod. Däremot har varje task har sin egen exekveringskontext. Kontexten innehåller de CPU-registervärden och systemresurser som tasket använder när det är dess tur att exekvera. När ett task börjar exekvera hämtas dess exekveringskontext från dess "task control block" (TCB), och när ett task lämnar ifrån sig exekveringen sparas exekveringskontexten undan i TCB:t. Ni kan hitta definition av ett TCB som en struct windTCB i include filen taskLib.h. Ett TCB innehåller framför allt: •
Programräknaren (var denna task är i sin kod)
•
CPU-register
•
En stack för dynamiska variabler och funktionsanrop
•
Vad som är standard input, output, och error
•
En timer för fördröjningar
•
En timer för tidsdelning
•
Kontrollstrukturer för kärnan
•
Signalhanterare
•
Debugging-information.
Förutom ett TCB så har ett task också: • Ett namn • Ett identitetsvärde (heltal) • En prioritet • Ett tillstånd
2
Ett tasks olika tillstånd Kärnan håller reda på de olika taskens tillstånd. Det finns fyra olika tillstånd (och ett antal kombinationer av dessa). READY PEND DELAY SUSPEND
Tasket är körklart och väntar inte på någon resurs (förutom CPU:n) Tasket väntar på någon resurs (t ex semafor, meddelandekö) som inte är tillgänglig för ögonblicket. Tasket sover för en viss tid. Tasket har tillfälligt avstannat på grund av debugging.
För att se vilket tillstånd en task är i kan ni använda funktionen statusString definierad i vxwTaskLib.h. Vad som får ett task att byta tillstånd illustreras i bilden nedan.
3
Schemaläggning och tidshantering i VxWorks Schemaläggarens uppgift är att fördela CPU-tid mellan de olika körklara tasken, dvs de som är i tillståndet READY. Task i andra tillstånd körs aldrig. Byten mellan olika processer som ska köras kallas för kontextbyten och detta görs när operativsystement periodiskt blir anropat via interrupts som genereras av hårdvaru timers. För att förstå när och hur dessa process byten sker så måste vi dels förstå hur VxWorks hanterar tid samt titta på de olika schemaläggnings algoritmerna som finns för att bestäma vilken process som ska köras när nästa kontext byte sker. Det finns två alternativa schemaläggningsmetoder; det förvalda alternativet är prioritetsbaserad föregripande (preemptive) schemaläggning, men tidsdelning (round robin) kan också väljas.
Ticks i VxWorks När man programmeorar inbyggda system och använder flera processer som ska se ut att köra samtidigt så måste RTOS'et byta mellan de olika processerna kontinuerligt. För att kunna göra detta så behöver vissa operativsystemsfunktioner köras periodiskt för att kunna göra kontextbyten. Detta byte till operativsystems-funktionerna implementeras mha interupts som genereras av hårdvarutimers. I VxWorks så sker detta med en fast periodicitet. Den grundläggande tidsenheten som VxWorks använder för att mäta tid, en Tick, motsvarar den period med vilken dessa interrupt genereras. Period längden och det antal ticks som genereras varje sekund kan ställas in mha operativsystems-funktionen sysClkRateSet(). Maximalt antal ticks per sekund som kan genereras beror i regel på hårdvaran, dvs. på upplösningen av de hårdvarutimers som används för att generera interrupts. Varje gång en tick genereras i VxWorks så kontrollerar operativsystemet om det finns någon annan task med högre prioritet än den som kör för närvarande som vill köra. Om det finns en sådan task så gör VxWorks ett kontextbyte, dvs. sparar undan exekveringskontexten (register innehåll, programräknare, stack,...) för den nuvarande processen och laddar in exekveringskontexten för den nya task'en. När en task gör ett operativsystems anrop som fördröjer den (t.ex. taskDelay) så försätts den i väntläge och operativsystemet gör ett kontextbyte till en annan task som är körklar och har högst prioritet. Eftersom kontextbyten endast kan ske i samband med ticks, så kan det hända att en task som sover blir tvungen att vänta lite längre än vad som var tänkt. Därför är det viktigt att ha en lämplig tick period som passar till applikationen. Naturligtvis tar dessa kontroller och byten mellan processer lite tid av processorn, så därför behövs en avvägning mellan processorkraft och hur ofta processbyten måste kunna ske.
Prioritetsbaserad föregripande schemaläggning Principen för prioritetsbaserad föregripande schemaläggning är att det task körs som har högst prioritet av de som är körklara. Finns det flera körklara tasks med högsta prioritet så körs ett av dem. Ett task körs antingen till det hamnar i ett annat tillstånd (t ex i DELAY efter ett taskDelay()), eller tills ett task med högre prioritet än det nuvarande exekverande tasket blir körklart. I det senare fallet så tar det högre prioriterade tasket över. I bilden nedan så avbryts t1 av t2 (som har högre prioritet), och sedan t2 av t3 (som har ännu högre prioritet). När t3 är klar kan t2 fortsätta, och när t2 är klar kan t1 fortsätta.
4
Kärnan använder sig av 256 prioritetsnivåer, från 0 (högst) till 255 (lägst). Ett task tilldelas en prioritet när det skapas, men prioriteten kan också ändras dynamiskt. Tidsdelning Den prioritetsbaserad föregripande schemaläggningen kan kompletteras med tidsdelning (round robin). Detta innebär att CPU-tiden fördelas någorlunda jämnt mellan processer med samma prioritet. Utan tidsdelning kan ett enda task lägga beslag på processorn utan att andra tasks med samma prioritet får tid att köra. Med tidsdelning får varje körklar task (av de med högst prioritet) köra en begränsad tid, lämna över till nästa task och sedan vänta på sin tur igen. Detta åstadkommes genom att körklara tasks med samma prioritet ordnas i en först-in-först-ut-kö. Varje task har dessutom sin egen tidsdelnings-timer som håller reda på hur mycket tid tasket har kvar. Om ett task avbryts av ett annat task med högre prioritet, så får det första tasket fortsätta den tid det har kvar när det högre prioriterade tasket är klart. Detta illustreras i bilden nedan.
Delad kod I VxWorks är det inte ovanligt att flera tasks använder sig av samma subrutin, t ex printf(), fast det bara finns ett exemplar av subrutinens kod i systemet. Detta kallas för delad kod. Det är viktigt att delad kod är "reentrant" (återinträdbar), d v s att flera tasks kan anropa den samtidigt utan att konflikter uppstår. Om detta ska hålla så får inte koden modifiera statiska (static) eller globala variabler. Det finns bara ett exemplar av varje statisk eller global variabel, d v s variabeln lagras på en enda plats i minnet. Om flera tasks modifierar den samtidigt kommer de att skriva i samma minnesutrymme, och det är detta som kan leda till konflikter. Lokala icke-statiska variabler, å andra sidan, är säkra eftersom ny plats för sådana variabler allokeras på stacken varje gång rutinen anropas. Det är också möjligt att starta flera tasks med samma huvud-rutin, eftersom varje task har sin egen stack och kontext, inklusive programräknare. Det är dessutom möjligt att skicka med parametrar till ett task. Även i detta fall måste koden vara återinträdbar. Bilden nedan visar hur flera tasks använder samma kod för att styra varsin led i en robotarm. Genom att parametersera tasken kan man tala om för dem vilken led de ska styra.
5
Hårdvaruavbrott i VxWorks Hårdvaruavbrott är naturligtvis mycket viktiga i VxWorks liksom i all realtidsprogramvara - det är ofta genom avbrott som ett realtidssystem kan detektera att någonting i systemets omgivning har hänt (t ex nya mätdata finns tillgängliga). Avbrott används ofta för att snabbt reagera på externa händelser utan att behöva polla indata portarna, vilket frigör viktig processorkraft till att utföra beräkningar istället för att kontinuerligt kontrollera om någon indata port har ändrats. För att kunna göra detta så krävs det att en eller flera ingångar i hårdvaran har kopplats till processorns avbrottsingångar, vilket beroror till stor del på hårdvaran som används. I VxWorks är hårdvaruavbrotten oberoende av vilket task som exekveras - varje interupt sparar undan den exekverande kontexten och skapar sin egen kontext. Den tidigare kontexten återställes sedan när avbrottsrutinen är klar. För att hantera avbrott behöver man skapa sina egna avbrottsservicerutiner (ISR) och lägga in dessa i en avbrottstabell. Det finns en funktion IntConnect(IR-nummer, funktion, arg)som gör detta. När ett hårvaruavbrott med det angivna numret kommer in, kommer funktionen att exekvera med det medföljande argumentet. Avbrott används ofta för att hämta in information från omvärlden, och naturligtvis vill man vidarebefordra denna information till olika tasks (kom ihåg att avbrottsrutiner exekverar utanför alla taskkontexter). Det finns ett flertal olika metoder för detta. En ISR kan: • skriva i delade variabler och ringbuffrar, • ge semaforer (utom av typen mutex), • skicka meddelanden på en meddelandekö (om kön är full förloras meddelandet), • skriva meddelanden på en pipe (se nedan), • skicka signaler till ett task. Obs! När man skriver avbrottsrutiner är det viktigt att inte anropa någon funktion som kan leda till att rutinen får vänta, t ex semTake() eller scanf() eller taskDelay(). Avbrottsrutinen bör avslutas fort, och att ställa sig och vänta på en semafor eller annan resurs är ett grovt brott mot god avbrottsetikett.
Andra sätt för tasks att kommunicera I VxWorks finns det fler sätt än delade variabler, semaforer och meddelandeköer för tasks att kommunicera med varandra. Signaler Signaler är ganska lika avbrott, men genereras inte från hårdvaran utan från ett task eller en ISR. Till skillnad från avbrott så skickas en signal till ett bestämt task. När tasket tar emot signalen avbryter det vad det håller på med och exekverar en signalhanteringsrutin. Till skillnad från ett avbrott så exekveras signalhanteringsrutinen i taskets kontext. Vilken rutin som exekveras bestäms av taskets signalhanteringstabell. Funktionen signal() används för att ställa upp signalhanteringstabellen och koppla ihop signaler med signalhanteringsrutiner. Funktionen kill() skickar en signal till ett bestämt task, och med funktionen raise() kan ett task signalera till sig självt. Det finns 32 olika möjliga signaler, numrerade från 0 till 31. Signaler används främst för att tala om att någonting har gått fel. Pipes En pipe är en virtuell I/O-enhet för task-till-task- kommunikation som man kan skriva på och läsa från med de vanliga I/O-funktionerna read() och write(). Man kan se en pipe som en virtuell fil som den ena processen skriver på och den andra läser från. En pipe skapas med funktionen pipeDevCreate("/pipe/name", maxMsgs, maxLength). En pipe utför i princip samma funktioner som en meddelandekö, med några mindre skillnader. Sockets En socket (svenska: sockel) är en virtuell I/O-enhet för att kommunicera över ett nätverk, mellan processer som kan exekvera på olika maskiner. Det går också att använda för processer på samma maskin - oberoende av var de olika processerna befinner sig ser det precis likadant ut från programmerarens perspektiv. "Namnet" på en sockel består dels av en nätverksadress (t.ex. mic8.oru.se) som anger vilken maskin sockeln ligger på, och dels ett portnummer (t.ex. 8001). Det senare svarar inte mot någon fysiskt port på datorn - all sockelkommunikation går via datorns nätverksport utan är bara till för att skilja mellan olika sockets på samma maskin. Man kan skriva på och läsa från socklar med de vanliga I/O-funktionerna read() och write().
6
Monitorer Denna sektion berör monitorer som kommunikationsform mellan processer, för en mer utförlig presentation av detta material läs kapitel 7 i kursboken. Som motiverande exempel tänker vi oss att vi behöver dela data mellan två eller fler processer. Vi kan t ex tänka oss att våra processer ska kunna skriva i och läsa från variabler innehållande textsträngar. Naturligtvis vill vi inte att t ex två processer försöker skriva i samma text-variabel samtidigt. Ett enkelt sätt att lösa detta är att sätta in semaforer vid de platser där läsande/skrivande sker: void task1() { … semTake(shared_sem,WAIT_FOREVER); strcpy(str1,shared_str); /* Write to shared string */ semGive(shared_sem); } void task2() { … semTake(shared_sem,WAIT_FOREVER); strcpy(shared_str,str2); /* Read from shared string */ semGive(shared_sem); } Tyvärr kan ett enda misstag i semaforhanteringen få katastrofala effekter. Om vi t ex glömmer att ge semaforen i task2 så innebär detta att ingen process därefter kan ta semaforen. I värsta fall kommer till slut samtliga processer att stå och vänta på semaforen, och hela systemet stannar upp. För att undvika detta kan vi i stället placera all semaforhantering på en enda plats: i en monitor. En monitor består av: - En datastruktur med plats för - delade data, och - semaforer för skydda kritiska regioner. - Ett antal procedurer för att manipulera (skriva, läsa etc) dessa data. Data i monitorn kan endast nås genom dessa procedurer. Exemplet ovan kan t ex skrivas om som i Tabel 1. Här finns en datastruktur för monitorn som innehåller dels den delade strängvariabeln och dels semaforen som behövs för att skydda strängen. Dessutom finns här procedurer för att initialisera monitorn och för att läsa och skriva från strängvariabeln. Notera att de senare inkluderar semaforhantering.
7
/* File: mon.c */ #include struct string_monitor { SEM_ID mutex; char value[32]; }; struct string_monitor *initStringMonitor(void) { struct string_monitor *m; m = malloc(sizeof(struct string_monitor)); m->mutex = semMCreate(SEM_Q_FIFO); m->value[0] = 0x00; return m; } char* read_string(struct string_monitor *m, char *dest) { semTake(m->mutex, WAIT_FOREVER); strcpy(m->value, dest); semGive(m->mutex); return dest; } char* write_string(struct string_monitor *m, char *src) { semTake(m->mutex, WAIT_FOREVER); strcpy(src, m->value); semGive(m->mutex); return src; }
Table 1: Exempel på en monitor, implemenationsdelen. Vi skapar också en header-fil, som innehåller deklarationer av monitorn och dess procedurer. Lägg märke till att i den här header-filen syns inte vad som göms inne i monitorn. Både semaforen och strängen är väl skyddade från manipulation utifrån. /* File: mon.h */ struct extern extern extern
string_monitor; struct string_monitor* initStringMonitor(void); char* read_string(struct string_monitor *m, char *dest); char* write_string(struct string_monitor *m, char *src);
Table 2: Exempel på en monitor, deklarations delen.
Nu kan vi sköta all manipulation av delade strängar med hjälp av vår monitor-datatyp och våra monitor-procedurer. De två processerna här nedan gör samma sak som de in det inledande exemplet.
8
/* File: tasks.c */ #include "mon.h" struct string_monitor *m1; int task1() { char str1[32]; write_string(m1, str1); } int task2() { char str2[32]; read_string(m1, str2); } void main() {
}
m1 = initStringMonitor(); /* Start processes etc */
Table 3: Hur monitorn kan användas.
9
Bankers algoritm Bakgrund Antag att ett datorsystem har m olika resurser och kör n olika processer. En av dessa processer - nummer k - begär plötsligt fler resurser. Med Bankers algoritm kan vi avgöra om det finns risk för dödlåsning om process k får de resurser den begär. Data Följande data behövs: Available[j] där 1 ≤ j ≤ m - hur många enheter av resurs j som för tillfället finns tillgängliga. Max[i,j], där 1 ≤ i ≤ n, 1 ≤ j ≤ m - hur många enheter av resurs j som process i maximalt kan behöva. Allocation[i,j] - hur många enheter av resurs j som för tillfället är tilldelade process i. Need[i,j] - hur många fler enheter av resurs j som process i maximalt kan behöva utöver de enheter den redan tilldelats. Max[i,j] = Allocation[i,j] + Need[i,j]. Lite förenklande notation: Tänk att A, B är vektorer av längd k. När vi skriver t.ex. A ≤ B, så menar vi att A[i] ≤ B[i] för varje i sådant att 1 ≤ i ≤ k. Med andra ord, man jämför tal på samma plats i de två vektorerna. T ex så har vi att (1 0 ) ≤ (1 2) , eftersom 1≤ 1 och 0≤ 2 . När vi skriver A + B = D, så menar vi att A[i] + B[i] = D[i] för varje i sådant att 1 ≤ i ≤ k. T ex har vi att (1 3)
+ (1 2) = ( 2 5) , eftersom 1+ 1 = 2 och 3+2 = 5 . Om C är en matris av storleken k×l, så står Ci för vektorn (C[i,1], C[i,2], …, C[i,l]), d v s den i:e raden i matrisen. T ex om
2 1 2 2 så är C1 = ( 2 1) och C2 = ( 2 2 ) . C =
Algoritmen Själva idén bakom algoritmen är att man tittar på läget som skulle uppstå om den aktuella processen tilldelades de resurser den begär. Sedan avgör man om det läget är säkert, d v s att man inte riskerar att hamna i en död låsning. Bankers algoritm Indata: Request k - hur många enheter process k begär av de olika resurserna. Obs! Den här vektorn innehåller bara information om den process som begär mer resurser; den har ingenting att göra med de andra processerna. Steg: 1. Om inte Request k ≤ Need k - fel! Processen har begärt fler enheter av någon resurs än vad den maximalt ska kunna behöva. 2. Om inte Request k ≤ Available - vänta! Det finns inte tillräckligt antal enheter av en av de begärda resurserna. 3. Pröva (rent hypotetiskt) vad som skulle hända om process k skulle tilldelas de begärda resurserna. Uppdatera de olika matriserna: Available := Available - Request k Allocation k := Allocation k + Request k Need k := Need k - Request k Om det nya läget är säkert (se nedan) så tilldela de begärda resurserna. Annars: återställ matriserna och låt processen vänta. Säkert läge? För att avgöra om ett läge är säkert, försöker man hitta ett sätt att ge varje process sitt maximala behov av resurser genom att upprepa de två stegen: (1) välj ut en process som kan tilldelas sitt maximala behov av resurser i det nuvarande läget och köra klart; och (2) notera att processen är klar och frigör de resurser som den har allokerat så att återstående processer kan använda dem. Om detta lyckas för samtliga processer är läget säkert; annars ej. Indata: Det nya läget enligt punkt 3 ovan. Temporära data: Work[j] - hur mycket av resurs j som finns tillgängligt vid varje steg. Finish[i] - process i är avslutad.
10
Steg: 1. Låt Work := Available Finish[i] := false, i=1,..,n. 2. Hitta ett l (d v s en process) sådant att både - Finish[l] = false - Needl ≤ Work d v s process l är inte avslutad ännu, men det finns tillräckligt med resurser för att ge den dess maximala behov och sedan avsluta den. Finns inget sådant l: gå till 4. 3. Låt Work := Work + Allocationl Finish[l] := true d v s uppdatera till läget då process l är avslutad och har lämnat tillbaka alla sina allokerade resurser. Gå tillbaka till 2. 4. Om Finish[i] := true för alla i=1,..,n: alla processer har lyckats avsluta läget är säkert. Annars: läget är ej säkert. Exempel Vi antar att vi har två processer och två resurser. I matriserna lägger vi processerna radvis och resurserna kolumnvis, enligt:
R1 R2 P1 2 2 P2 2 0
Av resurs #1 (i kolumn 1 i matriserna nedan) finns det totalt 4 enheter varav två är lediga (se Available), en används av process #1 (rad 1 kolumn 1 i matrisen Allocation) och en används av process #2 (rad 2 kolumn 1 i matrisen Allocation). Det finns också totalt 3 enheter av resurs #2 (i kolumn 2 i matriserna nedan) varav två är lediga (se Available) och en används av process #1 (rad 1 kolumn 2 i matrisen Allocation). Available = ( 2
2)
3 3 1 Allocation = 1
2 2
Max =
Need =
1 0
2 1 2 2
Nu behöver process #1 plötsligt en mer av resurs 1. Vi får: Request1 = (1 0) Vi konstaterar att villkoren i steg 1 och steg 2 i Bankers är uppfyllda, och går vidare till steg 3. Vi testar vad det nya läget skulle vara: Available := Available - Request1 = ( 2 2 ) - (1 0 ) = (1 2 )
2 1 1 0
Allocation1 := Allocation1 + Request1 = (1 1) + (1 0 ) = ( 2 1) , vilket ger Allocation :=
1 1 2 2
Need1 := Need1 - Request1 = ( 2 1) - (1 0 ) = (1 1) , vilket ger Need := Vi vill nu testa om det nya läget är säkert.
11
1.
Låt Work := Available = (1 2)
false false
Finish := 2.
Process 1 är inte avslutad ännu, men det finns tillräckligt med resurser för att ge den dess maximala behov och sedan avsluta den: - Finish[1] = false - Need1 ≤ Work, d v s (1 1) ≤ (1 2)
3.
Uppdatera till läget då process 1 är avslutad och har lämnat tillbaka alla sina allokerade resurser: Work := Work + Allocation1= (1 2 ) + ( 2 1) = ( 3 3)
true false
Finish := 2.
Process 2 är inte avslutad ännu, men det finns tillräckligt med resurser för att ge den dess maximala behov och sedan avsluta den: - Finish[2] = false - Need2 ≤ Work d v s ( 2 2 ) ≤ ( 3 3)
3.
Uppdatera till läget då process 2 är avslutad och har lämnat tillbaka alla sina allokerade resurser. Work := Work + Allocation2= ( 3 3) + (1 0 ) = ( 4 3)
true true
Finish := 2. 4.
Inga processer kvar - gå till 4. Läget är säkert! Vi kan först ge P1 alla de resurser den behöver och låta den köra klart, och sedan göra samma sak med P2.
Övning I exemplet ovan kunde process 1 tilldelas sina begärda resurser. Vi tänker oss att därefter (i det nya läget) begär process 2 följande resurser: Request2 = (1 2) Kan det leda till död låsning? Använd Bankers algoritm för att avgöra det.
12
Upptäcka dödlåsning Bakgrund Antag igen att datorsystemet har m olika resurser och kör n olika processer. Vi vill nu avgöra om systemet har hamnat i ett läge med död låsning. Observera att detta inte är självklart att avgöra eftersom systemet kan vara i dödlåsning även om det finns några processer som just nu kör. Tänk er exempelvis en dator som kör tre processer A,B och C. De två första processerna är pausade och väntar på att resurer ska frigöras medans process C kör. Det kan nu finnas två situationer: antingen så håller process C de resurser som behövs och kommer så småningom att frigöra dem, isåfall så kommer A och B att få köra i framtiden och vi har inte någon dödlåsning; alternativt så kan kan process A hålle de resurser som B behöver och virce versa, i så fall kommer de aldrig mer att kunna få de resurser de behöver och systemet sägs vara i dödlåsning. Naturligtvis så kan det vara betlydligt mer avancerat av avgöra om ett system är i dödlåsning eller inte och vi behöver en algoritm för det. Om man följer algoritmen nedan så fungerar detta alltid för att avgöra om ett system är i dödlåsning eller inte. Data Följande data behövs: Available[j] där 1 ≤ j ≤ m - hur många enheter av resurs j som för tillfället finns tillgängliga. Allocation[i,j] där 1 ≤ i ≤ n, 1 ≤ j ≤ m - hur många enheter av resurs j som för tillfället är tilldelade process i. Request[i,j] - antal enheter av resurs j som process i väntar på. Obs! Här är Request en matris som täcker alla processers nuvarande resursbegäran. Algoritmen För att avgöra om man har död låsning, försöker man hitta ett sätt att ge varje process sin begärda kvot av resurser genom att upprepa de två stegen: (1) välj ut en process som kan tilldelas sin begärda kvot av resurser i det nuvarande läget och sedan köra klart; och (2) notera att processen är klar och frigör de resurser som den har allokerat så att återstående processer kan använda dem. Om detta lyckas för samtliga processer är allting väl; annars har vi en död låsning med de återstående processerna inblandade. Lägg märket till att algoritmen för att avgöra om ett läge är säkert är väldigt lik den här algoritmen, för att upptäcka död låsning. Den enda skillnaden är att i den förra talar vi om processernas maximala behov av resurser, medan i den senare talar vi om processernas begärda kvot av resurser. Temporära data: Work[j] - hur mycket av resurs j som finns tillgängligt vid varje steg. Finish[i] - process i är avslutad. Steg: 1. Låt Work := Available
false om Allocationi ≠ 0 true annars
Finish[i] := 2.
3.
4.
Hitta ett l (d v s en process) sådant att både - Finish[l] = false - Requestl ≤ Work d v s process l är inte avslutad ännu, men det finns tillräckligt med resurser för att ge den vad den väntar på och sedan avsluta den. Finns inget sådant l: gå till 4. Låt Work := Work + Allocationl Finish[l] := true d v s uppdatera till läget då process l är avslutad och har lämnat tillbaka alla sina allokerade resurser. Gå tillbaka till 2. Om Finish[i] := false för något i=1,..,n så är systemet i ett läge med död låsning.
13
Exempel Vi tänker oss att datorsystemet befinner sig i följande läge: Available = ( 0 2)
2 1 2 0 0 1 Request = 1 0 Allocation =
1.
Låt Work := Available = ( 0
2)
false false
Finish := 2.
Process 1 är inte avslutad ännu, men det finns tillräckligt med resurser för att ge den vad den väntar på och sedan avsluta den. - Finish[1] = false - Request1 ≤ Work, d v s ( 0 1) ≤ ( 0 2)
3.
Uppdatera till läget då process 1 är avslutad och har lämnat tillbaka alla sina allokerade resurser. Work := Work + Allocation1= ( 2 3)
true false
Finish := 2.
Process 2 är inte avslutad ännu, men det finns tillräckligt med resurser för att ge den vad den väntar på och sedan avsluta den. - Finish[2] = false - Request2 ≤ Work d v s (1 0) ≤ ( 2 3)
3.
Uppdatera till läget då process 2 är avslutad och har lämnat tillbaka alla sina allokerade resurser. Work := Work + Allocation2= ( 4 3)
true true
Finish := 2. 4.
14
Inga processer kvar. Gå till 4. Ingen död låsning! Vi kan först ge P1 alla de resurser den begär och låta den köra klart, och sedan göra samma sak med P2.
Övning Vi tänker oss att datorsystemet befinner sig i följande läge: Available = ( 0 1)
2 1 3 1 1 0 Request = 0 2 Allocation =
Har vi död låsning?
15
Schemaläggning När man utvecklar programvara för ett inbyggt system så räcker det vanligtvis inte med att se till att programmet är logiskt korrekt, d v s inte innehåller buggar med felaktiga pekare, semaforer som aldrig släppts, loopar som aldrig slutar, variabler som ges fel värden eller andra typer av programmerings- och designfel i koden. Man måste också kontrollera att programmet lyckas leva upp till de tidskrav man ställer när det exekveras på den hårdvara (processor etc.) som det är avsett för. Om man t ex har en process som ska utföra en viss beräkning var 50 ms samtidigt som beräkningen tar 100 ms att göra på den valda processorn, så får man se till att antingen göra beräkningen på ett snabbare sätt eller välja en snabbare processor. Det hela kompliceras dessutom ofta av att man har flera olika processer i systemet, var och en med sina egna tidskrav. Schemaläggning handlar om hur man fördelar processortid (och processorer, om man har flera sådana) mellan olika processer så att processernas olika tidskrav uppfylls. I ett realtidsoperativsystem sköter kärnans schemaläggare om själva växlandet mellan processer, men det är programmerarens uppgift att sätta prioriteter, lägga in fördröjningar o s v för att styra vilka processer som exekverar när. Ingredienser Olika sorters processer När man talar om schemaläggning brukar man skilja mellan två sorters processer. • Periodiska processer utför en uppgift regelbundet, med en viss periodicitet. Det kan t ex röra sig om att läsa mätdata eller köra ett varv i en reglerslinga var 50 ms. Sådana processer har explicita tidsgränser; i synnerhet måste varje jobb vara klar inom en period. Med ett "jobb" menar vi t ex varje enskild läsning av mätdata eller varje varv i reglerslingan. Ibland kan jobben i en process ha tidigare tidsgränser än början på nästa period. • Aperiodiska eller sporadiska processer reagerar på externa händelser som inte uppvisar någon regelbunden periodicitet, utan kanske ibland kan komma ofta (i en skur) och ibland inte komma alls under lång tid. Det kan t ex röra sig om en varningssignal, kommunikation med en annan process eller kommandon från en operatör. Även sådana processer är associerade med tidsgränser. I synnerhet kräver man ofta att systemet svarar på händelsen inom en viss tid. Generellt är periodiska processer lättare att schemalägga än aperiodiska, eftersom de senares beteende är svårare att förutsäga (kom ihåg: ickedeterminism!). Tidsgränser Det finns ett flertal olika sorters tidsgränser som kan vara relevanta för en process. Det vanligaste är "dödlinjer" (deadlines) som handlar om att processen måste vara klar med en viss beräkning innan en viss tidpunkt. Man kan också vara intresserad av den minimala respektive maximala tiden från det att en händelse sker till att processen börjar exekvera, eller den tid det tar för processen att exekvera, både exklusive och inklusive eventuella väntetider under exekveringen. Man skiljer på två sorters tidsgränser med avseende på hur avgörande de är: • Hårda tidsgränser måste alltid hållas. En överskriden hård tidsgräns kan leda till att hela systemet fallerar eller orsaka allvarliga skador. • Mjuka tidsgränser bör hållas, men man kan acceptera att de överskrids emellanåt, eller med liten marginal. En försening kan påverka effektiviteten av systemet, men ska normalt inte få katastrofala konsekvenser. Vilken typ av tidsgränser man har inverkar naturligtvis på hur man väljer att lösa schemaläggningen. Ofta har man en blandning av båda sorternas tidsgränser. Prioritet Att tilldela processer olika prioriteter är ett viktigt instrument inom schemaläggning. Det man kanske först tänker på är att använda prioriteter för att indikera hur viktig en viss process är relativt andra processer. De avgör vilka processer som får företräde när processorkraften inte räcker till för alla processer. Det är tanken bakom föregripande prioritetsbaserad schemaläggning: om en process med högre prioritet blir körklar, t ex på grund av någon händelse, så avbryter den processer med lägre prioritet. Att låta prioriteter stå för viktighet är relevant för mjuka tidsgränser, men fungerar inte lika bra för hårda tidsgränser. I det senare fallet kan man inte tillåta att någon process inte hinner med. Längre fram kommer vi att titta på hur prioriteter kan användas för att indikera hur ofta en viss process behöver exekvera, i så kallad "rate monotonic" schemaläggning. Det är vanligt att tilldela prioriteter en gång för alla innan processerna startar. Detta kallas för statiska prioriteter, och är t ex det typiska för processer i VxWorks. Det finns dock schemaläggningsalgoritmer som utnyttjar dynamiska prioriteter. Dessa schemaläggningsalgoritmer beräknar och tilldelar nya prioriteter fortlöpande.
16
Synkronisering De olika processerna i ett inbyggt system exekverar inte alltid oberoende av varandra. Ofta synkroniseras de med varandra med t ex semaforer och meddelandeköer. Detta komplicerar bilden ytterligare. Bland annat så kan det vara svårt att bedöma hur lång tid det kan ta för en process att utföra en viss beräkning om det ingår synkronisering med andra processer i beräkningen, och processen kan tvingas vänta. Detta gör processens exekveringstid svårbedömd. Ett annat knivigt problem är prioritetsinvertering. Det kan uppstå när en process P1 med hög prioritet väntar på t ex en semafor som hålls av en process P3 med låg prioritet. Om samtidigt en process P2 med mellanhög prioritet är körklar, så kommer den att ges företräde framför den lågt prioriterade P3. Eftersom P3 håller en semafor som P1 väntar på, fördröjs P1 indirekt av P2. Med andra ord, en process (P2) fördröjer exekveringen av en annan process med högre prioritet (P1) (se fig 1). Detta är naturligtvis inte en önskvärd situation.
Figur 1: Inverterad prioritet. Gråa staplar visar när processen exekverar. Problemet med prioritets invertering kan i viss mån hanteras med prioritets ärvning. Detta innebär att om en process håller en resurs som en högre prioriterad process väntar på, så ärver den förra processen prioriteten av den senare. I exemplet ovan skulle detta innebära att P3 ärver P1s höga prioritet så länge som P1 väntar på semaforen som P3 håller. Därigenom kan P3 fortsätta exekvera tills den har gett semaforen till P1. I det läget återgår P3 till sin ursprungliga låga prioritet (se fig 2).
Figur 2: Prioritets ärvning Prioritetsärvning är en metod som t ex används för mutex-semaforer i VxWorks. Tyvärr är metoden beroende av att man kan identifiera vilken process det är som kan ge den högt prioriterande processen (P1) det den väntar på. När det gäller en mutex-semafor så är det enkelt att förutse vilken process som kommer att ge den tillbaka: det alltid samma process som en gång tog den. Det är betydligt svårare att förutse vilken process som kan sätta igång P1 om P1 väntar vid t ex en meddelandekö, som i princip vilken annan process som helst kan skicka meddelanden på. När går det att schemalägga en grupp processer? En viktig fråga i schemaläggning är när det är möjligt att schemalägga en viss grupp processer, d v s om det går att fördela processortid på ett sådant sätt att processernas olika tidskrav uppfylls. Vi antar (när inget annat sägs) att de tidskrav vi har är att avsluta varje jobb innan nästa period. Exekveringstid För att svara på det börjar man med att uppskatta hur lång tid det tar att exekvera ett "jobb" (t ex en periodisk beräkning) av varje process. För att göra en sådan uppskattning möjligt bör man bl a: • Undvika dynamisk minnesallokering. • Undvika dynamiskt skapande av nya processer.
17
• Se till att eventuella loopar har en känd övre gräns.1 • Undvika rekursion (nästlade funktionsanrop, där en funktion kan anropa sig själv). • Sätta begränsade väntetider på processkommunikation. För att sammanfatta: man bör undvika att göra saker som kan ta mycket olika lång tid vid olika tillfällen. Ibland kan t ex en process inte behöva vänta någon tid alls på en semafor, men vid andra tillfällen kan den behöva vänta väldigt länge. Därför blir processens exekveringstid svårbedömd. Framför allt kan den få en väldigt hög övre gräns. Nyttjandegrad Vet man hur långt tid det (maximalt) tar att exekvera ett jobb (e) av en periodisk process och med vilken periodicitet - d v s hur ofta - den exekverar (p) kan vi beräkna processens nyttjandegrad (N) enligt formeln:
N=
e p
Det går också att beräkna nyttjandegraden för en grupp av processer:
N totalt =
e e1 e2 + + ... + n p1 p 2 pn
Nyttjandegraden för en grupp processer kan berätta en del om huruvida det är möjligt att schemalägga processerna så att alla tidskrav uppfylls. Vi antar att processerna är oberoende. För och främst, om N totalt > 1 , d v s om nyttjandegraden är mer än 100%, så är det naturligtvis inte möjligt att schemalägga processerna på en enda processor. Hur är situationen om N totalt ≤ 1 ? Titta på följande processer P1, P2 och P3 (tabell 1). Process
Period Exekveringstid Nyttjandegrad (ms) (ms) P1 80 40 50% P2 40 10 25% P3 20 5 25% Tabell 1: tre processer med en total nyttjandegrad på 100% Tillsammans har dessa processer en nyttjandegrad på 100%. De går dock att schemalägga så att alla tidskrav uppfylls, som bilden nedan visar (fig 3). Lägg märket till att P1 hinner exekvera sammanlagt 40 ms varje 80 ms-period, att P2 hinner exekvera 10 ms varje 40 ms-period, och slutligen att P3 hinner exekvera 5 ms varje 20 ms-period.
Figur 3: Schemaläggning av processer med 100% nyttjandegrad Det är dock inte alltid som det är fallet att en grupp processer med nyttjandegrad mindre än 100% går att schemalägga om processernas prioriteter är statiska. Man kan teoretiskt visa att en grupp av n st periodiska och oberoende processer går att schemalägga på en processor om nyttjandegraden N totalt ≤ n ⋅ ( 21 / n − 1) , vilket för stora n går mot 0.69. Med en större nyttjandegrad 0.69 < N totalt ≤ 1.0 beror det på förhållandena mellan processernas periodicitet och deras respektive exekveringstid om schemaläggning är möjligt. Ett särskilt gynnsamt fall är om processerna är enkelt periodiska. Detta innebär att för varje par av processer Pi och Pj så att perioderna pi < pj, så är pj är en heltalsmultipel Förutom eventuella loopar utanför den del av koden som utgör en "omgång". T ex vill man ofta ha en oändlig loop som går ett varv varje period. Detta är helt OK. 1
18
av pi. En grupp processer med perioderna 20, 40 och 80 är till exempel enkelt periodisk. Med enkelt periodiska processer går det att schemalägga även då nyttjandegraden är 1.0. I andra situationer kan en betydligt lägre (ner till 0.69) nyttjandegrad vara nödvändig. Det finns schemaläggningsalgoritmer som dynamiskt ändrar processernas prioritet under exekveringen, och vi kommer att gå igenom dessa inom kort. Med en dynamisk prioritetsregim går det alltid att schemalägga om nyttjandegraden
N totalt ≤ 1.0
När det är svårt att uppskatta exekveringstiden Ofta är det svårt att få fram exakta siffror för processernas exekveringstider, och för aperiodiska processer finns det ingen exakt periodicitet eller avbrottsfrekvens. Eftersom aperiodiska processer ofta startas av ett avbrott som triggas av en händelse, så talar man ofta om avbrottsfrekvens i stället för periodicitet. I sådana fall behöver man (åtminstone): • Genomsnittlig exekveringstid • Värsta fallets exekveringstid Och för sporadiska processer även: • Genomsnittlig avbrottsfrekvens • Värsta fallets avbrottsfrekvens Tyvärr är exekveringstiden i det värsta fallet ofta avsevärt mycket större än i det genomsnittliga fallet. Om man schemalägger enbart efter värsta fallet, kan detta leda till en i praktiken mycket låg nyttjandegrad i det faktiska systemet. Ta t ex en grupp processer som i värsta fallet har en nyttjandegrad på 65% på en viss processor, men i genomsnittliga fallet har en på 6%. Om värsta fallet då inte är så sannolikt, så kan det verka vara ett visst slöseri. Men om man bara har processer med hårda tidsgränser, så är det nödvändigt att klara värsta fallet - även om det händer väldigt sällan. Om man å andra sidan har en blandning av processer med hårda respektive mjuka tidsgränser kan det vara rimligt att i stället kräva att: • Alla hårda tidsgränser ska klaras i värsta-fallet. • Alla mjuka tidsgränser ska klaras i genomsnitts-fallet. Därigenom kan man visserligen ibland råka ut för tillfällig överbelastning, om man under en period ligger över genomsnittet. Detta innebär att processorresurserna räcker inte till för alla processer med mjuka tidsgränser. Men, och detta är det viktiga, alla hårda tidsgränser blir alltid uppfyllda. I det tidigare exemplet kanske man i värsta fallet kan klara processerna med de hårda tidsgränserna på en hälften så snabb - och hälften så dyr - processor med en nyttjandegrad på 65%. Dock kan man i värsta fallet få en total nyttjandegrad på 130%, vilken innebär ett par missade mjuka tidsgränser. Schemaläggningsalgoritmer När man väl har kommit fram till att det går att schemalägga en grupp processer på en viss hårdavara, är nästa fråga hur. Här finns det en uppsjö olika algoritmer, och vi går igenom några av de mest kända. Föregripande prioritetsbaserad schemaläggning Vi har redan kortfattat nämnt principen bakom prioritetsbaserad föregripande schemaläggning: om en process med högre prioritet blir körklar, t ex på grund av någon händelse, så avbryter den processer med lägre prioritet. En process prioritet ska avspegla hur viktig processen är. Tyvärr kan denna schemaläggningsmetod bara garantera att den viktigaste processens tidsgränser klaras. Titta t ex på det tidigare exemplet med 100% nyttjandegrad, med prioriteter tillagda. Process
Period Exekveringstid Nyttjandegrad Prioritet (ms) (ms) P1 80 40 50% HÖG P2 40 10 25% MEDEL P3 20 5 25 LÅG Tabell 2: tre processer med en total nyttjandegrad på 100% inkl prioriteter. Om vi schemalägger dessa processer enligt den prioritetsbaserad föregripande principen får vi följande fördelning av processortid mellan processerna (fig 4).
19
Figur 4: Ett exampel på misslyckad föregripande prioritetsbaserad schemaläggning. Process P1 klarar sina tidskrav - den hinner exekvera ett fullständigt jobb varje 80 ms-period. Det är dock sämre ställt med P2, som missar varannan period, och P3, som missar två perioder på raken. Därför är den här schemaläggningsmetoden inte så lämplig för processer med hårda tidsgränser - i alla fall inte om man låter prioritet stå för viktighet. "Rate-monotonic" schemaläggning (RMS) "Rate-monotonic" schemaläggning2 (RMS) bygger på att man låter periodiciteten (och inte viktigheten) bestämma en process prioritet. Desto kortare period, d v s ju högra takt, en process har, ju högre prioritet tilldelas den. Namnet "ratemonotonic" kommer just av att om takten ("rate") ökar så ökar också prioriteten monotont ("monotonic"). RMS är lämplig för att schemalägga grupper av periodiska oberoende processer, särskilt om de relativa tidsgränserna är lika stora som perioderna. Det går dessutom att visa matematiskt att det här är en optimal algoritm för schemaläggning av periodiska oberoende processer med statiska prioriteter: om en grupp sådana processer går att schemalägga så går den att schemalägga med RMS. Därför kan man alltid känna sig säker med RMS om man har en nyttjandegrad på 69% eller lägre. Som vi redan har konstaterat så går det alltid att schemalägga i sådant läge, och därför så går det att schemalägga med RMS. I tabell 3 återvänder vi till exemplet med 100% nyttjandegrad, men med prioriteter satta enligt RMS. Process
Period Exekveringstid Nyttjandegrad Prioritet (ms) (ms) P1 80 40 50% LÅG P2 40 10 25% MEDEL P3 20 5 25% HÖG Tabell 3: tre processer med en total nyttjandegrad på 100% inkl prioriteter enligt RMS. Om vi tittar på hur dessa processer skulle exekvera, så får vi precis samma resultat som i fig 3 tidigare: P3 exekverar varje 20 ms-period, P2 varje 40 ms-period efter P3, och P1 blir uppdelad över den tid som återstår. Notera att det som skiljer RMS från prioritetsbaserad föregripande schemaläggning är hur man tolkar prioriteterna. I RMS sätts prioriteterna efter hur ofta en process exekverar, och inte efter hur viktig den är. När prioriteter väl är satta fungerar dock de två algoritmerna på precis samma sätt: högre prioriterade processer har alltid företräde. "Deadline-monotonic" schemaläggning (DMS) Hittills har vi antagit att varje jobb hos en process har en tidsgräns som är identisk med nästa periods början. Detta är dock inte alltid fallet i verkligheten. Om tidsgränserna skiljer sig från när nästa jobb börjar, är det lämpligare att använda algoritmen för deadline-monotonic (dödlinjes-monoton) schemaläggning (DMS). Vi kallar tidslängden mellan ett jobbs början och dess tidsgräns (dödlinje) för jobbets relativa tidsgräns. Vi antar även att alla jobb i en process har samma relativa dödlinje rd. I DMS tilldelar man processerna prioriteter efter deras relativa tidsgränser – ju kortare relativa tidsgränser rd, ju högre prioritet. Notera att i fallet då den relativa tidsgränsen är identisk med periodiciteten (rdi = pi) för varje process, så ger RMS och DMS identiska resultat. I annat fall så är DMS att föredra – den fungerar oftare än RMS. "Rate-monotonic" blir översatt till svenska ungefär "takt-monoton", vilket inte låter lika elegant. Därför håller vi oss till den engelska termen och dess förkortning (RMS). 2
20
Process
Period Exekveringstid Relativ Prioritet (ms) (ms) tidsgräns (ms) P1 80 40 40 LÅG P2 40 10 20 MEDEL P3 20 5 15 HÖG Tabell 4: tre processer med relativa tidsgränser. Tabell 4 visar ett exempel med relativa tidsgränser. Vi ser att i det här fallet får vi samma prioriteter som i tabell 3, och samma resultat. I det här fallet misslyckas dock schemaläggningen – den relativa tidsgränsen för P1 överskrids (se Fig 3). Schemaläggning med dynamiska prioriteter Vi avslutar genomgången av schemaläggningsalgoritmer med två algoritmer som använder sig av dynamiska prioriteter. • I Earliest-Deadline-First-algoritmen (EDF, tidigast dödlinje först) tilldelas processerna prioriteter efter hur nära det är tills nästa tidsgräns. Ju kortare tid, ju högre blir prioriteten. • I Least-Slack-Time-First-algoritmen (LST, minsta tidsglapp först) tilldelas processerna prioriteter efter hur litet tidsglappet d – t – x är, där d är nästa absoluta dödlinje för processen, t är den nuvarande tiden, och x är den återstående exekveringstiden av det nuvarande jobbet. Eller uttryckt annorlunda, eftersom (d – t) motsvarar den relativa tidsgränsen för det nuvarande (kommande) jobbet: tidsglappet är skillnaden mellan den relativa tidsgränsen och den tids som återstår att exekvera. Exempel Vi ger ett kort exempel på EDF-schemaläggning. Antag att vi har följande två processer (Tabell 5). Process
Period Exekveringstid Relativ (ms) (ms) tidsgräns (ms) P1 80 40 60 P2 30 10 20 Tabell 5: två processer med relativa tidsgränser. Vid tidpunkten t = 0 har vi tidsgränser 60 för P1 och 20 för P2, och sätter därför prioriteter H (hög) för P2 och L (låg) för P1. P2 exekverar ett jobb i 10 ms. När det jobbet är klart (t = 10) får P2 en ny tidsgräns 30+20=50. Eftersom P2 fortfarande har en närmare tidsgräns än P1 så behåller vi prioriteterna. Nu exekverar P1 tills t = 30, då P2s nästa jobb börjar och P2 tar över igen. P2 avslutar även det jobbet (t = 40), och får ny tidsgräns på 2·30+20 = 80. Detta leder till ett byte av prioriteter, så att P1 (med tidsgräns = 60) blir H och P2 blir L. P1 avslutar sitt första jobb vid t = 60, får ny tidsgräns 80+60 = 140, och de nya prioriteterna blir L för P1 och H för P2. Och så fortsätter det. I figur 5 kan vi se hur exekveringen utvecklar sig.
Figur 5: EDF-schemaläggning av processerna i tabell 4, med prioritetsbyten markerade. Både EDF och LST är optimalt effektiva schemaläggningsalgoritmer: om nyttjandegraden N totalt ≤ 1.0 och de relativa tidsgränserna är samma som perioderna, så går det alltid att schemalägga med EDF och LST. De har dock även nackdelar jämfört med RMS och DMS. De kräver att prioriteterna beräknas om regelbundet vilket leder till en del overhead (och en hel del extra programmeringsarbete i VxWorks), och de är svårare att analysera.
21
Sammanfattning Vi har diskuterat schemaläggning: hur man på en viss hårdvara fördelar processortid (och processorer, om man har flera sådana) mellan olika processer så att processernas olika tidsgränser klaras. Det finns två kvalitativa egenskaper hos de inblandade processerna som är särskilt viktiga: om de är periodiska eller aperiodiska, och om de har hårda eller mjuka tidsgränser. Dessutom måste man känna till (eller i alla fall uppskatta) hur ofta de exekveras (lätt för periodiska processer, svårt för aperiodiska) och hur lång tid varje exekveringsomgång tar. När man väl vet detta är det två frågor som måste besvaras. Först, om det går att schemalägga processerna. Här är nyttjandegraden en viktig indikator: om den är mindre än 69% går det, mellan 69% och 100% beror det på, och över 100% är inte möjligt på en processor. Om svaret är nej på den första frågan får man gå tillbaka till ritbordet igen. Om svaret är ja, är fråga nummer två "hur?" Här har vi diskuterat fem alternativ: föregripande prioritetsbaserad schemaläggning, "rate monotonic" schemaläggning, "dead-line-monotonic" schemaläggning (att använda när tidsgränserna skiljer sig från perioderna), samt "earliest-deadline-first" och "least-slack-time-first" som utnyttjar dynamiska prioriteter. Övningar Försök schemaläggande följande processgrupper med föregripande prioritetsbaserad schemaläggning, "rate monotonic" schemaläggning och för övning 3 även EDF-schemaläggning. Obs! Nyttjandegraderna har avsiktligt utelämnats, och "viktighet" är inte alltid samma sak som "prioritet". Övning 1 Process Period (ms) P1 100 P2 50 P3 25 Övning 2 Process Period (ms) P1 50 P2 40 P3 30 P4 20 Övning 3 Process Period (ms) P1 150 P2 50
22
Exekveringstid (ms) 60 10 5
Nyttjandegrad
Exekveringstid (ms) 10 10 5 5
Nyttjandegrad
Exekveringstid (ms) 50 10
Nyttjandegrad
Viktighet Medel Låg Hög Viktighet Medel Låg Högst Hög Viktighet Låg Hög
Petri-nät Inledning Petri-nät är en metod för att modellera och analysera vissa typer av system som förändras över tiden. Metoden utvecklades ursprungligen av Carl Adam Petri (1962) för att användas för att modellera datorsystem med asynkrona processer och asynkron kommunikation. Den har också använts för att modellera affärssystem, hårdvarusystem och produktionssystem. Syftet med att använda Petri-nät för att modellera olika sorters system är att försöka avgöra om ett system beter sig som det ska - t ex om ömsesidig uteslutning fungerar - eller om systemet kan hamna i något oönskat tillstånd, t ex död låsning. Genom att använda Petri-nät kan man upptäcka en del fel och buggar i designen av ett realtidssystem - något som kan spara mycket tid och möda jämfört med om man upptäcker samma problem när man t ex redan har byggt en prototyp av systemet.
Ett Petri-näts uppbyggnad Petri-nät är en grafisk metod, men samtidigt matematiskt väldefinierad. Därför är Petri-nät förhållandevis bekväma att använda manuellt samtidigt som de är lämpliga för datorsimuleringar. Ett Petri-nät beskriver ett system i termer av tillstånd, som kan delas upp i olika villkor (t ex ett löpande band är på eller av, processen väntar på en viss resurs) och i termer av händelser som är övergångar mellan tillstånd (t.ex. bandet slås på, processen får sin resurs). Med andra ord: • Tillståndet för ett system, t ex ett inbyggt datorsystem, är summan av de villkor som håller i systemet, t.ex. att process 1 skriver ut data på skärmen och process 2 utför en beräkning på ett indatum. • När en händelse sker så upphör en del villkor att gälla och nya tillkommer. Om t ex en varningssignal anländer (händelse) så slutar process 1 att skriva ut data (villkor som upphör) och skriver istället ut larmmeddelanden (villkor som tillkommer). Då kommer systemet att befinna sig i ett nytt tillstånd, där process 1 skriver ut att process 1 skriver ut data på skärmen och process 2 utför en beräkning på ett indatum. larmmeddelanden och process 2 (fortfarande) utför en beräkning på ett indatum. Ett Petri-nät består av två sorters noder. Platser representerar de olika villkor som kan hålla i systemet, och ritas som ringar. Övergångar representerar olika händelser, och ritas som ett tjock streck (en balk). Platser och övergångar kan bindas ihop med riktade bågar som ritas som pilar.
Bågar kan gå från en plats till en övergång, eller från en övergång till en plats.
Figur 6: Ett enkelt Petri-nät Figuren ovan beskriver ett enkelt Petri-nät med två platser (p1 och p2) och två övergångar (t1 och t2). En plats som har en båge till en övergång kallas för en in-plats till övergången. Platsen representerar ett för-villkor för övergången, dvs ett villkor som måste hålla för att den händelse som övergången representerar ska kunna ske. En plats som tar emot en 23
båge från en övergång kallas för en ut-plats till övergången. Platsen representerar ett efter-villkor för övergången, dvs ett villkor som kommer att hålla när övergångens händelse har skett.
Ett Petri-näts beteende Tecken och tillstånd Att ett visst villkor håller markeras med ett tecken på motsvarande plats. Ett tecken ritas som en svart prick. I figuren nedan visas ett tecken i platsen p1.
Figur 7: Ett Petri-nät med ett tecken Genom att placera ut tecken så bestämmer vi nätets tillstånd. Ett tillstånd M talar helt enkelt om hur många tecken det finns i varje plats. Nätet ovan befinner sig t ex i tillståndet där M(p1) = 1 och M(p2) = 0, d v s det finns ett tecken i p1 och inget tecken i p2. Alternativt kan vi beteckna tillståndet med en matris (1 0), där vi låter de olika positionerna i matrisen svara mot antal tecken i var sin plats; i det här fallet enligt (p1 p2).
En övergång avfyras En övergång är aktiverad om det finns ett tecken i övergångens samtliga in-platser. Om en övergång är aktiverad kan den fyras av. Detta innebär att ett tecken tas bort i varje in-plats, och att ett tecken sätts i varje ut-plats. Om det redan finns ett tecken i en ut-plats så läggs inte ett nytt till. En övergång som avfyras representerar en händelse i systemet. Dess in-platser representerar villkoren som måste hålla innan övergången fyras av men inte efter (för-villkoren), och dess ut-platser representerar villkoren som kommer att hålla efter händelsen (efter-villkoren). Figur 8 visar läget före och efter en avfyrning av övergången t1. Villkoret i plats p1 måste hålla för att t1 ska vara aktiverad, och så är fallet här. Efter avfyrningen försvinner tecknet i p1 och ett tecken läggs till i p2, d v s ett nytt villkor håller. t1
p1
•
t1
p2
p1
t2
p2
•
t2
Figur 8 Övergången t1 avfyras Det kan även finnas tecken på flera platser i nätet, och fler än en båge kan gå till/från en plats eller en övergång vilket nästa figur illustrerar.
24
Realtidsprogrammering Örebro Universitet
Figur 9 En övergång med flera in-platser avfyras Om flera övergångar är aktiverade så är ordningen de avfyras icke-deterministisk. Slumpen avgör vilken som avfyras. Se till exempel på nätet nedan (Figur 10). Här finns det två aktiverade övergångar, t1 och t2. Det är både möjligt att fyra av t1 först, och då kommer nätet att se ut som i det övre fallet. Notera att efter avfyrningen är t2 inte längre aktiverad. Det är också möjligt att fyra av t2 först, och då kommer nätet att se ut som i det nedre fallet.
Figur 10 Ett icke-deterministiskt tillstånd (t.v.) där två olika övergångar kan avfyras (t.h.) I fall som i Figur 10 då endast en av två aktiverade övergångar kan avfyras, så säger vi att övergångarna är i konflikt. Om å andra sidan avfyrandet av en övergång inte deaktiverar en annan övergång, så säger vi att övergångarna är samtidiga. Detta är t ex fallet i Figur 11. t1
t3 • p2
p1 t4
p4
• t2
p3
Figur 11 Två samtidiga övergångar t3 och t4.
Händelseutvecklingar En följd av övergångar som avfyras utgör en händelseutvecklig, t ex (t1, t2, t1, t2, t1) i det enkla cirkulära nätet i Figur 7. Lägg märket till att det räcker att ange vilka övergångar som avfyras: utifrån den informationen kan vi entydigt rekonstruera tillståndet i nätet vid varje tidpunkt. Övergångsföljden (t1, t2, t1, t2, t1) ger t ex tillståndsföljden ( (1 0), (0 1), (1 0), (0 1), (1 0), (0 1) ).
25
När vi analyserar ett Petri-nät med icke-deterministiska situationer (som ovan) måste vi ta hänsyn till alla möjliga val av aktiverade övergångar, och alla efterföljande händelseutvecklingar. Om vi t ex vill garantera att ett system inte kan hamna i en situation med död låsning, så måste vi testa alla möjliga händelseutvecklingar i systemet för att se om död låsning kan inträffa i någon av dem. I det icke-deterministiska exemplet ovan behöver vi beakta båda händelseutvecklingarna (t1) och (t2). De olika möjliga händelseutvecklingarna i ett Petri-nät kan representeras som en graf, där bågarna är övergångar och noderna är tillstånd (på matrisform). T ex har vi för det cirkulära nätet i Figur 7 följande graf (Figur 12) med två tillstånd. Starttillståndet är markerat med en kort pil.
t1 (1 0)
(0 1) t2
Figur 12 Graf med tillstånd och övergångar för Petri-nätet i Figur 7 Genom att följa bågarna i nätet kan vi generera de olika möjliga händelseutvecklingarna, och vi kan också se vilka tillstånd vi kommer att gå igenom. Tänk på att tillstånden beskriver i vilka platser vi har tecken. Detta är den enda del av nätet som ändras över tiden. Platserna och övergångarna är oförändrade. En händelseutveckling kan bli oändligt lång, dvs det finns ingen sista avfyrning i händelseutvecklingen. För Figur 7 så har vi en oändlig händelseutveckling som består av följden t1,t2 upprepad om och om igen.
Speciella övergångar: generatorer och stopp Det kan finnas övergångar utan några in-platser, eller utan några ut-platser. De förra kallas generatorer, och är alltid aktiverade och kan därför alltid skicka nya tecken till sina ut-platser. De senare kallas stopp, och absorberar ("äter upp") tecken från sina in-platser. I Figur 13 ser vi hur ett tecken genereras (i t1) och sedan stoppas (i t2). t1
t2
t1
t2
t1
t2
• p1
p1
p1
Figur 13 Petri-nät med generator och stopp
Ömsesidig uteslutning med Petri-nät Nu kommer vi att titta på ett enkelt exempel på hur man kan modellera ömsesidig uteslutning med Petri-nät. Tänk er att vi har två processer, P1 och P2, som ägnar sig åt att beräkna ett värde och sedan skriva ut det på en display, i en oändlig loop. De två processerna kan modeleras med Petri-nät enligt Figur 14. Processen P1 representeras av två platser, P1 beräknar och P1 skriver. Processen P2 består också av två platser, P2 beräknar och P2 skriver.
Figur 14 Petri-nät som beskriver två processer Tyvärr kan bara ett värde skrivas ut åt gången. Därför är utskriftsdelarna i de respektive processerna att betrakta som kritiska regioner. För att förhindra att de två processerna försöker skriva ut värdet på displayen samtidigt använder vi en
26
Realtidsprogrammering Örebro Universitet semafor som läggs runt om utskriftsdelarna. Figur 15 nedan visar hur Petri-nätet kan se ut. Semaforen har egen plats, sem ledig, som är ihopkopplad med de två processerna via övergångarna P1 tar sem, P2 tar sem, P1 ger sem och P2 ger sem.
Figur 15 En semafor läggs till Det här Petri-nätet är i ett tillstånd där båda processerna befinner sig i beräkningsskedet och semaforen är ledig. I Figur 16 nedan är en möjlig händelseutveckling - övergången P1 tar sem fyras av.
Figur 16 Process P1 tar semaforen I det nyuppkomna tillståndet är det inte längre möjligt för övergången P2 tar sem att aktiveras - in-platsen Sem ledig saknar markering. Detta svarar mot att för-villkoret att semaforen ska vara ledig inte håller i det nuvarande tillståndet. Faktum är att den enda aktiva övergången nu är P1 ger sem. Detta resulterar i nästa Petri-nät (Figur 17).
Figur 17 Process P1 ger tillbaka semaforen Vi är alltså tillbaka till utgångsläget igen. En annan möjlig händelseutveckling är att övergången P2 tar sem fyras av först, och sedan P2 ger sem. Det här systemet kan dock aldrig hamna i situationen att bägge processerna befinner sig i den kritiska regionen samtidigt, förutsatt att starttillståndet är som ovan. Vi ska nu studera vad som kan hända med en felaktigt konstruerad ömsesidig uteslutning. Antag att man har glömt att P1 ska ge semaforen efter en utskrift. Petri-nätet nedan (Figur 18) illustrerar detta.
27
Figur 18 Lösning där process P1 inte ger tillbaka semaforen En möjlig händelseutveckling här är att övergången p1 tar sem fyras av (Figur 19).
Figur 19 P1 tar semaforen
28
Realtidsprogrammering Örebro Universitet Denna övergång följs av P1 skriver klart (Figur 20).
Figur 20 P1 har skrivit klart, och nätet är dött I det här läget finns det inga övergångar som är aktiverade. Både P1 tar sem och P2 tar sem behöver ett tecken i sem ledig, men det saknas. Bägge processerna har avstannat!
29
Övning 1 Följande Petri-nät (Figur 21) föreställer två processer som synkroniseras med två semaforer. Analysera Petri-nätets olika möjliga händelseutvecklingar. Vad är det för välbekant fenomen som kan uppstå här?
Figur 21 Petri-nät med två semaforer
30
Realtidsprogrammering Örebro Universitet
Petri-nät med flera tecken per plats Det finns flera varianter av Petri-nät. I den variant vi har presenterat så här långt kan varje plats innehålla högst ett tecken. Om det vid en övergång redan finns ett tecken på en ut-plats, så lägger man inte till ett nytt. Platser i sådana nät representerar villkor som är antingen sanna eller falska, t ex om ett löpande band är på eller av. Ibland är man dock intresserad av att representera kvantitativa villkor såsom att det finns 4 st chokladbitar på det löpande bandet, eller 6 meddelanden i en meddelandekö. Detta kan göras med en variant av Petri-nät som tillåter mer än ett tecken i varje plats. Denna variant tillåter dessutom viktade bågar, som tar flera tecken från en in-plats, eller ger flera tecken till en ut-plats. Detta markeras med en siffra vid bågen. Ett sådant Petri-nät visas i nedan.
Figur 22 En övergång med viktade bågar avfyras Lägg märke till att vi använder två tecknen från p1 vid övergången t1, och ett tecken från p2. Samtidigt lägger vi bara till ett tecken vid p3, eftersom bågen dit har vikten 1. Om antalet tecken i en plats kan bli stort, skriver man ut en siffra i stället för att rita ut tecknen ett och ett, som i nätet i Figur 23 här nedan.
Figur 23 Nät med siffror för att representera antalet tecken
Exempel: meddelandekö
31
Figur 24 Petrinät med två processer som kommunicerar med en meddelandekö Figur 24 visar ett Petri-nät som modellerar två processer P1 och P1. Dessa kommunicerar genom en meddelandekö med plats för ett begränsat antal meddelanden (4 st). Vi använder två platser för att modellera kön: en för att hålla reda på hur många meddelanden där finns, och en för att hålla reda på hur många fler meddelanden som får plats i kön. I figuren finns det för ögonblicket två meddelanden som ligger och väntar i kön, och plats för två till.
Övning 2 Vilka olika händelseutvecklingar kan Petri-nätet i Figur 23 genomgå? Rita ut dem som en graf med övergångar och tillstånd, med start på (0 4). Om du är ambitiös, så gör samma sak med Figur 24.
32
Realtidsprogrammering Örebro Universitet
Egenskaper hos Petri-nät Ett skäl till att använda en matematisk metod som Petri-nät för att modellera ett system är att man kan analysera olika egenskaper hos systemet. Här tittar vi närmare på ett antal intressanta egenskaper som har att göra med möjliga händelseutvecklingar i ett Petri-nät.
Nåbarhet Givet ett Petri-nät i ett visst initialtillstånd M, så är ett annat tillstånd M’ nåbart, om det finns en händelseutveckling (ti1, ti2, … tin) som leder från M till M’. I Figur 7 så är tillståndet (0 1) nåbart från initialtillståndet (1 0), medan t ex (1 1) ej är nåbart (se även Figur 12). Nåbarhet är viktigt för att analysera systemet kan hamna i något förbjudet eller oönskat tillstånd. Det kan t ex handla om Petri-nät som modellerar autopiloten hos ett flygplan, och vi vill analysera om flygplanet kan hamna i ett tillstånd där det landar utan att ha fällt ner landningsställen. Om vi analyserar ömsesidig uteslutning, kan det handla om huruvida två processer kan komma att befinna sig i den kritiska regionen samtidigt.
Begränsat och säkert Petri-nät Ett Petri-nät med initialtillstånd M sägs vara begränsat om det finns en övre gräns k för antalet tecken i varje plats i nätet i tillståndet M och alla tillstånd som är nåbara från M. Med andra ord, oavsett vilken händelseutvecklig vi följer så kommer vi aldrig att råka ut för att en plats innehåller fler än k tecken. Till exempel är näten i Figur 23 och Figur 24 begränsade, bägge med k=4. För t ex meddelandeköer och buffrar kan begränsning vara en mycket viktig egenskap. Om den övre gränsen råkar vara 1 (k=1) så säger vi att nätet är säkert.
Levande Petri-nät Ett Petri-nät är levande om det aldrig kan råka ut för död låsning. Död låsning innebär att det finns övergångar som omöjligen kan avfyras. Definitionen på ett levande nät är: För varje tillstånd M’ som är nåbart från initialtillståndet M, så gäller det att varje övergång i nätet kan så småningom avfyras i något tillstånd M’’ som är nåbart från M’. Till exempel är Petri-nätet i Figur 15 levande, eftersom i samtliga tillstånd vi kan hamna i finns det alltid möjlighet att avfyra samtliga fyra övergångar, antingen direkt eller några få steg senare. Nätet i Figur 18 är däremot ej levande: vi kan vi nå ett tillstånd (Figur 20) där vissa (i det här fallet samtliga) övergångar aldrig mer kan komma att avfyras.
Rättvisa Rättvisa handlar i korta drag om att ingen övergång/händelse sker för sällan. Det finns flera olika rättvise-begrepp: Två övergångar sägs vara ömsesidigt begränsade rättvisa om det finns en övre gräns för hur många gånger den ena övergången kan avfyras medan den andra inte avfyras alls, och vice versa. En oändlig händelseutveckling sägs vara globalt rättvis om varje övergång förekommer oändligt många gånger i händelseutvecklingen. Ett Petri-nät sägs vara globalt rättvis om varje händelseutveckling från initialtillståndet är globalt rättvis. Till exempel så är det enkla cirkulära nätet i Figur 7 globalt rättvist. I den enda möjliga händelseutvecklingen (t1,t2,t1,t2,t1,t2,…) förekommer varje övergång oändligt ofta, vilket innebär att händelseutvecklingen är globalt rättvis. Dessutom är t1 och t2 ömsesidigt begränsade rättvisa.
Täckande tillstånd En variant av nåbarhetsproblemet är det sk. täckbarhets problemet. Antag att vi har ett petrinät och ett tillstånd M, vi säger att M är täckbar om man ifrån initialtillståndet kan nå ett tillstånd M' så att för varje plats i gäller M'[i] >= M[i]. Frågan om täckbarhet är intressant då vi inte är intresserade av om vi kan nå ett specifikt fullständigt tillstånd utan endast är intresserade av märkningen av vissa platser.
33
Övning 3 Försök besvara följande frågor: • I Figur 15: är något tillstånd där det finns tecken i både P1 skriver och P2 skriver nåbart från initialtillståndet? Med andra ord, fungerar vår kritiska region inte som den ska? • I Figur 24: Vi har påstått att detta nät är begränsat, med k=4. Visa ett tillstånd med just 4 tecken i någon plats. Varför kan det aldrig bli fler än 4 tecken någonstans? • I Figur 21 respektive Figur 24: är dessa Petri-nät levande? Motivera dina svar. • Är Petri-näten i Figur 15 respektive Figur 24 rättvisa? Motivera dina svar.
34
Realtidsprogrammering Örebro Universitet
Mjukvara för analys av petrinät När man vill modelera och simulera lite mer realistiska system än de enkla exempel vi sett hittils så blir näten oftast lite större och mer komplicerade. Detta gör att nåbarhetsgraferna har en tendens att växa och bli väldigt stora, i princip så kan ett petrinät med P platser som är K-begränsat få en nåbahetsgraf av storlek K P vilket för icke triviala K och P är orealistiskt att generera för hand. För att hantera dess petrinät så använder man oftast datorbaserade modellerings och simulerings verktyg. Modellerings verktygen används för att (ofta grafiskt) rita upp petrinät och eventuella extra parameterar. Detta för att underlätta att skapa och visualisera petrinätet, så att man kan övertyga sig om att det generarede petrinät motsvarar det fenomen som man vill simulera. Modelleringsverktygen kan ofta spara petrinäten i ett standardiserat filformat, exempelvis PNML vilket är ett XML baserat format för att beskriva petrinät. Se bilden nedan för hur detta kan se ut. Simulerings och analys verktygen använder beskrivningen av ett petrinät för att antingen generera en nåbarhetsgraf, eller för att sammanställa diverse statistik över dess egenskaper (levande, begränsat, etc). Resultaten av simuleringen och analysen kan ibland presenteras visuellt, ibland som text för vidare data behandling.
Platform Independent PetriNet Editor (PIPE 2) Detta är ett exempel på ett fritt tillgängligt (Java) program för både modellering och simulering/analys av petrinät. Ni kan hitta den på den här länken http://pipe2.sourceforge.net/, och föreslås ladda ner och testa den hemma. Testa gärna först att simulera några av de exempel petrinät som följer med programmet. Se bilden till höger för ett exempel på en modell för dining philosofers problemet samt den automatgenererade nåbarhetsgrafen för nätet. Försök över att återskapa några av petrinätet som är givna som övningar i den här boken. Blir de genererade nåbarhetsgraferna samma som de som ni gjort för hand?
35
Analys av mer avancerade petrinät Ni har tidigare sett hur man konstruerar tillståndsgrafen för ett petrinät. I de fall då ett petrinät har ett ändligt antal tillstånd är detta ett enkelt sätt att testa om nätet är ex. levande, man behöver ju bara se om det går att nå ett tillstånd ifrån vilket inga andra tillstånd kan nås. I praktiken fungerar dock detta inte alltid då många petrinät har oändligt (eller iaf. väldigt många) tillstånd. Ta exempelvis petrinätet nedan, som ni ser har den oändligt många tillstånd eftersom man kan få hur många tecken som helst på plats P3.
Lösningen på det problemet är att skapa ett sk. täckbarhetsträd för petrinätet. Skillnaden mellan tillståndsgrafen och täckbarhetsträdet för ett petrinät är att trädet inte innehåller några cykler samt att vi i trädet kan ha tillstånd innehållande symbolen ω på en plats för att markera att det kan finnas godtyckligt (oändligt) många tecken där. För att konstruera täckbarhetsträdet för ett petrinät så utgår vi ifrån initialtillståndet M som den första noden i trädet. Sedan fortsätter vi med att tillämpa reglerna nedan på varje löv M' i trädet tills dess att det inte går att expandera trädet vidare. 1. Om det finns ett M'' på vägen från M till M' så att M'' = M' så ska inte M' expanderas vidare. 2. Annars ska M' expanderas. För varje tillstånd M'' som kan nås från M' så ska en ny nod i trädet skapas. På varje plats där M' innehåller ω så ska M'' också innehålla ω. Vidare, om det finns ett tillstånd N i trädet på vägen från M till M'' så att M'' täcker N så ska den nya noden vara märkt med ω på alla platser där M''[i] > N[i]. Man kan med denna metod alltid skapa ett ändligt stort täckbarhetsträd för ett godtyckligt petrinät. I figuren nedan har ni täckbarhetsträdet för petrinätet från föregående exempel. Det är nu lätt att se att petrinätet exempelvis täcker tillståndet (0 1 14) samt att den kan råka ut för dödlåsning.
Observera att täckbarhetsträdet för ett petrinät inte säger hela sanningen om ett petrinät. Eftersom vi byter ut information om det exakta antalet tecken i en plats mot symbolen så kan vi bara vara säkra på att noderna i trädet täcker tillstånd som kan nås och kan alltså inte garantera att lösa ex. nåbarhets problemet.
36
Realtidsprogrammering Örebro Universitet
Utökningar av petrinät Vi har hittills sett endast två varianter av petrinät, ordinära petrinät som kan ha maximalt ett tecken per plats och generella petrinät utan begränsning. Detta är bara två av ett stort antal möjliga varianter och utökningar av petrinät. Vi ska här introducera några av dessa utökningar.
Petrinät med ändlig kapacitet En variant av ordinära petrinät är petrinät som för varje plats har ett bestämt maximum för hur många tecken som kan rymmas där. Detta tillägg till petrinät förändrar inte deras grundläggande beteende och det är lätt att se hur sådana petrinät kan skrivas om till vanliga ordinära (kapacitet 1) petrinät. Petrinätet nedan illustrerar hur en sådan omskrivning sker genom att byta ut varje plats med kapacitet k mot k st. platser i ett ordinärt petrinät samt lägga till övergångar som fritt kan flytta tecken mellan de nya platserna.
Petrinät med negation Detta är en variant av petrinät i vilken man kan ha sk. innhibitor bågar som är kopplade till övergångar. En sådan båge ritas med en cirkel i ena ändan och har till funktion att förhindra att övergången skjuter när platsen till vilken bågen är kopplad har ett tecken i sig. Om man betraktar det första petrinätet nedan så ser man att övergång T1 inte kan avfyras trots att det finns ett tecken i P1, detta eftersom tecknet i P2 pga. innhibitorbågen spärrar T1 från att avfyras. Däremot kan T2 avfyras eftersom den är aktiverad samt att det inte finns något tecken i P3. Naturligtvis kan inte T3 avfyras eftersom den inte är aktiverad.
Denna till synes enkla tillägg till petrinät är tillräckligt för att ge dom samma uttryckskraft som datorer. Tex. kan man betrakta det andra petrinätet ovan som ett enkelt program som adderar innehållet i register r1 till register r2. Tyvärr innebär denna uttryckskraft även att det blir mer avancerat att analysera näten. Att avgöra om ett generellt petrinät med negation är levande är exempelvis bevisat att vara oavgörbart, dvs. det går inte att skriva ett program som tar ett godtyckligt petrinät med innhibitorbågar och säger om det är levande eller ej.
Synkroniserade petrinät Hittills har vi bara modellerat och beskriv petrinät som exekverar utan någon extern påverkan. Vi ska nu undersöka en variant av petrinät i vilken vi inte bara kan beskriva vad som händer utan även när det händer genom att knyta en serie av externa händelser till exekverandet av ett petrinät. Ett synkroniserat petrinät är ett vanligt petrinät tillsammans med en mängd möjliga externa händelser samt en märkning som för varje övergång sätter ett krav på att en av de externa händelserna ska ske. I nätet nedan har vi tre övergångar. För att T1 eller T3 ska avfyras krävs att dom är aktiverade samt att händelsen e1 sker och för att T2 ska avfyras krävs att händelsen e2 sker.
37
Om e1 sker när vi befinner oss i initialtillståndet så kan T1 avfyras eftersom den är aktiv, vi säger att T1 är mottaglig för e1 i det här tillståndet. Eftersom T2 är knuten till händelsen e2 så kan inte T2 avfyras och eftersom T3 inte är aktiv så är den inte heller mottaglig trots att den är knuten till händelsen e1. Resultatet av att avfyra e1 blir tillståndet (0, 2, 0, 1, 0). Om istället händelsen e2 sker så kommer T2 att istället vara mottaglig. Petrinätet kommer du att avfyra T2 en gång och komma till tillståndet (1, 1, 0, 0, 1). För att bli av med bägge tecknen in P2 krävs alltså att händelsen e2 sker två gånger. Övergångar i ett synkroniserat petrinät kan även vara markerade med den neutrala händelsen e som alltid är uppfylld. Ett petrinät som inte har händelsen e knuten till någon övergång sägs vara ett totalt synkroniserat petrinät och ett tillstånd i vilket det går att avfyra övergångar markerade med e kallas instabilt. För att bestämma hur ett synkroniserat petrinät beter sig måste vi först introducera begreppet en Komplett Avfyrnings Sekvens (KAS). En KAS för händelsen E är en följd av avfyrningar S som från det givna tillståndet M uppfyller följande krav: 1. S är en följd av avfyrningar från tillståndet M som endast innehåller övergångar bundna till händelsen E. 2. Ingen övergång finns mer än en gång i S. 3. Alla permutationer S' av S är också giltiga avfyrnings sekvenser från M. 4. Det finns ingen längre sekvens S'' som innehåller S samt uppfyller kraven ovan. Om det finns en KAS S för händelsen E och S innehåller alla övergångar markerade med E så är S den enda KAS'en för denna händelse och tillstånd. Vi säger då att S är en maximal KAS. Det upprepade avfyrandet på den externa händelsen E för ett petrinät är att avfyra en godtycklig KAS för E eventuellt följd av avfyrandet av en eller flera KAS för e (den neutrala händelsen) tills dess vi når ett stabilt tillstånd. Algoritmen för att evaluera ett synkroniserat petrinät blir alltså: 1. Låt E vara nästa externa händelse. Om det inte finns fler händelser avsluta algoritmen. 2. Avfyra en godtycklig KAS för E i detta tillstånd. Om det inte finns någon sådan KAS gå tillbaka till steg 1. 3. Så länge det nya tillståndet är instabilt: Avfyra en godtycklig KAS för e. 4. Gå tillbaka till steg 1. Observera att algoritmen förutsätter att vi alltid når ett stabilt tillstånd efter tillräckligt många iterationer. Ett petrinät som uppfyller denna förutsättning kallas för ett prompt petrinät. Vi kan nu använda algoritmen på petrinätet nedan för att generera alla stabila tillstånd vi kan nå från initial tillståndet.
Lägg märke till hur vi markerat övergångarna i grafen över stabila tillstånd. I tillståndet (0 1 0 0 1 0 0) har vi sekvensen T2T5 som en KAS för e2, detta innebär att vi även har T5T2 som en KAS för e2 och alltså kan vi avfyra T2 och T5 i valfri ordning vilket markeras av {-parantesen runt T2T5.
38
Realtidsprogrammering Örebro Universitet
Petrinät med tid Ett petrinät med tid kan användas för att beskriva system vars funktioner är tidsberoende och kan användas för att exempelvis evaluera prestandan hos ett system. Det finns två sätt att bygga ut petrinät för att modellera att olika operationer tar tid; antingen kan man associera en konstant som representerar tidsåtgången hos varje plats i nätet eller hos varje övergång i nätet. I det första faller säger vi att nätet är P-timad och i den andra fallet T-timad (transition). Som vi kommer att se senare är den praktiska skillnaden mellan de två olika typerna mycket liten.
När ett tecken adderas till en plats P1 i ett P-timat petrinät med tidsåtgången d1 så måste den vara kvar på den platsen under åtminstånde d1 tidsenheter. Om vi vill undersöka det timade petrinätet till höger så kan vi skapa en tidsaxel på vilken vi ser hur många tecken som finns på varje plats i varje givet ögonblick. I den här exekveringen så avfyrar vi varje övergång så snart det går utan att bryta mot tidsgränserna. Vi säger att nätet körs på maximal hastighet.
För att visualisera beteendet hos ett petrinät med tid kan vi som för andra nät skapa dess tillståndsgraf och på varje båge anteckna hur lång tid från dess att vi kom till det här tillståndet det tar innan vi kommer till nästa tillstånd. Eftersom vi förrutom antalet tecken på varje plats även måste hålla reda på hur lång tid det tar innan tecknet är tillgänglig så måste vi göra skillnad på tillstånd som i ett vanligt nät betraktas som samma tillstånd. Vi kan göra detta genom att i representationen för tillstånd även inkludera den tid det tar innan tecknen är tillgängliga. På så sätt kan vi analysera det timade petrinätet i figuren nedan och se att det är cykliskt med en periodicitet på 5 tidsenheter. Notationen {T1,T2}/0 betyder här att övergångarna T1 och T2 har avfyrats (i vilken ordning spelar ingen roll) efter 0 tidsenheter från simuleringens start. Att nätet upprepar sig i en cykel med en konstant periodicitet är ingen slump. Det har visats att alla begränsade P-timade petrinät utan någon effektiv konflikt har ett cykliskt beteende med en given periodicitet. Ett petrinät som befinner sig i en sådan cykel sägs ha ett stationärt beteende. En egenskap som är av intresse för timade petrinät är avfyrningsfrekvensen hos övergångar vilken är definierad som antal avfyrning per tidsenhet som övergången har då petrinätet är i sitt stationära beteende. Exempelvis har T1 i det förra petrinätet en avfyrningsfrekvens av 2/5.0 = 0.4 avfyrningar per tidsenhet. En annan variant av timade petrinät är T-timade petrinät för vilka vi associerar tider på övergångarna. I ett T-timat petrinät kan våra tecken antingen vara lediga eller reserverade. I det första fallet så är tecknen fria att användas och närhelst en övergång har tillräckligt med fria tecken i dess inputplatser så kan vi bestämma oss för att avfyra den. Detta innebär att input tecknen blir reserverade tills dess att den tid som är associerad med övergången har passerat. När övergången har väntat tas de reserverade input tecknen bort och nya lediga tecken placeras på ut platserna. Observera att andra övergångar kan aktiveras medans vi väntar på denna övergång.
39
Eftersom T-timade petrinät kan skrivs om till P-timade petrinät så analysera vi dom inte djupare här utan nöjer oss med att konstatera att motsvarigheten till att köra ett P-timad när i maximal fart blir att varje övergång beslutas att avfyras omedelbart då det finns tillräckligt med lediga tecken i dess input platser.
Färgade petrinät Även om petrinät har visat sig vara mycket lämpliga för att visualisera och analysera olika system så har dom svagheten att dom inte har någon form av abstraktion. Detta gör att det lätt blir komplicerat och oöverskådligt då man modellerar mer avancerade system eftersom man blir tvungen att duplicera komponenter som man vill återanvända på flera ställen. Föreställ er exempelvis scenariot att vi har ett petrinät som implementerar funktionaliteten hos en semafor och att vi vill bygga ett mer avancerat system som innehåller ett antal semaforer. Vi blir då tvungna att ha en kopia av semafor komponent för varje semafor i det stor systemet, vilket naturligtvis snabbt innebär en massa likartade delar i det färdiga systemet. Färgade petrinät är en annan dialekt av petrinät som lämpar sig bättre för detta. Grundiden med färgade petrinät är att vi inte längre har en mängd identiska, anonyma tecken på varje plats. Genom att associera en mängd av färger (en datatyp) till petrinätet så kan vi hålla reda på olika tecken under exekveringen av nätet. Som vi kommer att se blir det därmed möjligt att återanvända delar av petrinätet för olika uppgifter, eg. ha ett delnät för att hantera semafor operationer som sedan kan användas för olika semaforer (representerade av tecken av olika färg). Förutom att ha en färg på varje tecken så innehåller ett färgat petrinät en funktion som för varje övergång ger en delmängd av färgerna som den kan användas på samt två funktioner Pre/Post som kan konvertera färger. Oftas kan man beskriva de här funktionerna genom att explicit ge alla dess utfall, eg. f(r) = g, f(g) = b osv.
Vi börjar med ett exempel. I petrinätet till vänster har vi två färger, r och g, och två övergångar. I start tillståndet är övergång T1 aktiv och den kan antingen avfyras med färgen r eller med färgen g. Om den avfyras med färgen g så kommer tecknet g att tas bort från P1 och läggas till i P2. I detta tillstånd kommer T1 fortfarande vara aktiv, men inte T2 eftersom T2 endast kan avfyras med färgen g. Eftersom övergångarna bara avfyras med avseende på en färg i taget så kommer tecken av olika färger inte att ha någon påverkan på varandra. Man skulle lika gärna kunna dela upp nätet i två olika delnät, ett som bara opererar på röda tecken och ett som bara opererar på de grön tecknen. Vi har alltså ännu inte tillfört något till funktionalitet hos petrinät genom att bara markera tillåtna färger till transitionen. Det är här som Pre- och Post-operationerna kommer in. Genom att använda sig av dom kan vi skapa mer avancerade petrinät som klarar av att modellera avancerade system.
Eftersom vi i det förra nätet inte har skrivit ut några Pre och Post funktioner så antas dom vara identitets funktionen, dvs om vi avfyrar T1 med färgen r så kommer ett r tecken tas bort från P1 och läggas till i P2. Om vi däremot betraktar nästa petrinät så ser vi att vi har en funktion f(x) som är associerad med övergång T3. Om man avfyrar T3 med färgen r så kommer ett r tecken att tas bort från P2 och ett f(r) (dvs ett grönt) tecken att läggas till i P2.
40
Realtidsprogrammering Örebro Universitet
Järnvägs scenario Vi ska nu skapa ett lite mer realistiskt scenario och se hur det ganska lätt kan modelleras som ett färgat petrinät. Föreställ er att vi ska modellera ett enkelt järnvägsnät. I det här nätet har vi två stationer, en sträcka mellan stationerna samt två tåg som åker fram och tillbaka mellan stationerna. På stationerna finns det dubbel spår och det kan därför stå flera tåg men på sträckan mellan stationerna finns det plats för endast ett tåg. Dock finns det en kort sträcka dubbelspår mellan stationerna så att två tåg kan mötas på ett säkert sätt. Vi kan nu modellera det här scenariot som ett färgat petrinät där vi har 5 platser som tågen kan åka på (stationerna, mötesplatsen och de två bitarna räls mellan stationerna och mötesplatsen) samt platser för att hantera två semaforer. Genom att ha färger som är antingen tecknet s eller en tupel av kan vi modellera tågen och deras riktning så att dom endast vänder när dom är frammer på en station. Det resulterande petrinätet finns nedan, dock utan märkning av färger och funktioner för övergångarna. För enkelhets skull ger vi först petrinätet en informel textform eftersom den grafiska beskrivningen av petrinätet lätt blir avancerad. Platser: station_A, station_B, räls_1, räls_2, mötesplats, ledig1, upptagen1, ledig2, upptagen2 Funktioner: F1(t1) = ; F1(t2) = F2(t1) = ; F2(t2) = FS(t1) = FS(t2) = s Övergångar: T1: Inplatser: station_A (F1), ledig1 (FS); T2: Inplatser: räls_1 (F1), upptagen1 (FS); T3: Inplatser: mötesplats (F1), ledig2 (FS); T4: Inplatser: räls_2 (F1), upptagen2 (FS); T5: Inplatser: station_B (F2), ledig2 (FS); T6: Inplatser: räls_2 (F2), upptagen2 (FS); T7: Inplatser: mötesplats (F2), ledig1(FS); T8: Inplatser: räls_1 (F2), upptagen1 (FS);
Utplatser: räls_1 (F1), upptagen1(FS); Utplatser: mötesplats (F1), ledig1 (FS) Utplatser: räls2 (F1), upptagen2 (FS) Utplatser: station_B (F2), ledig2(FS); Utplatser: räls2 (F2), upptagen2(FS); Utplatser: mötesplats (F2), ledig2 (FS); Utplatser: räls1 (F2), upptagen1 (FS); Utplatser: station_A (F1), ledig1(FS);
Färger: t1, t2; Färger: t1, t2; Färger: t1, t2; Färger: t1, t2; Färger: t1, t2; Färger: t1, t2; Färger: t1, t2; Färger: t1, t2;
Lägg märke till användandet av F2/F1 på övergångarna T4 och T5 vilket gör att tågen vänder (ie. ändrar färg från till ) när de kommer fram till stationerna. Observera att vi skulle kunnat ha återanvänt platserna som håller reda på om räls1 är ledig även för att göra detta för räls2 genom att ha två semafor färger s1 och s2. Vi valde dock att inte göra det i det här faller för att göra nätet lite enklare.
41
Övningar Övning 4: Petrinät med negation Rita nåbarhetsgrafen för petrinätet med negation till höger.
Övning 5: Petrinät med tid a) Rita tillstånds grafen för petrinätet med tid till höger b) Vilken avfyrningsfrekvens har petrinätet?
Övning 6: Färgade petrinät Antag att vi har det färgade petrinätet nedan: a) Rita upp nåbarhetsgrafen för petrinätet om möjligt. b) Är petrinätet begränsat? c) Är petrinätet levande?
42
Realtidsprogrammering – instuderingsfrågor Inbyggda system 1. Vilka egenskaper bör inbyggd mjukvara ha? Ge en kortfattat förklaring till varje punkt. 2. Förklara tidsgränser (deadlines) och hårda och mjuka realtidssystem. 3. Förklara: beräkningsprocess (computational process) , exekverande beräkningsprocess, asynkrona processer, synkronisering, resurs, monolitiskt system, realtids-OS, realtidsspråk? 4. Beskriv "vattenfallsmodellen", och vad dess olika steg innebär. Processer 1. Hur kan flera processer exekvera samtidigt på en processor? På flera processorer? (2.3) 2. Vad är externa händelser, hur kan de hanteras och vad har de för viktig konsekvens (2.4.3)? Kommunikation 1. På vilka sätt kan processer växelverka (interact)? 2. Vilka typer av synkronisering finns det? 3. Förklara hur en semafor fungerar och vad den används till. 4. Vad är en monitor och vad har den för syfte (kortfattat, med fokus på C)? 5. Förklara hur meddelandebaserad kommunikation fungerar. Vilka är för- och nackdelarna med kopierad information? 6. Beskriv klient-betjänare-modellen (Client-Server) med meddelandebaserad kommunikation. Dödlåsning & detektering 1. Vad innebär död låsning (deadlock)? 2. Vilka är Coffmans fyra villkor för en situation med död låsning? 3. Vilka metoder finns det för att undvika/hantera död låsning? Relatera till Coffmans villkor. 4. Vad är utsvältning (starvation) och rättvisa (fairness)? Formel programvaru utveckling 1. Vad är syftet med ett specifikationsspråk (specification- and description language) och när används det (i "vattenfallsmodellen")? Nämn ett sådant språk. 2. Vad innebär analys och testning och när används de?3 3. Vad innebär automatisk analys (formal structure analysis), manuell analys, och granskning? 4. Beskriv kortfattat ett antal metoder för testning. 5. När kan man ha användning av en simulator för att validera ett system (kortfattat)? Övrigt Förutom de två böckerna ingår ochså vad vi har gjort på laborationerna och (naturligtvis) det här kompendiumet.
Analys är någonting man gör när man har en specifikation (design eller kod), för att hitta eventuella fel i specifikationen. Man gör det ofta innan man bygger/kodar det delsystem eller den modul som specifikationen beskriver. Det är ju lite slöseri att bygga ett delsystem och upptäcka att man har gjort en miss i designen när delsystemet är klart. Testning, å andra sidan, är någonting man gör när man har en (nästan) färdigbyggd modul eller delsystem. I testning använder man den färdigbyggda modulen och kör den med olika indata och i olika situationer och jämför det faktiska resultatet med det avsedda resultatet. Jämför t ex med en ingenjör som bygger en bro. Analys kan t ex vara att göra hållfasthetsberäkningar innan man bygger bron, och test kan vara att köra ett par tunga lastbilar över bron för att bekräfta att den håller. 3
43
Blandade övningar Det följande är ett urval av övningar tagna från gamla tentor. Övning 1 (001027) Vi har tre stycken asynkrona, parallella processer P1, P2 och P3 som utnyttjar tre olika sorters gemensamma resurser R1, R2 och R3. Det finns bara en enhet av R1 och R2, men två enheter av R3. Varje resursenhet går bara att använda i en process åt gången. Processerna använder resurserna enligt: P1
P2
P3
Begär R1 Begär R3 … Återlämna R3 Återlämna R1
Begär R3 Begär R2 … Återlämna R2 Återlämna R3
Begär R2 Begär R3 Begär R1 … Återlämna R1 Återlämna R3 Återlämna R2
(a) Vilka processer och sekvenser av operationer ger problem här, och vilken sorts problem (b) Visa hur man kan arrangera om i vilken ordning resurser begärs och återlämnas för att undvika detta problem. Tips: Du behöver inte använda några avancerade algoritmer för den här uppgiften. Tänk bara igenom vad som kan hända. Övning 2 (001027) I ett datorsystem körs de tre processerna P1, P2 och P3 som alla använder samma sorts resurs R1. Vid ett tillfälle är resurserna fördelade enligt tabellerna Available för resursenheter som inte används av någon process (vi har 2 enheter av R1 lediga i det här fallet) och Allocation för resursenheter som är tilldelade en viss process. Dessutom innehåller tabellen Max det maximala antalet resursenheter som varje process totalt kan behöva, och tabellen Need innehåller hur många fler resursenheter som varje process kan behöva utöver de den redan har tilldelats. Available: R1 ( 2)
Allocation: R1 P1 2 P2 4 P3 0
Max:
Need:
R1 P1 4 P2 4 P3 4
R1 P1 2 P2 0 P3 4
Vid det här tillfället så begär process P3 två enheter av resursen R1. Med andra ord, vi har: R1 Request3: P3 ( 2 ) Kan P3 tilldelas sina två R1 utan risk för dödlig låsning? Visa det nya läge som skulle uppstå om P3 tilldelas det den har begärt, d v s uppdatera Available, Allocation och Need. Avgör om detta nya läge är säkert. Redogör detaljerat för varje steg.
44
Realtidsprogrammering Örebro Universitet
Övning 3 (001027) Du har två processer som ser ut som nedan: void Process1() { while(1) { do_something(); /* Sync */ do_something_else(); } }
void Process2() { while(1) { do_whatever(); /* Sync */ do_some_stuff(); } }
Du vill nu göra en full synkronisering av dessa två processer, d v s du vill programmera dem så att de väntar på varandra vid platserna markerade med "Sync" i koden. (a) Visa hur detta kan åstadkommas med hjälp av semaforer. Du kan använda följande kommandon: semTake(sem, WAIT_FOREVER) semGive(sem) sem = semBCreate(SEM_Q_FIFO, startvärde) (b) Beskriv din lösning i ett Petri-nät, och visa start-tillståndet. Visa också två möjliga exekveringar av nätet (det räcker att ange vilka övergångar som sker). Du kan utgå från nätet på nästa sida. Tips: varje semafor blir en ny plats. Du kan utgå från nätet nedan:
45
Övning 4 (010112) Du håller på att konstruera ett realtidssystem för ett elektronisk anti-flug-system. Du behöver fyra oberoende periodiska processer fly_radar, swat_arm_control, buzzer och user_interface. De har perioder och exekveringstider enligt tabellen nedan. (Obs! "Viktighet" är inte alltid samma sak som "prioritet"). Process Fly_radar Swat_arm_control Buzzer User_interface
Period (ms) 200 100 150 250
Exekveringstid (ms) 40 30 15 50
Viktighet HÖGST HÖG MEDEL LÅG
Är dessa processer schemaläggningsbara? Om så är fallet, visa hur med ett diagram, och ange vilken metod du använder. Övning 5 (011026) Betrakta följande Petri-nät, som beskriver en vanlig dag i en frisör-salong. Här finns fyra frisörer som klipper anländande kunder.
t1: kund anländer P1: kund väntar t2: kund tar plats P4: frisör väntar på kund (och röker)
P2: kund blir klippt av frisör t3: klippning klar P3: kund vid kassan t4: kund går
Vi vill nu undersöka ett antal egenskaper hos det här Petri-nätet. (a) Vad innebär nåbarhet? Är tillståndet (5 0 0 4), där 5 kunder väntar (plats P1) och 4 frisörer också väntar (plats P4), nåbart från initialtillståndet ovan? (b) Vad innebär det att ett Petri-nät är begränsat? Är nätet här ovan begränsat? (c) Vad innebär det att ett Petri-nät är rättvist? Är nätet här ovan rättvist? Motivera dina svar om nätet ovan utifrån de definitioner du ger.
46
Realtidsprogrammering Örebro Universitet
Övning 6 (011026) Vi har tre stycken processer, tank, meter och safety, med respektive prioriteterna 100, 100 och 90 (där 90 är högst!). Dessa processer exekverar i VxWorks med förgripande schemaläggning med tidsdelning på 100ms. Processen tank startar något före meter, och ligger därför före i processkön. Här finns också en mutexsemafor mutex. De tre processerna gör följande: Process: tank Process: meter Process: safety Prioritet: 100 Prioritet: 100 Prioritet: 90 Processen startar Processen startar Processen startar Exekverar 80 ms Exekverar 100 ms Exekverar 50 ms semTake(mutex, 200ms) taskDelay(50ms) taskDelay(150ms) Exekverar 120 ms Exekverar 50ms semGive(mutex) semTake(mutex, 200ms) Exekverar 50 ms Exekverar 20ms semGive(mutex) taskDelay(100ms) Beskriv vad som händer i det här systemet: - vilken process som exekverar när, - i vilka tillstånd processerna befinner sig, samt - vad som händer med semaforens tillstånd, enligt mönstret: - 0ms: mutex=1, processerna tank, meter och safety startar och samtliga blir READY. - 0ms: safety börjar exekvera - osv Illustrera gärna också med ett diagram vad som händer över tiden. Uppgift 7 (020111)
Tänk dig att du ska implementera tre asynkrona processer Head, Arm och Leg. Head beräknar två styrvärden, och skickar ett var till de väntande Arm och Leg. Dessa två processer använder sedan sina styrvärden. De arbetar parallellt, och när de är klara - inte nödvändigtvis samtidigt - skickar de tillbaka vart sitt statusmeddelande till den väntande Head. Förloppet upprepas sedan cykliskt. (a) Visa hur detta kan åstadkommas med hjälp av meddelandeköer. Ge koden för de tre processerna, och för att initiera de använda datastrukturerna. Du kan använda följande kommandon för meddelandehantering: mq = msgQCreate (maxMsgs, maxMsgLength, MSG_Q_FIFO); msgQSend(mq, buffer, Nbytes, WAIT_FOREVER, 0); msgQReceive(mq, buffer, maxNBytes, WAIT_FOREVER); Kom ihåg: ett meddelande är en textsträng (char buffer[n]). Dessutom finns det funktioner för att beräkna och använda styrvärden och statusvärden - du kan själv döpa dessa funktioner men behöver inte ge koden för dem. Dessa funktioner kan antas använda textsträngar. (b) Beskriv din lösning i ett Petri-nät, och visa start-tillståndet. Visa också en möjlig exekvering av nätet (det räcker att ange vilka övergångar som sker).
47
Övning 8 (020111) I ett datorsystem körs de tre processerna P1, P2 och P3 som använder resurserna R1 och R2. Vid ett tillfälle är resurserna fördelade enligt tabellerna Available för resursenheter som inte används av någon process och Allocation för resursenheter som är tilldelade en viss process. Dessutom innehåller tabellen Request det antal resursenheter som varje process väntar på för tillfället. Available: R1 R 2 (1 10)
Allocation: R1 R 2 P1 2 10 P2 1 5 P3 1 0
Request: R1 P1 1 P2 0 P3 3
R2 12 10 0
(a) Befinner sig systemet i ett läge med dödlig låsning? Redogör detaljerat för varje steg. (b) Vilket är det minsta antalet resurser som kan finnas tillgängliga för att systemet ej ska befinna sig i dödlig låsning? D v s vilket är den minsta tänkbara Available (allt annat lika)?
48
Lösningar till övningar Övning i kapitel Bankers algoritm: Vi hade följande begäran: Request2 = (1 2) Vi konstaterar att villkoren i steg 1 och steg 2 i Bankers är uppfyllda, och går vidare till steg 3. Vi testar vad det nya läget skulle vara: Available := Available - Request1 = (1 2) - (1 2) = ( 0 0) 2 1 Allocation2 := Allocation2 + Request2 = (1 0 ) + (1 2) = ( 2 2 ) , vilket ger Allocation := 2 2 1 1 Need2 := Need2 - Request2 = ( 2 2 ) - ( 2 1) = ( 0 1) , vilket ger Need := 0 1 Vi vill nu testa om det nya läget är säkert. Låt Work := Available = ( 0 0) false Finish := false 2. Process 1 är inte avslutad ännu, men det saknas tillräckligt med resurser: - Finish[1] = false - Need1 ≰ Work, d v s (1 1)≰ (0 0). Samma sak gäller för process 2: - Finish[2] = false - Need2 ≰ Work, d v s (0 1)≰ (0 0). 1.
1. Vi har fortfarande: false Finish := false Och alltså är läget ej säkert. Vi bör ej tilldela de begärda resurserna! Övning i kapitel Upptäcka död låsning: Låt Work := Available = (0 1) false Finish := false 2. Process 1 är inte avslutad ännu, men det saknas tillräckligt med resurser: - Finish[1] = false - Request1 ≰ Work, d v s (1 0)≰ (0 1). Samma sak gäller för process 2: - Finish[2] = false - Request2 ≰ Work, d v s (0 2)≰ (0 1). 1.
4. Vi har kvar: false Finish := false Alltså har vi död låsning!
49
Övningar i kapitel schemaläggning: Övning 1. Nyttjandegraden här blir 1.0, och processerna är kanske schemaläggningsbara. Vi försöker med RMS (vi har markerat varje avslutad period för P1 med ett tjockt streck, eftersom P1 delas upp på flera segment):
Med föregripande prioritetsbaserad schemaläggning får vi:
Här ser vi hur P2 missar en del av sina dödlinjer. Inga lösningar ges för övningar 2 och 3.
50
Realtidsprogrammering Örebro Universitet
Övningar i kapitel Petri-nät Övning 1 Ett par möjliga händelseutvecklingar: t1-1, t1-2, t1-3, t1-4, t2-1, t2-2, t2-3, t2-4, osv (process 1 kör först, sedan 2) t2-1, t2-2, t2-3, t2-4, t1-1, t1-2, t1-3, t1-4, osv (process 2 kör först, sedan 1) t1-1, t1-2, t1-3, t1-4, t1-1, t1-2, t1-3, t1-4, osv (process 1 kör flera gånger) t2-1, t2-2, t2-3, t2-4, t2-1, t2-2, t2-3, t2-4, osv (process 2 kör flera gånger) t1-1, t2-1, stopp - inga övergångar aktiva! Död låsning. t2-1, t1-1, stopp - inga övergångar aktiva! Död låsning. Övning 2
Övning 3, fråga 1: Nej, det tillståndet är ej nåbart. Se följande graf, med tillstånden i ordningen (P1-beräknar, P1-skriver, semledig, P2-skriver, P2-beräknar)
Det efterfrågade tillståndet skulle se ut: (0 1 0 1 0) (där 0:orna i princip kan bytas ut mot 1:or). Det är uppenbarligen ej nåbart från initialtillståndet! Övning 3, fråga 2: Ett tillstånd med fyra tecken i en plats: (1 0 0 4 0 1) enligt schemat (P1-beräknar, P1-förbereder, ledigt, meddelanden, P2-förbereder, P2-beräknar). Vi kan se att vi kan aldrig få mer än ett tecken i looparna P1beräknar - P1-förbereder och P2-beräknar - P2-förbereder eftersom det inte finns någon övergång som kan föra in nya tecken utan att använda samma antal tecken i loopen. Av samma skäl kan vi aldrig få fler än 4 tecken i loopen ledigt - meddelanden. Detta kan också ses i nätets tillståndsgraf för den som ritar ut denna.
51
Övning 3, fråga 3: Nätet i figur 16 är ej levande. Som redan konstaterats i övning 1 så finns det händelseutvecklingar som leder till tillstånd där ingen övergång kan avfyras. Nätet i figur 19 är levande. Oavsett tillstånd så kan vi avfyra vilken övergång som helst inom ett par steg. (Kan ses i nätets tillståndsgraf). Övning 3, fråga 4: Nätet i figur 10 är ej rättvist. T ex kan vi låta avfyra P1 tar sem och P1 ger sem i all oändlighet utan att någonsin avfyra P2 tar sem och P2 ger sem. Nätet i figur 19 är rättvist. T ex kan vi bara avfyra P1 beräkning klar och P1 sänder högst 4 gånger var (i det nåbara tillståndet (1 0 4 0 0 1)) utan att avfyra P2 beräkning klar och P2 tar emot. Övning 4: Se nåbarhetsgrafen till höger Övning 5: a) Se nåbarhetsgrafen till höger b) Petrinätet har en avfyrningsfrekvens av 0.75 eftersom den avfyrar 3 övergångar över 4 sekunder i sitt cykliska tillstånd.
Övning 6: a) Se nåbarhetsgrafen till höger b) Ja, den är begränsad (1-begränsad) c) Ja, den är levande Observera att det här färgade petrinätet tydligen bara kan avfyras map. på färgen grönt. Det gäller inte generellt utan blev så bara pga. märkningen in till T3. T3(g) kan inte avfyras eftersom det aldrig finns två gröna tecken på P3. T1(g) resp T2(g) kan inte avfyras eftersom det aldrig finns något grönt tecken på P1 eller P2.
52
Realtidsprogrammering Örebro Universitet
Blandade övningar Svar 1: (a) Ett exempel på en sekvens av operationer som leder till problem: 1. P1: Begär R1 2. P2: Begär R3 3. P3: Begär R2 4. P3: Begär R3 Efter de här operationerna är alla resurser upptagna (1 st R1 av P1, 1 st R2 av P3, och 1 st R3 av P2 och P3 vardera). P1 väntar på en R3, P2 väntar på en R2 och P3 väntar på en R1. Vi har död låsning. (b) Ett exempel på en lösning: P1
P2
P3
Begär R1 Begär R3 … Återlämna R3 Återlämna R1
Begär R2 Begär R3 … Återlämna R3 Återlämna R2
Begär R1 Begär R2 Begär R3 … Återlämna R3 Återlämna R2 Återlämna R1
Samtliga processer begär resurserna i samma ordning! Svar 2: Använd "bankers"-algoritmen. Nytt läge: Available: R1 ( 0)
Allocation: R1 P1 2 P2 4 P3 2
Låt Work := Available =
Max: P1 P2 P3
Need: R1 4 4 4
P1 P2 P3
R1 2 0 2
R1 P1 P 2 P3 , och låt Finish := . ( 0) ( false false false )
Vi ser att Need2 = ( 0) ≤ Work , så vi kan låta P2 köra färdigt först och sedan av-allokera dess resurser. R1 P1 P 2 P3 Detta ger Work := och Finish := ( 4) ( false true false ) Vi ser att Need1 = ( 2) ≤ Work , så vi kan nu också låta P1 köra färdigt först och sedan av-allokera dess resurser. Detta ger Work :=
R1 P1 P 2 P3 , och Finish := . ( 6) ( true true false )
53
Till sist ser vi att Need3 = ( 2) ≤ Work , så vi kan nu också låta P3 köra färdigt först och sedan av-allokera dess resurser. R1 P1 P 2 P3 , och Finish := . ( 8) ( true true true ) Alla processer är klara, och vi har inte hamnat i död låsning. Alltså är det nya läget säkert, och P3 kan tilldelas de resurser den har begärt! Detta ger Work :=
Svar 3: (a) Först gäller det att initialisera semaforerna: sem1 = semBCreate(SEM_Q_FIFO, 0); sem2 = semBCreate(SEM_Q_FIFO, 0); Sedan placerar vi dem här: void Process1() { while(1) { do_something(); semGive(sem1); semTake(sem2, WAIT_FOREVER); do_something_else(); } }
void Process2() { while(1) { do_whatever(); semTake(sem1, WAIT_FOREVER); semGive(sem2); do_some_stuff(); } }
Det går även att kasta om ordningen på semGive och semTake i Process2, men då finns det viss risk att den ena processen hinner gå runt ett varv och göra ett nytt semGive innan den andra processen har hunnit göra sitt semTake. Detta kan kanske orsaka problem, beroende på hur de binära semaforerna är implementerade. (a) Petrinätet ser ut så här:
Övergångarna t2 och t5 motsvarar semGive(sem1) och semGive(sem2), och t3 och t4 motsvarar semTake(sem2) och semTake(sem1). Man kan också lägga till ett tillstånd alldeles innan var och en av t3 och t4, som representerar att processen väntar på semaforen.
54
Realtidsprogrammering Örebro Universitet
Två möjliga exekveringar: t2, t4, t5, t3, t1, t6 t2, t4, t5, t3, t6, t1 Svar 4: Den totala nyttjandegraden blir 40/200+30/100+15/150+50/250 = 0,2+0,3+0,1+0,2 = 0,8, vilket ligger mellan 0,69 och 1,0. Det går alltså inte direkt att säga om processerna går att schemalägga. Men vi vet om att de går att schemaläggarna, så går det med "rate monotonic"-metoden (RMS). Om man prövar RMS så får man:
Svar 5: Se kapitel "Petri-nät" för definitionerna. (a) Det finns en händelseutveckling (t1, t1, t1, t1, t1) som leder från initialtillståndet till (5 0 0 4), och därför är det senare nåbart. (b) Nej, det är inte begränsat. Det kan finnas ett obegränsat antal tecken i P1 (genom att man kan upprepa t1 hela tiden). (c) Nej, det är det inte heller. Det finns t ex en oändlig händelseutveckling (t1, t1, t1, t1, t1, ...) helt bestående av t1, där de andra övergångarna aldrig händer. För att nätet ska vara rättvist måste alla övergångar ske oändligt många gånger i varje oändlig händelseutveckling. Svar 6: - 0ms: mutex=1, tank, meter, safety startar och blir READY - 0 ms: safety börjar exekvera - 50ms: safety gör taskDelay(150ms) och blir DELAYED, tank börjar exekvera. - 130ms: tank gör semTake(mutex), mutex=0. - 150ms: processbyte pga tidsdelning, meter börjar exekvera - 200ms: safety blir READY igen, safety börjar exekvera - 250ms: safety gör semTake( mutex), safety blir PENDING, läggs i mutex kö, tank som håller mutex ärver safetys prioritet (90), exekverar - 350ms: tank gör semGive(mutex), tank återfår sin tidigare prioritet (100), safety som ligger i mutexs kö blir READY och tas bort från kön, safety exekverar - 370ms: safety gör semGive(mutex), mutex=1, safety gör taskDelay(100ms), blir DELAYED, meter exekverar - 420ms: meter gör taskDelay(50ms), blir DELAYED, tank exekverar - 470ms: slut Att tänka på:
55
-
Om man kör tidsdelning och en process blir avbruten av en annan högre prioriterad process så kommer den förra processen ihåg hur länge den hade exekverat innan den blev avbruten (den har en timer), och kommer att exekvera den återstående tiden när det blir dess tur igen. Prioritetsärvning finns för mutex-semaforer i VxWorks.
Se kapitel ”Processer i VxWorks”.
56
Realtidsprogrammering Örebro Universitet
Svar 7 (a) void init() { … Create tasks … /* Init queues*/ arm_status_q = msgQCreate (10, MSG_LENGTH, MSG_Q_FIFO); arm */ leg_status_q = msgQCreate (10, MSG_LENGTH, MSG_Q_FIFO); leg */ arm_cmd_q = msgQCreate (10, MSG_LENGTH, MSG_Q_FIFO); /* */ leg_cmd_q = msgQCreate (10, MSG_LENGTH, MSG_Q_FIFO); /* */ }
/* status from /* status from Command to arm Command to leg
void head() { char arm_cmd[MSG_LENGTH], leg_cmd[MSG_LENGTH], arm_status[MSG_LENGTH], leg_status[MSG_LENGTH]; for(;;) { computeCommands(arm_cmd, leg_cmd); msgQSend(arm_cmd_q, arm_cmd, MSG_LENGTH, WAIT_FOREVER, 0); msgQSend(leg_cmd_q, leg_cmd, MSG_LENGTH, WAIT_FOREVER, 0); msgQReceive(arm_status_q, arm_status, MSG_LENGTH, WAIT_FOREVER); msgQReceive(leg_status_q, leg_status, MSG_LENGTH, WAIT_FOREVER); evaluateStatus(arm_status, leg_status); } } void arm() { char cmd[MSG_LENGTH], status[MSG_LENGTH]; for(;;) { msgQReceive(arm_cmd_q, cmd, MSG_LENGTH, WAIT_FOREVER); executeCommands(cmd, status); msgQSend(arm_status_q, status, MSG_LENGTH, WAIT_FOREVER, 0); } } void leg() { char cmd[MSG_LENGTH], status[MSG_LENGTH]; for(;;) { msgQReceive(leg_cmd_q, cmd, MSG_LENGTH, WAIT_FOREVER); executeCommands(cmd, status); msgQSend(leg_status_q, status, MSG_LENGTH, WAIT_FOREVER, 0); } }
57
Svar 7(b) Head
T1: send
Arm_cmd_q
Arm T6: send exe
comp
T2: send
Arm_status_q T7: rec
T5 Leg eval
T3: rec
T8: send Leg_cmd_q
T4: rec
Leg_status_q
exe T9: rec
Svar 8 (a) Låt Work := (1 10) och Finish := (f f f). Vi ser att Work >= Request2, så vi kan låta P2 få sina resurser och avsluta. Detta ger Work := (2 15) och Finish := (f s f). Vi ser att Work >= Request1, så vi kan låta P1 få sina resurser och avsluta. Detta ger Work := (4 25) och Finish := (s s f). Vi ser att Work >= Request3, så vi kan låta P3 få sina resurser och avsluta. Detta ger Work := (5 25) och Finish := (s s s). Därmed är vi klara och kan se att systemet befinner sig ej i dödlig låsning. (b) Vi kan låta Available = (0 10). Då kan P2 och sedan också P1 och P3 köras klart (visa hur!). Om Available = (0 9) eller mindre kan ingen process köra klart.
58