En problemställning är att realisera primitiver (funktioner) som måste programmeras vid inträde i och vid utträde ur (godtyckliga) kritiska avsnitt.

Relevanta dokument
Föreläsning 15: Parallella subrutiner. Parallellitet. Varför parallella underprogram?

Fö 5+6 TSEA81. Real-time kernel + Real-time OS

Några gamla tentamensuppgifter: Processer. 3. Antag givet ett system i vilket rent CPU-bundna processer med följande egenskaper exekveras.

Operativsystem - Processkommunikation

Mekanismer. (implementation)

Datorteknik ERIK LARSSON

Institutionen för elektro- och informationsteknologi, LTH

Datorteknik. Föreläsning 5. Realtidssystem och realtidsprogrammering. Institutionen för elektro- och informationsteknologi, LTH.

Tentamen Grundläggande programmering

TDDIU81. Processer och trådar. Andreas Dahlberg, Jonathan Doherty, Tony Magnusson, Patrik Ottosson, Rasmus Siljedahl

Synkronisering. Föreläsning 8

Realtidssystem. - Dödläge - EDAF85 - Realtidssystem (Helsingborg) Elin A. Topp. Föreläsning 5

F5: Högnivåprogrammering

Synkronisering - Semaforen. Om att vänta men inte i onödan

F5: Högnivåprogrammering

Föreläsning 6: Introduktion av listor

Datastrukturer. föreläsning 3. Stacks 1

Synkronisering. Ordning och reda

Datalogi, grundkurs 1. Lösningsförslag till tentamen

Vad är viktigast? Sammanfattning. Processer och trådar. Processer och trådar. Flerprocessorsystem. Schemaläggning. Interprocesskommunikation.

Summering av fält. Synkronisering. Summering av fält. Bounded Buffer. Bounded Buffer (get) Bounded Buffer (put)

Dagens OS. Unix, Linux och Windows. Unix. Unix. En översikt av dagens OS Titt på hur de gör. Många varianter Mycket gemensamt. En del som skiljer

Tentamen. Datorteknik och realtidssystem

Operativsystem ID2200 Tentamen TEN1 3.8 hp :00-18:00

Föreläsningsanteckningar, Introduktion till datavetenskap HT S4 Datastrukturer. Tobias Wrigstad

Olika OS. Unix, Linux och Windows. Unix. Unix. En översikt av ett par OS. Titt på hur de gör. Många varianter. Mycket gemensamt. En del som skiljer

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 3 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Trådar. Aktiva objekt

Outline. Datorsystemtekni. Kravspecifikation. Kravspecifikation (forts.)

Introduktion till formella metoder Programmeringsmetodik 1. Inledning

Pipelining i Intel Pentium II

TENTAMEN TDDB53. Programmering i Ada för MI (provkod TEN2) den 7 april 2010 kl Institutionen för datavetenskap, IDA Olle Willén mars 2010

Exam Concurrent and Real-Time Programming

Svar till tentamen den 16 december 2013 Datorarkitekturer med operativsystem, EDT621, 7,5 poäng

syftar till att förbättra prestanda. Den kan avse något eller flera av följande mått.

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 3 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Per Holm Lågnivåprogrammering 2014/15 24 / 177. int och double = = 2, 147, 483, 647

Realtidssystem. - Semaforer, trådsynkronisering - EDAF85 - Realtidssystem (Helsingborg) Elin A. Topp. Föreläsning 2

Fö 8: Operativsystem II. Minneshantering. Minneshantering (1) Minneshantering (2) Minneshantering och Virtuelltminne.

GRUNDER I VHDL. Innehåll. Komponentmodell Kodmodell Entity Architecture Identifierare och objekt Operationer för jämförelse

ADT Kö. Seminarium 4 Köer och Stackar Innehåll. Operationer. ADT Stack. Definition. Definition

Classes och Interfaces, Objects och References, Initialization

Klassdeklaration. Metoddeklaration. Parameteröverföring

Operativsystem ID1200/06 (ID2200/06 6hp) Tentamen :00-18:00

Länkade strukturer. (del 2)

Realtidssystem. - Semaforer, trådsynkronisering - EDA698 - Realtidssystem (Helsingborg) Elin A. Topp

Summering av fält 1.1. Synkronisering - Semaforen. Summering av fält 1.3. Summering av fält 1.2. Summering av fält 2.3 (INTE GK)

Operativsystem. Hierarkin för hårdvara läses nerifrån

Lågnivåprogrammering. Föreläsning 2 Lågnivåprogrammering. Binära tal. En enkel modell av datorns inre

Operativsystem ID2206 Tentamen TEN1 4.5 hp :00-18:00

Föreläsning 4 Innehåll. Abstrakta datatypen lista. Implementering av listor. Abstrakt datatypen lista. Abstrakt datatyp

Mål. Datorteknik. Repetition av avbrott. Innehåll. Mätning och styrning. Datorer för mätning och styrning. timer. Datorsystem A/D. Analog insignal D/A

Deluppgift 17 Processhantering: exec, sleep, exit, plist

F2: Motorola Arkitektur. Assembler vs. Maskinkod Exekvering av instruktioner i Instruktionsformat MOVE instruktionen

Concurrency Saker händer samtidigt. Process En instans av ett program

Programmera i C Varför programmera i C när det finns språk som Simula och Pascal??

Visual Basic, en snabbgenomgång

Vad händer när man kör ett program? Program och processer. Funktionsanrop. Avsluta programmet

Tentamen PC-teknik 5 p

Lite om felhantering och Exceptions Mer om variabler och parametrar Fält (eng array) och klassen ArrayList.

Tommy Färnqvist, IDA, Linköpings universitet

Tentamen Lösningar EDA698 Realtidssystem

Flera processer. Minneshantering. Trashing kan uppstå ändå. Ersätta globalt

F8: Undantagshantering

Föreläsning 2 Datastrukturer (DAT037)

Omgivningar. Omgivningar är viktiga eftersom de avgör vilka namn som är synliga och därmed dessas innebörd och de värden som är förknippade med dem.

Operativsystem. Informationsteknologi sommarkurs 5p, Agenda. Slideset 7. Exempel på operativsystem. Operativsystem

Tentamen ID1004 Objektorienterad programmering October 29, 2013

Föreläsning 2. Operativsystem och programmering

Deadlocks. detektera och undvik

Operativsystem (IS1350) :00-12:00

Elektroteknik MF1016 föreläsning 9 MF1017 föreläsning 7 Mikrodatorteknik

Operativsystem - input/output, skydd, virtualisering

Dagens föreläsning. Diverse Common Lisp. Konstanter, parametrar, globala variabler

Digitalitet. Kontinuerlig. Direkt proportionerlig mot källan. Ex. sprittermometer. Elektrisk signal som representerar ljud.

Föreläsning 5: Introduktion av pekare

Föreläsning 4. ADT Kö Kö JCF Kö implementerad med en cirkulär array Kö implementerad med en länkad lista

Datalogi, grundkurs 1. Lösningsförslag till tentamen

Abstrakta datatyper Laboration 2 GruDat, DD1344

Introduktion. Klasser. TDP004 Objektorienterad Programmering Fö 2 Objektorientering grunder

Imperativ programmering. Föreläsning 2

Standardfilerna Input och Output

Operative system. LRU-algoritm (2 p) Svar: 7 fel. c) Optimal algoritm (2 p) Svar: 6 fel

Tentamen den 18 mars svar Datorteknik, EIT070

Sätt att skriva ut binärträd

PARALLELL OCH SEKVENTIELL DATABEHANDLING. Innehåll

Standardfilerna Input och Output. Filer... Filer... vanlig utbyggnad för ökad dynamik. Filer... filtyper

En Von Neumann-arkitektur ( Von Neumann-principen i föreläsning 1) innebär:

Datorarkitekturer med operativsystem ERIK LARSSON

CE_O3. Nios II. Inför lab nios2time

Datastrukturer och algoritmer

Hjälpmedel: Inga hjälpmedel förutom penna, suddgummi och glatt humör.

Realtidsprogrammering Ordinarie tentamen

Föreläsning 2 Programmeringsteknik och C DD1316. Mikael Djurfeldt

TDIU01 - Programmering i C++, grundkurs

Föreläsning 4 Innehåll

Kompilering och exekvering. Föreläsning 1 Objektorienterad programmering DD1332. En kompilerbar och körbar java-kod. Kompilering och exekvering

Lösningar till tentamen i EIT070 Datorteknik

Classes och Interfaces, Objects och References Objekt-orienterad programmering och design (DIT952) Niklas Broberg, 2016

Språket Python - Del 1 Grundkurs i programmering med Python

Transkript:

39 3. 3.1. Preliminära frågeställningar En problemställning är att realisera primitiver (funktioner) som måste programmeras vid inträde i och vid utträde ur (godtyckliga) kritiska avsnitt. Processmallen är alltså : i. Kod för att begära att få gå in i ett kritiskt avsnitt (b). ii. Kod för det kritiska avsnittet (cs). iii. Kod för att lämna det kritiska avsnittet (l). 3.1.1. Idé 1. Låsvariabel Associera en (boolean) låsvariabel open till varje kritisk resurs eller kritiskt avsnitt och skriv. b: while not open do null; open := false; (eller <<L>> : if not open then goto L; open := false;) l: open := true; Problemet är att sekvensen b som manipulerar (testar och ändrar) open i sig är ett kritiskt avsnitt: Två eller fler processer kan, som följd av processbyten, testa open och finna att open = true (ledigt) för att först därefter negera open (markera upptagen). Processerna går då samtidigt felaktigt in i de kritiska avsnitten. 3.1.2. Idé 2. Turordning Ordna processerna (i och j), använd en tur-variabel turn och definiera för process i b: while turn = j do null; l: turn := j; Här utfaller ömsesidigt uteslutande tillfredställande, men dessa b och l tvingar fram en alternerande exekvering i, j, i, j, i,..., vilket definitivt inte är bra i de flesta tillämpningarna, tex hantering av en buffert, där i så fall bufferten urartar till att omfatta endast ett element. 3.1.3. Idé 3. Avbrottssystemet Försök åstadkomma atomicitet via avbrottsystemet. b: disable interrupt; l: enable interrupt; Detta ger oavbrytbarhet relativt processer på en processor. Men: a. Lösningen fungerar inte då flera processorer delar minne (flerprocessorsystem) eftersom avbrottssytemet är en processorberoende komponent. b. Dessutom måste vi tänka oss att de kritiska avsnitten kan vara (godtyckligt) långa. Sådana innebär (risk för) förlorade eller för sent omhändertagna avbrott. c. Slutligen skjuter lösningen in sig på fel källa: Avbrott är inte alls ovälkomna i ett kritiskt avsnitt bara vi vet att den avbrytande processen inte ändrar samma minnescell och att vi också kommer tillbaka ('liveness') till den avbrutna sekvensen. Tekniker som laborerar med fler avbrottsnivåer har samma begränsningar.

40 3.1.4. Petersons metod För specialfallet med endast två (i, j = 0.. 1) samverkande processer är denna metod korrekt. b: flag[i] := true; -- jag är intresserad, turn := j; -- men kollar om det är den andres tur while flag[j] and turn = j do null; l: flag[i] := false; där initialt flag[0] = flag[1] = false och värdet av turn är 0 eller 1. Observera 'busy wait'; se nedan. 3.1.5. Lamports algoritm Korrekta programvaruimplementeringar för det allmänna fallet finns. Lamports algoritm ('the bakery algorithm') är som följer. Processerna är 0, 1,..., n-1. b: choosing[i] := true; num[i) := max ( num[0],..., num[n-1] ) + 1; choosing[i] := false; for j := 0 to n-1 do while choosing[j] do null; while num[j] 0 and ( num[j], j ) < ( num[i], i ) do null; end for; l: num[i] := 0; där förkortningen (a,b) < (c,d) står för att a < c eller att a = c och b < d. 3.1.6. 'Test-and-set' instruktionen De flesta processorer, se tex exemplen nedan, har maskinvaruunderstöd (dvs instruktion(er)) för ömsesidigt uteslutande. Instruktionen test-and-set (tas) kan beskrivas enligt följande mall. atomic function tas (var t: boolean): boolean; begin tas := t; t := true end; -- atomic = odelbar Avsnitten b och l kan nu implementeras; initialt antas lock = false; b: while tas (lock) do null; -- lock antas = false (ledigt) initialt l: lock := false; Som maskininstruktion är tas oavbrytbar och löser problemet från "Idé 1". Den är också odelbar (atomär, atomic) i ett flerprocessorsystem så att den läser och skriver i en minnescell t odelbart processorer emellan. På maskinnivå implementeras det så att bussen mellan minne och processorer allokeras och låses till en processor till dess minnesadressinnehållet förändras, dvs ömsesidigt uteslutande på maskinvaru- nivå. Instruktionen och paret <b,l> löser problemet med ömsesidigt uteslutande men har (minst) tre nackdelar/begränsningar. - Det arbetar med sk 'busy wait' ('spin lock'), så att en process som i alla fall inte kan gå in i sitt kritiska avsnitt testar på tas och därigenom tar upp både onödig processortid och busskapacitet. - Svält ('starvation'), dvs det förhållande att det inte finns någon garanti för en enskild process att den någonsin tillåts komma in i sitt kritiska avsnitt, kan uppstå via två eller flera konspirerande processer.

41 - Om en process blir avbruten i sitt kritiska avsnitt, så finns ingen garanti att vi återvänder dit, varför det kritiska avsnittet blir blockerat för alla processer för all framtid. Då kan man tänka sig att kombinera tas med disable interrupt, men då återfås problemet enligt "Idé 3". Icke desto mindre finns tas eller någon därmed ekvivalent instruktion, tex exchange eller swap, i de flesta processorer. Tanken med detta är att den ska kunna användas för att bygga bättre primitiver, tex semaforer, eller i andra specialfall. Anmärkning 1. I princip skulle man kunna tänka sig att använda en vanlig aritmetisk/logisk operation som sätter en indikator på slutresultatet i ett cpu-register och testa på denna. De flesta processorkonstruktörer har emellertid av effektivitetshänsyn valt att göra sådana operationer icke-odelbara för flerprocessorsystem. Anmärkning 2. För att komma tillrätta med 'starvation' kan följande generaliserade operationer användas. Processindex (processnamn) är i och j. LOCK(i): waiting [i] := true; key := true; while waiting [i] and key do key := test_and_set(lock); waiting [i] := false; UNLK(i): j := i + 1 mod n; while i j and not waiting [j] do j := j + 1 mod n; if i = j then lock := false else waiting [j] := false; Exempel: M680x0 (x 1) har en odelbar minnesrefererande instruktion tas <eff address> som sätter 'condition code' efter innehållet i adresserad minnesbyte och sätter bit7 i samma ord lika med 1. Programmeringen blir sålunda; initialt antas lockvar = 0; lock: tas lockvar ; kontrollera om cs är ledigt bmi lock ; om inte, så vänta <kritiskt avsnitt> ; annars kör cs clr.b lockvar ; "öppna" låset Exempel: i80x86 (x 2) har en odelbar minnesrefererande instruktion xchg reg, mem, som byter ut innehållet i mem mot innehållet i reg och vice versa. Programmeringen blir sålunda. loop: mov al, 1 ; sätt reg al till 1 (låst) xchg al, lockvar ; lockvar = 1 <=> låst cmp al, 0 ; kontrollera om cs ledigt jne loop ; om inte, så försök igen <kritiskt avsnitt> ; annars kör cs mov lockvar, 0 ; "öppna" låset Instruktionen tas/motsvarande har sin styrka i att den kan användas för att implementera mer kraftfulla primitiver för ömsesidigt uteslutande och synkronisering; tex semaforer. Anmärkning 3. M680x0 och en del andra system har en instruktion cas ('compare and swap') med vars hjälp en annan teknik att hantera kritiska avsnitt erhålls; sk 'optimistic concurrency control'.

42 3.2. Semaforer Tanken är att via blockering (om detta skulle vara nödvändigt) undvika 'busy wait' och att vänteköer används för att komma till rätta med de andra två nackdelarna med test-and-set. 3.2.1. Specifikation En semafor kan ses som en struktur med två operationer s: [ c(s), q(s) ], där c(s) är ett heltal och där q(s) är en pekare till en lista av pcb-er. P(s) (holländska probehren eller passeren (?)) kallas ibland wait (s), down (s) eller stop (s). V(s) (verhogen, vrijmaken (?)) kallas ibland signal (s), up (s) eller go (s). och definieras som odelbara [... ] (tex via tas och/eller disable interrupt) enligt följande. P(s) [c(s) := c(s) - 1; if c(s) < 0 then place caller in q(s); dispatch first process on ready list end if.] V(s) [c(s) := c(s) + 1; if c(s) 0 then move one process from q(s) to RL; dispatch first on ready list end if.] Instruktionerna P och V kan ses som systemanrop som implementeras i kärnan av operativsystemet (eftersom de är så korta). I praktiken måste det också finnas en operation create_semaphore (<name>, <initial values>) som definierar ett semafornamn och sätter initialvärden för räknaren och pekaren. 3.2.2. Användning Ömsesidigt uteslutande. För att åstadkomma ömsesidigt uteslutande i en process px programmeras px:... ; P (s) ; kritiskt avsnitt; V (s);...; och analogt för andra processer med samma kritiska avsnitt, där s = [c(s), q(s)] = [1, null] initialt. Om process nummer två utför P och semaforvärdet blir negativt, så måste denna process vänta till dess process nummer ett ökar värdet (till noll) med V(s) efter sitt kritiska avsnitt. Observera att s i sig är en kritisk resurs (därav kravet på odelbarhet) och att om c(s)<0 så är -c(s) antalet blockerade processer. - En semafor som bara tillåts ha c(s) = 0, 1 kallas en binär semafor. - Allmänna semaforer kallas ibland räknande semaforer. - En semafor som används så att endast en process utför P på denna semafor brukar kallas en privat semafor (för processen i fråga). Synkronisering. Semaforer kan också användas för att synkronisera processer. Exempel: Antag att det finns ett parallellt system av fyra processer sådant att - p1 ska gå klar före p2 och - p2 och p3 ska gå klara före p4. Figur 3.1 visar precedensordningen; en strikt partiell ordning (irreflexiv, asymmetrisk, transitiv).

43 p1 Graf-form Relationen Motsvarande matrisform ------------------ 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0 0 p2 p3 som kallas precedens definieras av { <pi,pj> pi är klar innan pj startar }. I detta fall har vi att = { <p1,p2>, <p2,p4>, <p3,p4> } p4 Figur 3.1 Exempel på precendensordning Med initialvärden xij = 0 hos semaforerna kan detta system programmeras. parbegin p1: <kod för p1>; signal (x12); p2: wait (x12); <kod för p2>; signal (x24); p3: <kod för p3>; signal (x34); p4: wait (x24); wait (x34); <kod för p4>; parend; där parbegin... parend anger parallellexekvering av satser efter px:. En teknik som alltid fungerar är att associera en semafor till varje precedensförhållande och att avsluta en föregående process med signal till efterföljarna och att inleda varje efterföljande process med wait på väntevillkoren (de föregående processerna). Ibland kan antalet semaforer reduceras, tex om precedensordningen specificeras som ett lattice (systemet av processer brukar då kallas slutet). I ett slutet system av n processer {p 1,..., p n }, finns exakt en startprocess, säg, p 1 och exakt en slutprocess, säg, p n. En godtycklig precedensordning kan framställas med hjälp av följande konstruktiva procedur: 1. Associera en semafor s j till varje icke-initial process, dvs till p 2,..., p n. 2. Inled varje p j, j = 2,..., n, med en operation wait (s j ). 3. Avsluta varje p j, j = 1,..., n-1, med en signal (s k ) för varje omedelbar efterföljare p k till p j. 4. Initialvärden för s j ska sättas till - (d j -1), där d j är antalet omdelbara föregångare till p j. Baklås. Oförsiktigt användande av semaforoperationer (läs: wait) kan orsaka problem. Operationen wait kan ses som en resursreserveringsoperation. Låt oss anta att vi behöver följande kod. parbegin p1:... wait (x);.. ; wait (y); <mer kod för p1>;... signal (y);...; signal (x);... p2:... wait (y);...; wait (x); <mer kod för p2>;... signal (x);...; signal (y);... parend; för att reservera odelbara resurser svarande mot x respektive y men i olika ordning i de båda processerna. En olägenhet uppstår om p1 minskar x från 1 till 0, fortsätter men p2 kommer in och gör sammanlunda med y. Därefter blir p1 blockerad på y och p2 på x.

44 Problemet är nu att ingen av dem kan gå vidare och utföra kommande signal-operationer. Båda processerna är blockerade för all framtid: Baklås ('dead lock', 'deadly embrace') har uppstått. En nackdel med semaforer är att sådana situationer är svåra att överblicka i stora system. Semaforerna kanske kan sägas vara lite för primitiva för att vara bekväma (jämför go to i programspråk). Allmänt gäller följande nödvändiga och tillräckliga Baklåsvillkor. 1. Resurserna är odelbara (= föremål för ömsesidigt uteslutande). 2. Resurserna är 'non-preemptible' (= kan frigöras endast av de som har dem allokerade). 3. En 'cirkulär' (oupplöslig) resursväntan har uppstått. Villkor 1 är följden av att P behövs och villkor 2 är för handen då endast den process som gjort P(sem) utför motsvarande V(sem). Dessa två villkor går generellt sett inte att komma ifrån: Endera därför att resultatet skulle bli otillfredsställande eller för att utnyttjandet skulle bli alltför ineffektivt. Ett förslag till lösning på baklåsproblemet är att, genom att alltid kräva av alla processer att de begär resurserna i en given ordning, tillse att det tredje villkoret aldrig uppfylls. 3.2.3. Exempel: Begränsad buffert Vi återvänder till det inledande exemplet i avsnittet 2.3.1. Problemet är tvåfalt: Dels finns ett synkroniseringsproblem: En producent måste vänta till dess bufferten är icke-fylld och en konsument måste vänta till dess bufferten är icke-tom. Vidare såg vi att vardera process innehåller ett kritiskt avsnitt med avseende på delade data in, ut och e. Variablerna in och ut delas om det finns flera producenter eller flera konsumenter. Med hjälp av semaforer löser vi problemet så: Producenter Konsumenter Obs. pluralis! loop loop...... <produce>... wait (mayproduce) wait (mayconsume) wait (mutex) wait (mutex) <deposit> (e, in, ändras) <remove> (e, ut ändras) Kritiska avsnitt signal (mutex) signal (mutex) signal (mayconsume) signal (mayproduce)... <consume>...... end loop end loop Semaforen mayproduce = buffertlängden initialt så att producenterna kan placera in precis så många element innan konsumenterna behöver vakna. Semaforen mayconsume = 0 initialt men uppräknas (av producenterna) så att konsumenterna kan plocka ut data när sådana finns. Semaforen mutex för ömsesidigt uteslutande ska (som vanlig) ha initialvärdet 1. Följande kommentarer kan göras. -- Om det bara finns en producent och en konsument behövs inte mutex. Då blir inte in och ut kritiska resurser. Vidare behövs inte e alls eftersom mayconsume och mayproduce klarar synkroniseringarna varför inga kritska avsnitt finns och ingen semafor mutex behövs. -- Om vi använder en annan struktur, tex en länkad lista, i stället för en array som representation för bufferten kommer processerna att manipulera delade variabler (pekare): En programskiss blir:

45 För producenten För konsumenten Bufferten är new (p); c := first; type buffer = record p.elem := nextp; first := first.next elem: <någonting>; p.next := first; nextc := c.elem; next : access buffer first := p; end; där p, c, first: access buffer; och nextp, nextc : <någonting>; dvs "data". Då återinförs behovet av mutex även om det bara finns en konsument och en producent. -- Om producenterna och konsumenterna sinsemellan inte har delade resurser, kan man tänka sig att styra de kritiska avsnitten producenterna emellan och konsumenterna emellan med olika semaforer mutexprod och mutexcons för att möjliggöra ökad parallellism. -- Om wait-operationerna i producenterna reverseras fås en "baklåslösning", som måste förkastas. Ty om producenterna blir blockerade (för att bufferten är fylld) i ett kritiskt avsnitt inramat med wait(mutex), så kan inte heller konsumenterna fortsätta för att tömma bufferten. Varning för synkronisering i ett kritiskt avsnitt! Öppna ett kritiskt avsnitt innan processen blockeras! 3.2.4. Exempel: Fem filosofer (resursallokering) Fem processer (filosofer) exekverar vardera alternerande icke-kritiska (de tänker) respektive kritiska (de äter) avsnitt. Ätavsnitten är kritiska därför att det runt (det runda) bordet mellan filosoferna bara finns fem gafflar (resurser), men att varje process behöver två resursenheter (gafflar) för att äta i de kritiska avsnitten. Problemet är att konstruera den kod för varje process som behövs vid övergången mellan tänkande och ätande. En första idé, som måste förkastas är att till varje filosof associera två semaforer; en till vänstergaffeln och en till högerdito för att därefter skriva process F(i): -- i = 0, 1, 2, 3, 4 loop <think>; P(left-hand-fork); P(right-hand-fork); <eat>; V(right-hand-fork); V(left-hand-fork) end loop; en uppenbar baklåslösning. (rhf för en filosof = lhf för filosofen till höger cirkeln runt.) (En "fusklösning" är att definiera operationer SP och SV (S = simultaneous) som atomärt och samtidigt ändrar och testar flera (här: två) semaforer. En "asymmetrisk" lösning är att byta ordningen P(lhf); P(rhf) till P(rhf; P(lhf) för den sista filosofen.) För att lösa problemet behövs ytterligare statusvariabler: Låt c(i) = 0 betyda att F(i) tänker och låt c(i) = 2 stå för att F(i) äter. Eftersom det just är transitionen mellan tänkande och ätande som är problematisk inför vi konventionen c(i) = 1 <--> F(i) är hungrig. Därur följer slutsatsen: Om: c((k-1) mod 5) 2 and c(k) = 1 and c((k+1) mod 5) 2 def cond (k), så: sätt c(k) := 2 och signalera att F(k) kan äta. Definiera därför följande (atomära! mutex) hjälpprocedur:

46 procedure test (k); begin P(mutex); if cond (k) then c(k) := 2; V( s[k] ); end if; V(mutex); end; Proceduren arbetar på en array s[k] av semaforer. På varje semafor (array-element) utförs wait bara av en process; en sådan semafor kallas privat. Koden för filosoferna blir nu som följer. process F(i) loop <think>; c(i) := 1; test (i); P( s[i] ); <eat>; c(i) := 0; test (i-1); test (i + 1); end loop; -- vänta vid P om inte båda grannarna har ätit klart -- meddela grannarna att du nu ätit klart och gör dessa redo Huvudprogrammet består i att initiera semaforerna och processerna för parallell exekvering. Lösningen har fortfarande en nackdel. Vilken? och hur löser man detta problem? 3.2.5. Exempel: Sovande frisören ('clients and server') En frisersalong består av ett väntrum med n stolar, frisörrummet med en stol och frisörens vilrum med en säng. Om inga kunder finns går frisören och sover. Om en kund anländer och alla n stolar är upptagna, så lämnar kunden salongen. Om frisören är upptagen sätter sig annars kunden i en väntestol. Om frisören sover, så väcker kunden honom. Uppgiften är att skriva ett program som simulerar frisören och hans kunder. Vi inför följande (globala) variabler. - barber: semaphore = 0; -- frisören sover vid denna semafor - busy: semaphore = 0; -- kunder måste vänta om frisören är upptagen - entry: semaphore = 1; -- entré till och 'exit' från salongen är kritiska avsnitt - count: integer = 0; -- antalet anlända icke omhändertagna kunder Koden för frisören blir: process sleeping-barber; loop wait (barber); -- här sover frisören (kanske) <shave or cut>; end loop; Koden för kunderna blir: process customer(i); begin -- Kritiskt avsnitt för att göra entré wait (entry); if count = n then <exit>; count := count + 1; if count > 1 then signal (entry); wait (busy) else signal (entry); -- Här möts frisör och kund signal (barber); <shave or cut>; - Kritiskt avsnitt för att lämna salongen wait (entry); count := count - 1; if count > 0 then signal (busy); signal (entry); end;

47 3.2.6. Exempel: Enkelriktad bro (halv-duplex-kanal) En norr- och en södergående ström av bilar närmar sig en enkelriktad bro. Eftersom endast bilar i en riktning kan befinna sig på bron samtidigt måste trafikströmmarna samordnas. Uppgiften är att med hjälp av semaforer skriva ett program som simulerar/beskriver detta scenario. En tänkbar lösning är som följer. program single_lane_bridge; var southern, northern: integer; bridge, south, north: semaphore; bridge := 1; south := 0; north := 0; southern := 0; northern := 0; -- jfr create_semaphore (<name>, <init val>) process southbound; wait (bridge); if southern = 0 then inc (nothern); signal (south); end if; signal (bridge); wait (south); -- vänta här om bron är upptagen <pass the ocean>; wait (bridge); dec (nothern); if nothern = 0 then signal (north); signal (bridge); end {southbound}; process northbound; wait (bridge); if northern = 0 then inc (southern); signal (north); end if; signal (bridge); wait (north); <pass the ocean>; -- basic #36 wait (bridge); dec (southern); if southern = 0 then signal (south); signal (bridge); end {southbound}; schedule (southbound); schedule (northbound); end {single_lane_bridge}. 3.2.7. Semaforimplementering Beskrivningen som ges här är i "högnivå-notation" (Ada-liknande). Verkliga implementeringar görs kanske i assembler eller i mikroprogramform. Datastrukturer I det följande lämnas bortsett från länkfältet innehållet i processbeskrivningarna ospecificerat: Poster som bör ingå är plats för alla registervärden för den underliggande arkitekturen inklusive programräknaren, processnamn, prioritet, tidsuppgifter för schemaläggning, beskrivningar av resurser allokerade till processen för bla in- och utmatning, beskrivning av processens domän, dvs pekare till tabeller för hantering av minnet och resterande kontext.

48 - Länkfältet specificeras som ett array-index i en mängd av PCB-er som ligger lagrade i en vektor. - Färdiglistan (RL) består av en pekare till huvudet och en pekare till svansen.fcfsalgoritmen används här; en annan metod skulle kanske vara betjänt av en dubbellänkad lista. Vidare ingår en lås-variabel eftersom RL är en kritisk resurs i flerprocessorsystem. - Varje semafor representeras av en lista SL(j), j = 1,..., m som förutom huvud och svans innehåller räknarvärdet count som kan anta godtyckliga heltalsvärden. Semaforerna i sig är också kritiska resurser. Ömsesidigt uteslutande med avseende på operationerna P (wait) och V (signal) ges av LOCK och UNLK. I det följande är alla initieringar utelämnade. Processbeskrivningar, semaforer register etc måste ges korrekta startvärden. -- Datatyper type processindex is range 0..n; semindex is range 1..m; PCB is record stateword:...; link: processsindex; end record; -- antalet processer. -- antalet semaforer. -- process control block. -- beror på arkitekturen. -- länkfält. -- Variabler RL: record -- huvudet och svansen på head, tail: processindex; -- färdiglistan. lock: boolean; -- lås behövs i end record; -- flerprocessorsystem. PL: array (processindex) of PCB; -- pool av PCB-er SL: array (semindex) of -- semaforlistorna. record -- en per semafor. count: integer; -- head & tail som ovan, head, tail: processindex; -- tail av kodningstekniska skäl. lock: boolean; -- lock; se ovan end record; Följande figur 3.2 visar samma sak i bildform. PL är en vektor av pcb-er PL(0) PL(1) PL(n) l i n k Namn Reg-area Kontext etc head tail lock RL Starten av redolistan head tail count lock SL(1) SL(m) En vektor av semaforer Start på väntelistor; en per semafor Figur 3.2. Semaforer och listor

49 Dolda operationer Dessa operationer är inte avsedda att användas av processer. De är interna rutiner i kärnan. i. Paret LOCK och UNLK är det underliggande primitivet för att på lägsta nivå åstadkomma ömsesidigt uteslutande. Som de är implementerade är de oavbrytbara eftersom de anges som (korta) maskininstruktioner som inte testar någon avbrottssignal från 'timer' eller yttre enheter. De är också odelbara i ett flerprocessorsystem på så vis att en och endast en processor kan exekvera dem åt gången. För UNLK är detta "trivialt", men observera att LOCK innehåller både en läs- och en skrivcykel i minnet men på samma plats Mem (x). Mem är det delade primärminnet och x är en adress. Protokollet på maskinvarunivå är alltså att bussen låses till en processor till dess innehållet i minnesadressregistret ändras. Instruktionen LOCK har tre nackdelar/begränsningar. Det kritiska avsnittet som ramas in kan inte tillåtas vara avbrytbart eftersom ett sådant inte garanterat skulle återuppptas. LOCK-instruktionen ger en sk 'busy waiting' lösning som belägger såväl cpu-er som minnescykler i onödan. Eftersom LOCK "saknar minne" (är utan kö) kan 'starvation' uppträda: Två (eller fler) processer kan genom "konspiratoriskt beteende" utestänga en tredje process från att någonsin tillåtas exekvera i sitt kritiska avsnitt. Dessa nackdelar är ofrånkomliga: De måste dyka upp någonstans. De är dock acceptabla eftersom paret LOCK/UNLK endast används för att implementera de (mycket) korta kritiska avsnitt som ingår i semaforoperationerna. De används ju inte för allmänna kritiska avsnitt. ii. Paret SAVESW - LOADSW utför processbytet ('process switch') som ingår i waitoperationen då semaforens värde blir negativt. LOADSW kan sägas vara en 'dispatcher', som kör ingång den process som ligger först i färdiglistan RL. Observera att det är nödvändigt att låsa RL till en process då vi har en konfiguration med flera processorer. iii. Avsnittet READY (i) gör processen i redo, dvs lägger PCB(i) sist i färdiglistan RL. iv. Beteckningen b används för ett ('scratch pad') register i processorn och index self innehåller numret på den exekverande processen. De är synliga bara på mikroinstruktionsnivån. *** Primitiva avsnitt för ömsesidigt uteslutande *** LOCK x: b := Mem (x); if b = 1 then 'retry LOCK'; Mem (x) := 1; -- Odelbar maskininstruktion (likartad tas) -- b är ett cpu-register. -- x = 1 <=> "busy". -- lås! UNLK x: -- Odelbar maskininstruktion Mem (x) := 0; -- x = 0 <=> ledig! *** Mikrokod för processbyte *** SAVESW (L): -- Odelbar rutin för att spara processstatus PL (self).stateword := "regs" -- adressen L går till pc-fältet. LOADSW: -- Dispatcher (obs för LOCK; UNLK) LOCK RL.lock; -- lås färdiglistan. self := RL.head; -- plocka fram nytt proccess-id RL.head := PL( RL.head ).link; -- länka ur exekverande process; UNLK. RL.lock; --??? redan här??? PL( self ).link := 0; -- exekverande process tas ur RL och kör self. "registers" := PL( self ).stateword;

50 *** Mini-'scheduler' *** READY (i): -- Schemalägger process i; in sist i RL PL(i).link := 0; -- sist i listan <=> link = 0. LOCK RL.lock; -- för flerprocessorsystem. if RL.head = 0 then RL.head := i -- är lista tom? else PL( RL.tail ).link := i; end if; -- om inte; ta nästa (följ länken) RL.tail := i; UNLK RL.lock; Semaforoperationerna Operationen wait (eller P) minskar semaforvärdet med 1. Om resultatet blir negativt så blockeras anropande process och en annan (den process som ligger först i färdiglistan) startas. Observera var (plats L) den avbrutna processen senare ska återstartas. Operationen signal (V) ökar semaforräknaren med 1. Om den fortfarande blir icke-positiv så är det ett tecken på att det finns process(er) som är blockerade på denna semafor. Den äldsta av dessa görs redo. (Observera att olika författare definierar semaforer olika. Somliga tillåter tex endast binära semaforer (count = 0 eller 1); andra talar om icke-blockerande wait-operationer ('busy wait', 'spin lock'); några minskar/ökar variabeln count efter test av värdet etc. Olika definitioner passar som alltid bäst för det ena eller det andra ändamålet. Viktigt är emellertid att P och V inte kan vara korrekta eller felaktiga var för sig utan ska alltid definieras i par. Observera att paret LOCK/UNLOCK är nödvändigt i flerprocessorsystem. Det behövs inte och bör alltså strykas i ett en-processorsystem.) procedure wait ( j:semindex ) is begin disable; LOCK SL(j).lock; SL(j).count := SL(j).count - 1; if SL(j).count < 0 then if SL(j).head = 0 then SL(j).head := self; else PL( SL(j).tail ).link := self; end if; SL(j).tail := self; SAVESW (L); UNLK SL(j).lock; LOADSW; else UNLK SL(j).lock; end if; L: enable; end wait; procedure signal ( j:semindex ) is i: processindex; begin disable; LOCK SL(j).lock; SL(j).count := SL(j).count + 1; if SL(j).count 0 then i := SL(j).head; SL(j).head := PL( SL(j).head ).link; UNLK SL(j).lock; READY (i); else UNLK SL(j).lock; end if; enable; end signal; -- P operationen -- LOCK endast i fler-proc-syst -- går negativ => blockera. -- lista tom specialbehandlas -- ska börja vid L nästa gång. -- kör igång första i färdiglistan -- V operationen -- LOCK kräver disable -- icke-positiv sem => gör redo. -- först på färdiglistan. -- länka ur semaforlistan. -- och gör redo.

51 Generaliseringar: PP, VV, SP, SV och BP, BV I vissa tillämpningar kan följande operationer vara användbara. De ingår också i vissa operativsystem. i. Villkorade semaforer. De kan vara användbara tex för readers/writers-problemet (se avsnitt 9.4.1) och andra problem där förutom semaforer även en tillståndsvariabel (här c) behövs för selektiv blockering/uppväckning av processer. procedure pp( m, s: semaphore; c:integer); begin wait(m); c := c + 1; if c = 1 then wait(s); signal(m) end pp; procedure vv( m, s: semaphore; c:integer); begin wait(m); c := c - 1; if c = 0 then signal(s); signal(m) end vv; Observera att att operationen pp måste användas med försiktighet ('understatement') eftersom det finns en wait-operation i ett kritiskt avsnitt. ii. Multipla villkor. Följande operationer kan användas om man behöver reservera och frisläppa fler resurser/signaler odelbart (sp och sv; s = 'simultaneous'.). procedure sp (s1, s2,..., sn); begin <arrangera odelbarhet> for i in [1..n] do SL(i).count := SL(i).count - 1; if SL(1).count < 0 and... and SL(n).count < 0 then suspend caller and dispatch a ready process; end; och motsvarande sv för signalering. Dessa kallas ibland 'and-synchronizing'. iii. Begränsade semaforer. Sådana semaforer bs kan anta positiva värden upp till, säg, bsmax. Semaforoperationerna (i en variant) kan skrivas. BP(bs): wait until bs > 0 then decrement bs by 1. BV(bs): wait until bs < bsmax then increment bs by 1. I avsnitt 3.4 diskuteras en generalisering av detta som kallas brevlådor. 3.3. 'Event counts' och 'sequencers' En variant på semaforer är sk 'event counts' och sequencers'. Dessa typer är anpassade för att obesörja interprocesskommunikation i främst distribuerade system. i. En 'sequencer' S är en icke-negativ heltalsvariabel, initierad till 0, som används för att ordna händelser; att jämföras med en kölappsapparat som finns i diverse butiker. På S kan uföras operationen v := ticket (S), som returnerar antalet (v) hitills uförda operationer ticket på denna S. En sekvens av denna operation ger en ökande, sammanhängande följd av heltal. ii. En 'event count' E, är med ovanstående liknelse ett objekt som anger numret på den senaste kölappen. På E kan tre operationer utföras: - await (E, v) En sekvens v := ticket (S); await (E, v); innebär att anropande process (en konsument) blockeras till dess E v. En väntekö per 'event count'. Sekvensen skrivs oftast await(e,ticket (S)).

52 - advance (E) Öka E med 1 atomärt och om det finns processer som väntar på att värdet ska bli det erhållna så gör dessa processer redo. - read (E) returnerar aktuellt värde på E. Exempel: För att programmera ömsesidigt uteslutande med avsende på ett kritiskt avsnitt skrivs: await (E, ticket (S)); <kritiskt avsnitt>; advance (E); Exempel: Producent/konsument problemet kan lösas med följande variabler och kod. Globala variabler pticket, cticket: sequencer; in, out: evencount; buffer: array [0..N-1] of elem; Producent i Konsument j var t : integer; -- lokal per process var u : integer; -- lokal per process loop loop produce_item (m); --- t := ticket (pticket); u := ticket (cticket); await (in, t); await (out, u); await (out, t - N + 1); await (in, u + 1); buffer [t mod N] := m; m := buffer [u mod N]; advance (in); advance (out); --- consume_item (m); end loop; end loop; Det faller utanför ramen för denna kurs att diskutera implementering av dessa primitiver i ett distribuerat system, dvs ett system utan delat minne och utan global klocka. En realisering av funktionerna i ett en-processorsystem där processerna delar minne och tid är mycket snarlik semaforimplementeringen. 3.4. Brevlådor Ett alternativ till P och V är operationerna send och receive på brevlådor ('mail boxes'). P och V utgår ifrån att processer samarbetar genom att använda delat (logiskt) minne och att samtidig icke önskvärd åtkomst förhindras via (skickligt) programmerade P och V operationer i processerna. Ett annat synsätt är att låta processer utbyta meddelanden/data via brevlådor. Sådana finns i verkliga system i många varianter. 3.4.1. Specifikation En brevlåda skapas via ett systemanrop create-mbox (name, size). Kommunicerande processer kan därefter utbyta data via name. Parametern size sätter en begränsning på hur många meddelanden brevlådan kan rymma. Brevlådor kan också ha obegränsad kapacitet. Operationerna är atomära. - Operationen send (name, mess) deponerar ett meddelande (mess) i brevlådan (name) om det går. Om brevlådan redan är fylld så blockeras avsändande process (på en väntekö associerad med denna brevlåda). Processen väcks när brevlådan töms via en receive-operation. Om brevlådan har obegränsad kapacitet så blockeras aldrig sändande process.

53 - Operationen receive (name, mess) hämtar ett meddelande till den anropande processen (konsumenten) via argumentet mess om det finns något. Om brevlådan var tom så blockeras mottagaren till dess en sändande process placerar ett meddelande i brevlådan. En eventuellt väntande, sändande process väcks då brevlådan töms. 3.4.2. Implementering Brevlådan representeras som en datastruktur som hanteras av kärnan i operativsystemet. Den innehåller size stycken element av typen - <mess addr (i), mess len (i), attributes (i)>, i = 1,..., size, så att meddelanden av olika längd och typ kan hanteras lätt och ett element av typ - <head, tail>, som är pekare till början respektive slutet av väntande processers pcb-er. Visserligen finns det två väntevillkor (tom/fylld brevlåda), med dessa är ju varandra uteslutande så att ett par <head, tail> räcker. Om man så önskar kan brevlådorna i sig också länkas i en lista för att underlätta vid frekvent skapande och borttagande av brevlådor. Brevlådan måste också ha en variabel - <lock_name> så att implementeringen av operationerna send och receive kan göras odelbara. Precis som vid semaforer kan tas-instruktionen eller avbrottsystemet användas för detta ändamål. Om brevlådan har obegränsad kapacitet låter vi i stället ett element mess-queue-addr ersätta paren <mess addr, mess len> och vara startpunkten till en länkad lista av meddelanden. Brevlådeoperationerna send och receive finns precis som P och V som rutiner i kärnan och kan implementeras på ett likartat sätt. Figur 3.3 visar brevlådestruktur efter ovanstående specifikation. M(1) M(n) a s tail head l o c k pcb En väntelista (en kritisk resurs ==> lock) s Ett meddelande Till (evt) blockerade processers pcb-er Observera att om det finns väntande processer så är M endera fylld (som här) eller tom. Figur 3.3 En brevlåda, två meddelanden och tre processer

54 3.4.3. Fler operationer Ibland är det önskvärt att komma ifrån den inbyggda blockeringen i dessa operationer. En del operativsystem har därför icke-blockerande operationer; conditional-send och conditional-receive. De har då en extra parameter (typ boolean) som indikerar om deponerande respektive uthämtande gick att genomföra eller ej. Processer kan med conditional receive implementera 'busy wait' hämtning från brevlådor, eller kontrollera om meddelande finns i (minst) en av flera brevlådor utan att för den skull behöva bli blockerad för att inget fanns att hämta från tidigare avläst brevlåda. 3.4.4. Användning Precis som semaforer duger brevlådor både för processynkronisering och för ömsesidigt uteslutande. Meddelandenas innehåll blir då irrelevant; meddelandena behöver bara räknas och vi har nästan återfått (räknande) semaforer, men med ett maximumvärde size för brevlådor med begränsad kapacitet. Ömsesidigt uteslutande. process(i) :... receive(me, dummy); <critical section>; send (me, dummy);..., där me är en brevlåda med ett meddelande initialt och dummy är ett meddelande som inte används i övrigt. Synkronisering. process1:...; L1:; send (me, dummy);...; process2:...; receive (me, dummy); L2:,...; garanterar att process1 har hunnit förbi L1 innan process2 kan börja vid L2. Begränsad buffert. process-producers: process-consumers: loop... ; <assemble mess>; send (mbox, mess);...; end loop; loop...; receive (mbox, mess); <use mess>;...; end loop; med size(mbox) = buffertlängden. 3.4.5 Kommentarer i. Processer kommunicerar i denna modell indirekt med varandra via brevlådor. En brevlåda används av en eller fler producenter och/eller en eller fler konsumerande processer enkelriktat eller dubbelriktat. Ansvaret för korrekt utnyttjande åvilar processerna. Sk "protokoll" som innehåller tex 'acknowledgements' kan behövas om man befarar att meddelanden kan gå förlorande. ii. Operationerna send och receive kan också definieras för direkt kommunikation: send (toprocess, mess) respektive receive (from-process, mess). Detta kan uppfattas som en brevlåda med kapaitet 0. Operationerna blockerar processerna till dess både sändare och mottagare anländer till respektive kommunikationspunkt. När de möts, så utbyter de data. Detta kallas (ibland) ett 'rendez-vous'. iii. Det finns operativsystem som hanterar in/utmatning som brevlådeoperationer. In/utkanalerna är då brevlådor med fixa namn och in/ut-matning kan göras med receive/send. iv. Brevlådeliknande konstruktioner är vanliga i operativsystem för realtidstillämpningar. Brevlådor kan också användas som konstruktionsdelar för abstraktare uttrycksmedel tex för dem som återfinns i Ada.

55 v. System som bygger på semaforer och utnyttjande av gemensamma data säges tillhöra klassen 'shared memory' och system som bygger på brevlådor (eller liknande) kallas 'message based'. Observera att detta är abstraktioner. En relisering kan bygga på delat primärminne eller inte. I en flerprocessorarkitektur kan delar av det totala minnet vara delat och övrigt minne vara lokalt per processor. 'Shared memory' och 'message passing' som abstrakt teknik kan användas oberoende av den fysiska organisationen. Varje programmerat system av processer som konsekvent utnyttjar delat minne och semaforer/monitorer kan (algoritmiskt) översättas till ett system av processer som samverkar via brevlådor och omvänt. Teknikvalet blir därför en smakfråga. Många operativsystem innehåller därför (motsvarigheter till) både semaforer och brevlådor; ibland med extra parametrar. vi. Eftersom brevlådekommunikation påminner om in/ut-matning och eftersom "i/o är svårt" vill många idag, speciellt i distribuerade system, använda konstruktionselement för kommunikation som liknar vanliga funktionsanrops parameterlistor. Ett sk 'remote procedure call' måste dock på någon nivå realiseras som in/ut-matning över bussar eller andra kommunikationsmedier. 3.4.6. En operativsystemkärna Figuren 3.4 visar datastrukturer för brevlådor och processer, hur dessa länkas ihop och anges sex anrop till en operativsystemkärna (för realtidstillämpningar). Kärnan är avsedd för ett enprocessorsystem och exekverar med deaktiverat avbrottsystem för att bevara konsistens av interna data. i. Systemanropen (specifikationen) är: - Skapa och ta bort processer - Skapa brevlådor - send, receive (för meddelanden) och delay (process) ii. Listorna (för implementeringen) består av: - En lista över alla processer - Redolistan - Tidslistan (över fördröjda processer) - En väntelista per brevlåda - En lista över alla brevlådor - En lista över meddelanden per brevlåda iii. Datastrukturerna är: Processbeskrivningarna Delay link: Kedjar ihop processer i den tidsordning de ska göras redo (kallas tidslista) Thread: Allmänt länkfält; blockerat tillstånd, redo-tillstånd. Delay time: Ger ordningen i tidslistan. Priority: Ger ordning i redolistan. Status: (ready, suspended, delayed, dormant) Brevlådorna Message head: Början på kön av medd. som finns i en mbox med obegr. kapacitet Message tail: Slutet för samma lista. Process head: Början av kön av väntande processer; mottagande pga tom brevlåda. Process tail: Slutet på samma lista. Mbox link: Alla brevlådor länkas. Processes: Alla pcb-er är länkade i --------------------------------------------------- en lista efter den ordning de är skapade i. Meddelanden märks med längd och typ och länkas i en lista per mbox Name: Processernas namn

56 (Unspecified: Pekare till processens stack och ingående procedurers 'activation stacks', mm) iv. Tillståndsövergångarna för processer visas också i figuren. Tillståndet 'delayed' används för processer som är tidsfördröjda efter ett delay-anrop och därför ingår i tidslistan. v. För att upprätthålla reltidsklockan används en klockprocess. Denna väcks upp vid varje klockavbrott (sker typiskt var 10:e millisekund) och räknar upp en värdena i en datastruktur som anger aktuellt klockslag; <år, månad, dag, timme, minut, sekund, 10-dels sekund, 100- dels sekund>. Vidare görs de processer redo vars fördröjningstid har infallit, dvs flyttas från tidslistan till redolistan. Fördröjningstiderna i pcb-erna brukar anges relativt tiden för den första processen i tidslistan. På så sätt behöver endast denna pcb's 'delay time' ändras vid klockavbrott. 3.4.7. Varianter Avslutningsvis kan man säga att följande konstruktionsalternativ kan tillämpas för meddelandebaserade system. - Datautbyte: Direkt mellan processer eller indirekt via brevlådor. I stället för att skicka ett meddelande till en brevlåda kan en process skicka ett meddelande direkt till en annan process; send (mess, process-name). Operationen receive fungerar analogt. - Meddelanderiktning: Enkelriktat eller dubbelriktat. - Brevlådekapacitet: 0, begränsad eller obegränsad. - Operationer: Blockerande eller icke-blockerande. - Överföringsprincip: Värde- eller referensanrop, dvs kopiering respektive via pekare. - Meddelandetyp: Fix eller variabel. - Felhantering: Via givna primitiver eller via processerna själva (map förlorade, förändrade eller duplicerade meddelanden). Meddelandeutbyte är centralt i distribuerade system.

57 pcb innehåll processes delay link thread delay time priority status name unspec. 7 4 48 47 del ready susp ready susp dorm del pr1 pr2 pr3 pr4 pr5 pr6 pr7 head ready Processbeskrivningar, brevlådor, meddelanden och listor Sju processer nil head delay head all processes INTE ALLA FÄLT ÄR UTRITADE! Två meddelanden på brevlåda m3 head mboxes mess head mess tail process h. process tail m1 (name) mbox link m2 m3 nil 2 6 B L 4 7 0 1 1 8 n Tre brevlådor Följande listor ingår: 1. Alla processer, 2. alla brevlådor, 3. meddelanden, 4. vänte-, 5. redo- och 6. tidslista. längdindikator (evt typvärde) Ovanstående visar: 7 existerande processer, pr2 och pr4 är redo. 3 brevlådor; på m2 är pr3 och pr5 blockerade. Brevåda m3 innehåller två meddelanden. Process pr6 är inaktiv (sovande, 'dormant') och pr7 och pr1 väntar i tidsllistan 4 respektive ytterligare 7 tidsenheter (tex klocktick eller sekunder). Systemanrop, händelser och tillståndsövergångar Create proceess/mbox Send, Receive Delete process PASSIVATE Exekverande Running Receive SUSPEND Delay Sovande Dormant DISPATCH Highest priority Send, Interrupt PREEMPT Blockerade Waiting Uppehållen (fördröjd) Delayed alla övergångar via OS Create process ACTIVATE Redo Ready Message arrival SCHEDULE Time out Observera att det finns ett väntetillstånd per vänteorsak, brevlåda. Eftersom meddelanden länkas blir brevlådornas kapacitet oändlig. Detta innebär att en Send aldrig blockerar anropande process. Figur 3.4 En operativsystemkärna baserad på brevlådor

58 3.5. Interprocesskommunikation i OS/2 OS/2 är ett 'single user multitasking system' för bla Intels processorer 80286, 80386, 80486 och Pentium. Varje process finns i ett eget domän ('virtual address space', se kapitel 5) och kan innehålla en eller flera 'threads'. OS/2 består av kärnan, drivrutiner för i/o och filhantering, dynamiskt länkade bibliotek, OS/2 API och Shell. I detta system består kärnan av funktioner för 'multitasking'; 'dispatcher', 'scheduler' och ipcmekanismer, avbrotts- och 'trap'hantering och för minneshantering. Processer och 'threads' kan skapas och tas bort via systemanrop. Redolistan ordnas efter prioriteter och inom varje prioritetsnivå är schemaläggningen 'preemptive round robin' (se kapitel 4). En processbeskrivning kallas 'per task data area' (PTDA) och varje 'thread' har en egen descriptor (TCB). Dessa deskriptorer innehåller följande information. PTDA Parent PTDA link First child PTDA link Sibling PTDA link TCB chain head Thread count Current thread pointer Priority Process adress space pointer Module table pointer File system information Current drive Current directory Open file table Critical section count Exception vectors Signal vectors Semaphores Code page information TCB TCB chain link Forced action vector User stack information TSD link; thread swappable data i/o information NPX information Thread state; running, blocked, ready Priority Scheduler queue links Processor usage count Exception information TSD portion TCB link Kernel stack IPC-mekanismerna i detta system beskrivs ibland som hierarkiskt ordnade. - De mest grundläggande mekanismerna avser paradigmen 'shared memeory' och innehåller semaforer, signaler och 'exceptions'. - Dessa utnyttjas av mekanismer för 'message passing'; 'queues' och 'pipes' - Överst i hierakin finns API- ('application program interface') funktioner för de andra. 3.5.1. Semaforer Semaforer kan vara delade eller privata. Delning avser att semaforerna kan användas av flera processer, medan attributet privat avser att de endast används inom en process, men av flera 'threads'. Båda typerna av semaforer delas in i klasser (T = m, e, w): - 'mutual exclusion semaphores' (m) - 'event semaphores' (e) - muxwait semaphores' (w) Semaforer är väsentligen av typen binära. Seamforer nås via följande API-anrop (prefix Dos och suffix Sem är utelämnade i tabellen och - är insatt för bättre läsbarhets skull):

59 API namn Create-T Open-T Close-T Query-T Request-m Release-m Post-e Reset-e Wait-e Add-w Delete-w Wait-w Innebörd Skapa en semafor av typ T; ger ett 'handle' för användning För att göra delade semaforer tillgängliga för en ny process Det omvända Efterfåga status Binär semaforoperation P Binär semaforoperation V Dessa tre opertioner används för att koordinera threads' Lägg en semafor till en sk muxwait lista Ta bort Vänteoperation 3.5.2. 'Message passing' i. Typen 'queue' kan ses som en (FIFO, eller LIFO, eller prioritetsordnad) brevlåda där fler processer kan deponera meddelande men bara en process (ägaren) kan läsa. Ägaren brukar kallas 'server' medan sändande processer kallas klienter. Elementen i brevlådan är länkade 4-bytes element, som vanligen tolkas som pekare till det "riktiga" meddelandet API-anropen är som följer: API namn CreateQ OpenQ CloseQ Innebörd skapa och returnera ett 'handle'; ägaren en klient kan öppna och stänga en kö PeekQ Läs nästa element utan att ta bort det ur kön PurgeQ Ägaren kan radera hela kön QueryQ Vilken status har kön; tom? ReadQ WriteQ en receive operation en send operation ii. Typen 'pipe' kan ses som en kanal mellan två processer. En 'pipe' kan vara anonym eller namngiven. Anonyma 'pipes' är FIFO-köer som används som i Unix; sk filter(program) läser från 'standard input', gör någon bearbetning och skriver på 'standard otput'. Ett 'shell'kommando kan omdefiniera fil-i/o till i/o via 'pipes', så att kanalen blir "osynlig" för filtren. Namngivna 'pipes' är tänkta att användas för kommunikation via datornät, tex rpc ('remote procedure calls'; beskrivs i kurser om distribuerade system) och transaktionshantering.

60 3.6. Monitorer Semaforer har givna strukturer och fixa operationer. Brevlådor har godtyckliga data men fixa operationer. Tanken med monitorer är bla att göra möjligt att skapa godtyckliga data(strukturer) och godtyckliga operationer. 3.6.1. Specifikation Följande punkter beskriver informellt en monitors syntax och semantik. i. Det är en programspråkskonstruktion (monitor namn (params) ) och ska i dess helhet kunna översättas till maskinkod inkl systemanrop av en kompilator. ii. En grundfunktion är att konstruktionen manifesterar en abstrakt datatyp som begreppet class (i Simula) eller package (i Ada). Data som definieras i monitorn, tex en begränsad buffert, är åtkomliga från processer endast indirekt via anrop till procedurer (procnamn (par)) i monitorn: namn (params).procnamn (par) iii. En monitor kan vara en typdeklaration, så att olika instanser av monitorobjekt kan skapas. iv. Exekvering av procedurer i monitorn är per definition på basis av ömsesidigt uteslutande: Endast en process åt gången kan exekvera i (äga) en monitor-procedur. v. Valfria synkroniseringar kan byggas med hjälp av primitiverna wait och signal (obs ej exakt lika motsvarande semaforoperationer) på kövariabler (queue, condition): var xxx: queue;...; xxx.signal; xxx.wait;.... a. xxx.wait betyder att den process åt vilken monitorproceduren körs blockeras och att monitorn blir ledig (för eventuellt väntande process). b. xxx.signal betyder att den signalerande processen blockeras och att en på xxx väntande process omedelbart körs igång. Om ingen väntande process finns, så tolkas operationen som null. Regel v.b infördes för att underlätta bevisföring som avser programegenskaper utan att behöva förlita sig på en process-schemaläggare. En annan monitor-definition erhålls om man låter signal-operationen göra en väntande process redo. 3.6.2. Exempel: Begränsad buffert Följande monitor visar hantering av en begränsad buffert. Monitorn anropas från producentprocessen med bounded-buffer(size).put(x) för att deponera ett meddelande och från konsument-processen med bounded-buffer(size).get(y) för att ta emot ett meddelande. monitor bounded-buffer (N); begin var buffer: array [0.. N-1] of item, -- item och N deklarerade globalt, bufferten lokal här lastpointer, count: 0.. N-1, nonfull, nonempty: queue; procedure put (x:item); begin if count = N then nonfull.wait; buffer [lastpointer] := x; count := count + n 1; - modulo n addition lastpointer := lastpointer + n 1; nonempty.signal; end;

61 procedure get (x:item); begin if count = 0 then nonempty.wait; x := buffer [lastpointer - n count]; count := count - 1; nonfull.signal; end; lastpointer := count := 0; -- initieringar end bounded-buffer; 3.6.3. Exempel: Fem filosofer Problemet "fem dinerande filosofer" har följande lösning uttryckt i monitorer. monitor fdf; var state: array [0..4] of (thinking, hungry, eating); self: array [0..4] of queue; internal procedure test (k: 0..4); -- intern procedur i fdf; begin if state [k-1 mod 5] eating and state [k+1 mod 5] eating and state[k] = hungry; then state [k] := eating; self[k].signal; end if; end {test}; callable procedure pickup (i: 0..4); begin state [i] := hungry; test (i); if state [i] eating then self [i].wait; end {pickup}; callable procedure putdown (i: 0..4); begin state [i] := thinking; test (i-1 mod 5); test (i+1 mod 5); end {putdown} end {fdf}; Monitorn anropas från varje "filosofprocess" k = 0,..., 4 med loop {think}; fdf.pickup(k); {eat}; fdf.putdown(k); end loop; 3.6.4. Implementering I en implementering uppstår köer av tre skäl: -- Köer av processer som väntar på att få komma in i anropade monitorprocedurer. -- Köer av processer som låtit monitorn utföra signal. -- Köer associerade med varje variabel med typvärde queue. Monitorer kan implementeras genom att kompilatorn genererar systemanrop dels vid inträdet i och vid lämnandet av monitorn, dels vid varje signal och wait-operation. Anropen kan vara semaforinstruktioner. Vi behöver följande: