Synkronisering af sekventielle processer



Relaterede dokumenter
Abstrakte datatyper C#-version

Skriftlig eksamen i Datalogi

Videregående Programmering for Diplom-E Noter

Specifikation Abstrakt OO OS-API Rev Specifikation. Abstrakt, objektorienteret operativsystem-api

Kursus navn: Indledende programmering Kursus nr

SmartAir TS1000. Daglig brug

DM507 Algoritmer og datastrukturer

University of Southern Denmark Syddansk Universitet. DM502 Forelæsning 2

DATALOGI 1E. Skriftlig eksamen torsdag den 3. juni 2004

Eksamensadministration, EUD, udtrækning af elever Sidst opdateret /version 1.3 /UNI C/Steen Eske Christensen

Introduktion til datastrukturer. Introduktion til datastrukturer. Introduktion til datastrukturer. Datastrukturer

i x-aksens retning, så fås ). Forskriften for g fås altså ved i forskriften for f at udskifte alle forekomster af x med x x 0

Introduktion til datastrukturer. Introduktion til datastrukturer. Introduktion til datastrukturer. Datastrukturer

Formålet med undervisning fra mediateket er at styrke elevernes informationskompetence, således de bliver i stand til:

Projekt - Valgfrit Tema

Programmering i C. Lektion december 2008

dcomnet-nr. 8 Simpel aritmetik på maskinniveau Computere og Netværk (dcomnet)

Tastevejledning Windows XP

1 Overfør kvalitetsområdedokumenterne fra Revimentor Mastermanual 1. Åbn egen Revimentor, og vælg modulet "Manualen".

Notat. Introdansk beskrivelse af fastlagte krav til indberetning af statistikoplysninger fra udbydere JL

Opkrævninger - menuen 3.1

DM507 Algoritmer og datastrukturer

19 Hashtabeller. Noter. PS1 -- Hashtabeller. Hashing problemet. Hashfunktioner. Kollision. Søgning og indsættelse.

Regulære udtryk og endelige automater

Algoritmeskabeloner: Sweep- og søgealgoritmer C#-version

18 Multivejstræer og B-træer.

Processer og tråde. dopsys 1

Løbetræning for begyndere 1

AAU, Programmering i Java Intern skriftlig prøve 18. maj 2007

Lineære ligningssystemer

dcomnet-nr. 6 Talrepræsentation Computere og Netværk (dcomnet)

Athena DIMENSION Varmeanlæg 4

CCS Formål Produktblad December 2015

Kom godt i gang med. Nem Konto. Vejledning til sagsbehandlere. NemKonto hører under Økonomistyrelsen

Programmering af CS1700-Proxlæser

Eksamens spørgsmål i Teknologi (Digital) 3. Semester (i)

BRP Kursusintroduktion og Java-oversigt

SPSS introduktion Om at komme igang 1

Regler for CTF. (Energinet.dk Gastransmissions regler for Capacity Transfer Facility)

Allan C. Malmberg. Terningkast

Anklagemyndighedens Vidensbase

DATALOGI MASKINARKITEKTUR Blok 2 samt Reeksamination i DATALOGI MASKINARKITEKTUR Blok 1 og arkitekturdelen af DATALOGI 1E

DM536. Rapport og debug

Håndtering af stof- og drikketrang

Tilgang til data. To udbredte metoder for at tilgå data: Sekventiel tilgang Random access: tilgang via ID (også kaldet key, nøgle) for dataelementer.

DESIGNMANUAL Roskilde Dyrskue

Bemærk det sidste kapitel Modtagelse af et brev, som bl.a.. bruges når du skal modtage og indlæse en henvisning.

Tips & trick til at finde rundt på halvtolv.dk

Læs Dette Først! DM100i/DM200i Serien Digital frankeringsmaskine. Quick Installeringsguide

Om at gå til mundtlig eksamen en manual for studerende

Vejledning for FMS_PBS

Hvad sker der med sin i moderne dansk og hvorfor sker det? Af Torben Juel Jensen

Kom godt igang med Inventar registrering

Algebra INTRO. I kapitlet arbejdes med følgende centrale matematiske begreber:

Grundlæggende Programmering ITU, Efterår Skriftlig eksamen i Grundlæggende Programmering

Eksamensopgaver datalogi, dlc 2011 side 1/5. 1. Lodtrækningssystem

Vejledning til Tidtagningssystem til ridebanespring. Sønderborg. Knap for addering af 6 sekunder ved total nedrivning af forhindring.

Et udtryk på formena n kaldes en potens med grundtal a og eksponent n. Vi vil kun betragte potenser hvor grundtallet er positivt, altså a>0.

Transkript:

Synkronisering af sekventielle processer Klaus Hansen, Niels Elgaard Larsen, Maz Spork, Jørgen Sværke Hansen 15. februar 2005 1 Introduktion Ved en multiprogram forstår vi et program, hvori vi kan specificere, at flere sekvenser af ordrer er under udførelse samtidigt og uafhængigt af hinanden. Disse sekvenser kan enten implementeres som forskellige tråde internt for en proces eller som forskellige processer. I det første tilfælde omtaler man også til programmet som et flertrådet program. I det følgende vil vi referere til sekvenserne som processer. Under specifikationen af en enkelt proces kan vi ikke vide noget om de andre processers hastighed, og dermed hvor langt de er kommet. Dette sidste viser sig at være en af de væsentligste vanskeligheder, når man skal konstruere et multiprogram. Problemet findes i mange sammenhænge i såvel materiel som programmel, og er blevet løst på mange forskellige måder. Principielle overvejelser omkring 1965 førte til Dekkers algoritme og Dijkstras semaforer 1. Varianter af disse findes beskrevet i [SGG04], afsnit 7.2 og 7.5. I det følgende behandles semaforer lidt mere dybtgående. 2 Tidsafhængighed i multiprogrammer Når der opstår en fejl i et sekventielt program, altså i et program med kun én sekvens af ordrer under udførelse, kan denne fejl reproduceres ved at køre programmet igen med de samme data. Vi kan altså foretage programtestning, som vi kender det. Anderledes forholder det sig med multiprogrammer. Nu spiller tiden en rolle, dvs. hastigheden hvormed de enkelte processer afvikles, og resultatet af en kørsel kan ikke nødvendigvis reproduceres. Lad os som eksempel vende tilbage til forespørgselssystemet fra forrige afsnit. I hver post i registret er der en tæller, som angiver hvor mange gange en given post har været aflæst siden den blev oprettet. Når vi således har fundet den post vi søger, skal vi opdatere tælleren og skrive blokken tilbage på pladelageret inden indholdet i posten skrives på terminalen: 1 Ordet semafor stammer fra det franske ord sémaphore (fra græsk "sema"tegn, og "férein"bære) og betegner en signalmast for optisk telegrafi. Ordet bruges stadig på engelsk for jernbanesignal og på italiensk for trafiklys. 1

<læs blok>; if (<post fundet>) { blok.post.tæller++; <skriv blok>; Problemet er, at de tre processer arbejder på fælles data beliggende på pladelageret. Når en proces ønsker at opdatere disse, må den ikke blive forstyrret af andre processer, der piller ved de samme data. Opdateringen, der starter med læs blok er først overstået, når skriv blok er udført, og når dette foregår, må andre processer ikke pille ved den samme blok. Problemet kan løses hvis opdateringen kan foregå udeleligt i en kritisk region. En kritisk region knytter sig til de fælles data (her pladelageret), man opererer på; og det er kun processer, der ønsker adgang til de samme data, vi ønsker standset. At have kritiske regioner er desværre ikke nok, når vi skal lave et multiprogram korrekt. Kritiske regioner sikrer kun, at vi kan opdatere fælles data udeleligt; de hjælper os ikke når vi skal transmittere data fra en proces til en anden. Lad os se på et eksempel: I figur 1 ses en konfiguration med en kommunikationsport og en skriver. Vi ønsker et program, der kan indlæse en række linier og kopiere indholdet ud på skriveren så hurtigt som muligt. Et sekventielt program: læs linie; udskriv linie; forever er således ikke tilfredsstillende, da læseren (kommunikationsporten) venter, når skriveren arbejder og omvendt. Vi skal altså overlappe brugen af læser og skriver. Fx således: char indlinie[80], udlinie[80]; process læser { læs(indlinie); memcpy(udlinie, indlinie, 80); forever process skriver { udskriv(udlinie); forever Hvis tiden det tager at kopiere indlinie er lille i forhold til læsehastigheden, vil processen læser indlæse med portens fulde hastighed. Skriveren udnyttes også fuldt ud. Men vil programmet fungere korrekt? Ja, vi har vel allerede en mistanke om, at dette ikke altid vil være tilfældet. De to processer opererer nemlig på den fælles variable udlinie, og resultaterne afhænger derfor af de to processers indbyrdes hastighed. Vi kan tabe en linie, hvis læseren et øjeblik er for hurtig og indholdet i udlinie overskrives inden det er trykt, eller vi kan få flere udgaver af samme linie, hvis skriveren er for hurtig. Problemet kan ikke klares med kritiske regioner. Selvom vi har udelukket processen læser fra at røre udlinie, kan vi jo ikke i processen skriver afgøre om linien skal udskrives eller ej ud fra 2

Kommunikationsport Centralenhed Skriver Figur 1: Konfiguration, hvorpå vi kan kopiere indlæste linier ud på en skriver værdien af udlinie alene. Når vi har skrevet indholdet af udlinie er processen nødt til at vente på, at den variable får et nyt indhold, og det kan vi kun få at vide fra processen læser. Tilsvarende er processen læser nødt til at vente med kopieringen til forrige linie er udskrevet. De to processer er altså nødt til at kommunikere omkring brugen af variablen udlinie. Vi skal nu først se på de kritiske regioner, og dernæst hvordan vi klarer en venten som ovenfor, når der er tale om processer i et multiprogram. 3 Semaforer Den simpleste form for kommunikation mellem to processer består i at en proces kan sende et signal til en anden. Til at klare denne form for kommunikation foreslog Dijkstra [Dij] en speciel type variable, semaforer, som er ikke-negative heltal, der tæller antal signaler, som er sendt og endnu ikke modtaget. Han foreslog desuden to operationer på sådanne variable: signaler, hvorved et signal sendes, og vent, hvorved et signal modtages. (Hos Dijkstra kaldes disse operationer V og P 2 ). Da vi jo hverken kender hastigheden af den afsendende eller den modtagende proces er det umuligt at vide om en proces sender signalet før modtageren venter på det eller omvendt. Operationerne signaler og vent er derfor defineret, så det er ligegyldigt i hvilken rækkefølge de udføres. Hvis en proces ønsker at modtage et signal før det er klart, forsinkes processen simpelthen indtil signalet afsendes. Omvendt, hvis signaler sendes hurtigere end de modtages, bliver de simpelt gemt ved, at semaforens værdi tælles op med én for hvert signal. Dermed er synkroniseringen blevet tidsuafhængig. De to operationer ser således ud, idet s er et heltal: 2 Oprindelsen til P og V var ifølge Andrews & Schneider [AS83] med henvisning til Dijkstra oprindeligt de hollandske ord passeren (at passere) og vrijgeven (at frigive). Senere efterrationaliseredes betydningen af P og V til prolagen = proberen verlagen (at forsøge at nedtælle) og verhogen (at optælle). 3

vent(s) { if (s> 0) s--; else <vent på at s>0>; signaler(s) { if (ventende) <aktiver en>; else s++; Operationerne navngives lidt forskelligt i litteraturen oftest afhængigt af den valgte semantik for semaforerne, eksempler er: vent kan hedde wait, lock, down, lås, P, enqueue, sleep, acquire; signaler kan hedde signal, open, up, send, åbn, V, dequeue, wakeup, release. I lærebogen [SGG04] afsnit 7.5 kaldes de release og acquire. Lad os nu se, hvordan vi kan bruge disse semaforer til at løse problemerne fra forrige afsnit. Først etablering af udelelig adgang til fælles data. Det klares med en semafor adgang (i bogen kaldet mutex) og så følgende udformning af opdateringen vent(adgang); <kritisk region, hvor der opdateres>; signaler(adgang); Semaforen skal så have værdien 1 til at starte med. Når adgang har værdien 1 er der ikke udført en venteoperation, og ingen proces er i den kritiske region. Er værdien 0, har en eller flere processer udført vent, men kun én har kunnet køre videre, de øvrige venter. Semaforen kan kun have disse to værdier. Dernæst de to kommunikerende processer. Det klares med to semaforer: læst, skrevet. char indlinie[80], udlinie[80]; semafor læst, skrevet; process læser { læslinie(indlinie); vent(skrevet); memcpy(udlinie, indlinie, 80); signaler(læst); forever 4

process skriver { vent(læst); udskriv(udlinie); signaler(skrevet); forever Semaforerne initialiseres så skrevet = 1 og læst = 0. Hvis der er flere buffere i systemet end de to, er det let at generalisere løsningen; det kaldes i litteraturen bounded-buffer eller et producer-consumer system. Semaforen skrevet skal da initialiseres til antallet af ledige buffere. De her nævnte semaforer kaldes tællesemaforer, idet de kan antage ikke negative værdier, og operationerne vent/signaler tæller værdien ned/op. Der findes andre semafortyper: binære semaforer, som kun antager værdierne 0, 1; beskedsemaforer, hvor der ikke tælles i en tæller, men sættes beskeder i en kø (antallet af køelementer svarer til tællesemaforens værdier); generelle semaforer, som er beskedsemaforer, hvor beskeder har en type og vent har en yderligere parameter, der angiver en mængde af typer, processen er interesseret i; postkassesemaforer, hvor signaler aktiverer samtlige ventende processer; trafiklyssemaforer, som har to tilstande, rød og grøn, og tre operationer: start, som aktiverer alle ventende og sætter tilstanden til grøn, stop, som sætter tilstanden til rød og passer, som enten lader processer fortsætte (grøn), eller sætter den til at vente (rød tilstand). En semafor er altså ikke et entydigt begreb, men der er nogle fællestræk: 1. Semaforer løser synkroniseringsproblemer en gang for alle, således at disse tages fra brugerprogrammer ind i en fælles kerne. 2. Semaforoperationer er udelelige; hvorledes er ikke et problem i brugerprogrammet. 3. Aktiv venten i venteløkker i brugerprogrammet undgås; skal en proces vente på en betingelse, kan dette ske på en måde således at processkift er muligt. Den ovenfor beskrevne tællesemafor blev vist som en heltalsvariabel, idet konsekvensen af sætningen vent på at s > 0 med hensyn til datastrukturer blev forbigået. I dagliglivet venter man gerne på en hændelse ved at stå i kø, idet køen dog ikke altid er klart synlig. Ved beskrivelsen af semaforer kan vi benytte køstrukturer til at vente i, denne er kaldt prockø nedenfor; bemærk at der ved implementation af operationerne ikke nødvendigvis benyttes en kø eller en liste. Derfor er skitserne for operationerne skrevet uden direkte brug af denne kø. Variablen ventende holder styr på antallet af ventende processer. De forskellige semafortyper kan løseligt beskrives, som angivet herefter. 5

Eksempel 1: binær semafor class BinærSem { enum Tilstand {åben, låst tilstand; kø prockø; int ventende; public: void BinærSem(Tilstand t=åben) { tilstand=t; ventende=0; void vent(); void signaler(); ; Eksempel 2: tællesemafor class TælleSem { int tæller; kø prockø; int ventende; public: void TælleSem(int c) { tæller=c; ventende=0; void vent(); void signaler(); ; Eksempel 3: beskedsemafor class BeskedSem { kø bufkø; kø prockø; int ventende; public: void BeskedSem(){ ventende=0; buf *vent(); void signaler(buf *); ; 6

Eksempel 4: postkassesemafor class PostkasseSem { kø prockø; int ventende; public: void PostkasseSem() {ventende=0; void vent(); void signaler(); ; Eksempel 5: trafiklyssemafor class TrafiklysSem { enum Farve {grøn, rød farve; kø prockø; int ventende; public: void TrafiklysSem (Farve startfarve=grøn) { farve=startfarve; ventende=0; void vent(); void start(); void stop(); ; De tilsvarende operationer kan nu skitseres; idet <kø>.tilkø(), <kø>.frigiv() og <kø>.tom() nøje svarer til operationerne put, get og isempty på Queue i appendiks A.2. I skitserne er anvendelsen af disse på prockø skjult i <bloker proces> og <aktiver proces>, idet det det er et implementationsanliggende, 1) om der overhovedet er en kø, 2) hvad det er, der sættes i kø og tages ud af kø, og 3) hvorledes en proces blokeres og aktiveres. I stedet for <prockø>.tom() anvendes derfor ventende>0. binær semafor void BinærSem::vent() { if (tilstand==åben) tilstand=låst; else { ventende++; <bloker proces>; void BinærSem::signaler() { if (tilstand==låst) if (ventende>0) { <aktiver proces>; ventende--; else tilstand=åben; 7

tællesemafor void TælleSem::vent() { if (tæller>0) tæller--; else { ventende++; <bloker proces>; void TælleSem::signaler() { if (ventende>0) { <aktiver proces>; ventende--; else tæller++; beskedsemafor buf *BeskedSem::vent() { if (!bufkø.tom()) return(bufkø.frigiv()); else { ventende++; <bloker proces>; void BeskedSem::signaler(buf *buffer) { if (ventende>0) { aflever(buf); <aktiver proces>; ventende--; else bufkø.tilkø(buf); postkassesemafor void PostkasseSem::vent() { ventende++; <bloker proces>; void PostkasseSem::signaler() { while (ventende>0) { <aktiver proces>; ventende--; ; 8

trafiklyssemafor void TrafiklysSem::vent() { if (farve==rød) { ventende++; <bloker proces>; void TrafiklysSem::start() { farve=grøn; while (ventende>0) { <aktiver proces>; ventende--; void TrafiklysSem::stop(){ farve=rød; Der findes andre løsninger på synkroniseringsproblemet og de kritiske regioner, se Silberschatz, Galvin og Gagne [SGG04], kapitel 7. 4 Valg af semafortype Det er ikke muligt at pege på en af semafortyperne og sige at det er den bedste; hver type er lavet til bestemte formål. At implementere alle mulige semafortyper i en kerne synes derimod at være en upraktisk ide; det ville være bedre at vælge en eller to som er særligt anvendelige, og dermed velegnede til at tage med i en generel kerne. At udvælge fx binære semaforer, fordi de er særligt nemme at implementere på nogle maskiner, er heller ikke en god ide. En måde at sortere på efter anvendelighed er at overveje anvendelsesområder, samt at overveje hvordan én semafortype kunne implementeres ved brug af en anden; dette kunne vise lidt om typernes generalitet. Det vil føre for vidt her at søge alle 20 kombinationer beskrevet; dette overlades til læseren som en opgave. Vi kan dog vise to kombinationer, tællesemafor baseret på binær og postkasse baseret på tællesemafor. Eksempel 1: tællesemafor En tællesemafor konstrueres ved hjælp af to binære semaforer, MUTEX (initielt åben) og LOCK (initielt låst), og en heltalsvariabel tæller (initielt 0). MUTEX sørger for at operationer foregår udeleligt. Operationerne bliver: 9

void TælleSem::vent() { LOCK.vent(); MUTEX.vent(); tæller--; if (tæller<> 0) LOCK.signaler(); MUTEX.signaler(); void TælleSem::signaler() { MUTEX.vent(); if (tæller== 0) LOCK.signaler(); tæller++; MUTEX.signaler(); Eksempel 2: postkassesemafor En postkassesemafor kontrueres ved hjælp af en tællesemafor, LOCK (initielt 0), en heltalsvariabel antal (initielt 0), samt en binær semafor MUTEX, som sørger for udelelig udførelse af operationerne, som bliver: void PostkasseSem::vent() { void PostkasseSem::signaler() { MUTEX.vent(); MUTEX.vent(); antal++; for (i= 0; i<antal; i++) LOCK.signaler(); MUTEX.signaler(); antal=0; LOCK.vent(); MUTEX.signaler(); Semaforers anvendelsesområder Resultatet af en komplet undersøgelse bliver, at enhver semafortype er tilstrækkelig, idet alle typer kan konstrueres ud fra én af disse. Tælle- og beskedsemaforer er de to typer, der er mest generelt anvendelige. Hvad er nu anvendelsesområderne for de forskellige typer? Det vil føre for vidt at give udtømmende eksempler, og læseren henvises til rapporten om MIK systemet for detaljer [Sch76]. Her kan kort opsummeres følgende: Semafortype binær semafor tællesemafor beskedsemafor postkassesemafor trafiklys anvendelse tilgang til kritiske regioner (mutual exclusion). administration af en samling ens ressourcer. udveksling af data mellem processer. signal fra en proces til en eller flere andre om at en betingelse er opfyldt. semafor signal om at en proces er i en given tilstand (temmelig speciel). 10

FULDE LÆSER SKRIVER FRIE Figur 2: Køer af tomme og fyldte buffere i eksempel 3 I dagligdagen kendes semaformekanismer også: binære semaforer svarer til låsen på en dør, der giver adgang til steder, hvor man gerne vil være alene, fx et toiletrum. Tællesemaforer svarer til adgangen til fx parkeringskældere, restauranter eller diskoteker med begrænset kapacitet der er en kø ved indgangen, når der er flere der vil ind end der er plads til. Beskedsemaforer er sværere at finde eksempler på, ét er et øldepot med tomme og fulde ølkasser. Nedenfor vises eksempler på brug af beskedsemforer til dataudveksling. Det er den samme opgave der løses, en generalisering af eksemplet først i afsnit 3 med to kommunikerende processer, således at der anvendes et vilkårligt antal buffere. Eksempel 3: brug af beskedsemaforer En klassisk anvendelse af beskedsemaforer er Producent-Forbruger-algoritmen. Se Figur 2. typedef char buf[80]; BeskedSem FRIE, FULDE; buf *lbuf, *sbuf, *bufref; process læser { lbuf=frie.vent(); læslinie(*lbuf); FULDE.signaler(lbuf); forever; process skriver { sbuf=fulde.vent(); udskriv(*sbuf); FRIE.signaler(sbuf); forever; 11

De to køer skal initialiseres. Det antages at en beskedsemafor har en tom kø, når den erklæres. Der skal derfor fremskaffes N beskeder til FRIE, fx ved for (i=0; i<n; i++) { bufref=alloker(sizeof(buf)); FRIE.signaler(bufref); ; Konklusionen af dette afsnit vil altså blive, at man i et generelt system står over for valget mellem besked- og tællesemaforer, og her er beskedsemaforen langt det stærkeste værktøj 3. Til gengæld er tællesemaforen langt enklere at implementere, og operationerne derfor sandsynligvis hurtigere. Moralen må derfor blive at begge typer med fordel kan medtages i en generel kerne. I de følgende afsnit benytter vi udelukkende tællesemaforer på grund af deres enkle implementation. 5 Opgaver 1. Hvorledes kan frakobling af alle afbrydelser bruges ved implementering af semaforoperationer? 2. Hvad er fordele og ulemper ved at implementere semaforoperationer i mikroprogram eller PAL-kode, således at de ligestilles med egentlige instruktioner i maskinen? 3. Hvorledes kan en binær semafor konstruerers ud fra tællesemaforer? 4. Hvorledes kan en beskedsemafor og en trafiklyssemafor konstrueres ud fra tællesemaforer? 5. Hvorledes kan en tællesemafor konstrueres ud fra en beskedsemafor? 6. Hvorledes kan en tællesemafor konstrueres ud fra en trafiklyssemafor? Litteratur [AS83] [Dij] Gregory R. Andrews and Fred B. Schneider. Concepts and notations for concurrent programming. Computing Surveys, 1983. E.W. Dijkstra. Cooperating sequential processes. In Programming Languages. [Sch76] B. Schrøder. Mik - korutineorienteretstyresystem til en mikrodatamat. Technical report, DIKU, 1976. [SGG04] J.L. Silberschatz, P.B. Galvin, and G. Gagne. Operating System Concepts with Java (6.ed). John Wiley & Sons, 2004. 3 Beskeder (messages) var det eneste interproceskommunikationsprimitiv i RC4000 (se [SGG04] afsnit 22.5), i RC3600 og RC8000 fra Regnecentralen, hvor det beviste sin velegnethed. 12