Modelvragen Besturingssystemen I 1) Reeks A 1.1. Vraag A1 Procesbeheer: interrupts en uitvoering van besturingssystemen a) Beschrijf de werking en de bedoeling van interrupts: eenbeschrijving van het mechanisme van I/O bewerkingen !), andere interrupt optreedt. b) Bespreek drie alternatieven waarop de relatie tussen het besturingssysteem tot processen kan worden benaderd (cfr. §2.4 van de cursus, uitvoering van besturingssystemen): bestaat het besturingssysteem zelf uit processen ? c) Welke alternatieven worden gevolgd in UNIX, Linux en Windows NT ? d) Kan men in deze besturingssystemen gebruik maken van threads ? Zo ja, welk implementatiemodel volgen deze threads ? Werking en bedoeling van interrupts: Een interrupt is een hardwaremechanisme dat een extern apparaat in staat stelt om de CPU een signaal te sturen wanneer de I/O operatie voltooid is. Een interrupt wordt gegenereerd door een I/O controller (niet door de administrator), door een signaal op de IRQ (Interrupt Request Line) lijn te zetten. Verwerking door de CPU: In de interruptcyclus controleren of er interrupts zijn opgetreden door het interruptsignaal te bekijken. Er zijn geen interrupts: -> De CPU gaat verder met de opvraagcyclus. Er zijn wel wachtende interrupts: -> De CPU onderbreekt huidige programma, slaat informatie op voor het hervatten van het programma. De inhoud van alle registers en het adres van de volgende instructie in het hoofdprogramma, worden op de stack geplaatst. De programmateller wordt bijgewerkt zodat hij naar het begin van de interruptroutine verwijst. Verwerking door het besturingssysteem: De verwerking is afhankelijk van de computerarchitectuur en het besturingssysteem. Soms is er één enkele routine voor de interruptafhandeling, of er is een specifieke routine voor elk type interrupt. Soms is er één interruptlijn per I/O controller, soms is er één interruptlijn per voor meerdere controllers, deze moeten zich dan kunnen identificeren bij het genereren van een interrupt. De meeste architecturen maken gebruik van een interruptvector, een tabel van verwijzingen naar gespecialiseerde routines voor specifieke interruptafhandeling. De routine voor de interrut wordt hierbij indirect via de tabel aangeroepen. Zo moeten niet alle apparaten afgescand worden, om te zien welk apparaat de interrupt gegenereerd heeft. Mechanisme van I/O bewerkingen: Om een I/O-operatie te starten, laadt de CPU gegevens in de I/O-controller. Hierdoor weet de controller, welke gegevens moeten opgehaald worden. Bij een uitvoer, van hoofdgeheugen naar I/Oapparaat, moeten ook de buffers gevuld worden. (1) en (2). De controller interageert dan met het apparaat, om de data in of uit de buffer te schrijven. (3). Bij geprogrammeerde I/O moet het gebruikersprogramma nu een wachtlus uitvoeren, om te zien of de actie is uitgevoerd. (4) en (5). In elke wachtlus gaan 3 instructiecycli verloren: Het apparaatregister lezen. Waarde van signaleringsbit bepalen. Voorwaardelijke spronginstructie uitvoeren. Na de wachtcycli kan nu de buffer naar het hoofdgeheugen geschreven worden, of is de buffer vrij voor een nieuwe I/O-operatie. (6). Deze synchrone benadering houdt de processor telkens op wanneer een I/O-aanvraag wordt verwerkt. Een I/O-bewerking bestaat uit 3 stappen: Een voorbereidende sectie. De feitelijke I/O-overdracht. Voltooiing van de opdracht. De feitelijke I/O-overdracht kan enkele duizenden processorcycli in beslag nemen, dit verspilt processorgebruik. Interrupts en I/O-bewerkingen. Met interrupts kan de processor andere instructies uitvoeren terwijl een I/O-bewerking bezig is. Nadat de I/O-opdracht is gegeven, wordt de besturing teruggegeven aan het hoofdprogramma. De I/O-bewerking wordt gelijktijdig uitgevoerd met het hoofdprogramma, als de bewerking voltooid is wordt een interrupt naar het hoofdprogramma doorgestuurd, die de I/O-opdracht voltooit, en dan doorgaat met het hoofdprogramma. Hierdoor ontstaat overhead, door het verwerken van de interrupt, maar de processor verspilt geen tijd met wachten op afhandeling van de I/O-bewerking. Toch kan het toch voorvallen dat de processor toch moet wachten, maar dan nog is er verbetering van efficiëntie. Een interrupt gedurende een interrupt: Er zijn twee mogelijke manieren om dit op te lossen. Een eerste benadering is om interrupts te blokkeren, als een interrupt verwerkt wordt. De interrupts worden in sequentiële volgorde behandeld. In een tweede benadering worden prioriteiten ingevoerd. Routines voor interruptafhandeling kunnen onderbroken worden door interrupts met hogere prioriteit. Drie alternatieven waarop de relatie tussen het besturingssysteem tot processen kan worden benaderd. Het besturingssysteem als proces. Kernel zonder processen: Dit wordt vooral gebruikt in oudere besturingssystemen. De kernel heeft een eigen geheugengebied en een eigen systeemstack. Processen worden enkel toegepast op gebruikersprogramma’s. De kernel werkt als een afzonderlijke entiteit, in een geprivilegieerde kernelmodus. Bij een interrupt, trap of systeemaanroep wordt het huidige actieve proces opgeslagen en de besturing overgedragen aan de kernel. Besturingssysteem is een verzameling van systeem aanroepen. De software van het besturingssysteem wordt uitgevoerd in de context van een gebruikersproces. De procesbeelden bevatten ook programma-, gegevens- en stackgebieden voor kernelprogramma’s. Bij een interrupt, trap of systeemaanroep wordt een moduswisseling uitgevoerd, en een routine voor het besturingssysteem uitgevoerd. Er dient niet altijd een proceswisseling plaats te vinden. Indien een proceswisseling noodzakelijk is, wordt binnen het proces de context opgeslagen en de besturing overgedragen aan een routine voor proceswisseling buiten alle processen om. Binnen één proces kunnen zowel gebruikersprogramma’s als besturingssysteemfuncties uitgevoerd worden, door de verschillende modi, kan de gebruiker de functie van het besturingssysteem niet storen. Microkernelbenadering. Besturingssysteemfuncties worden gestructureerd als aparte processen, uitgevoerd in kernelmodus. In deze benadering zijn veel meer proceswisselingen met de nodige overhead noodzakelijk. Het besturingssysteem kan wel beschouwd worden als een verzameling verschillende modules, met onderling eenvoudig interfaces. De processen kunnen met aangepaste prioriteit verweven worden met andere processen. Sommige processen hoeven niet noodzakelijk in de kernelmodus uitgevoerd worden. Een deel van de besturingssysteemprocessen kan toegewezen worden aan specifieke processoren. Windows NT: Windows NT is gemodelleerd volgens de microkernelarchitectuur. NT bestaat uit een microkernel en een aantal modules. Het verschil met een zuivere microkernelarchitectuur is dat enkele modules in de kernelmodus worden uitgevoerd. UNIX en Linux: In UNIX en Linux wordt alle software van het besturingssysteem uitgevoerd in de context van een gebruikersproces. Het besturingssysteem wordt dus beschouwd als een verzameling van systeemaanroepen. Gebruik van threads in besturingssystemen: UNIX (klassiek): De klassieke UNIX versies ondersteunen geen multithreading, elk proces komt met één thread overeen. Solaris: Solaris laat in tegenstelling tot de klassieke UNIX varianten, een zeer intensief gebruik van threads toe. Solaris gebruikt net zoals Windows NT en Linux een combinatie van user-level en kernel-level threads. 1.2. Vraag A2 Procesbeheer: kenmerken van moderne besturingssystemen a) Bespreek de (drie) meest typische kenmerken van moderne besturingssystemen. b) In hoeverre beantwoorden UNIX, Linux en Windows NT hieraan ? Geef hierbij de belangrijkste elementen van de detailstructuur van deze besturingssystemen. a) 1. Multitasking en multithreading multitasking processoren hebben een groot deel vd tijd niets te doen: processorgebonden perioden worden afgewisseld met I/O gebonden perioden, waarbij de CPU deze laatste periode niet voldoende benut (zelfs niet met interrupts) oplossing: multitasking/multiprocessing: bij onderbreking van een programma door een interrupt, kan de routine voor interruptafhandeling na verwerken van de interrupt de besturing onmiddellijk teruggeven aan het onderbroken programma, of aan een ander programma overdragen. de volgorde van verwerking van programma's door hun relatieve prioriteit. interleaving: processorgebonden perioden van meerdere programma's met elkaar verweven. elk individueel programma heeft de illusie als enige de systeembronnen te gebruiken (door transparantie van gedeeld gebruik van processortijd, geheugen en andere bronnen door OS) multithreading geavanceeerde vorm van multitasking en verschilt van multiprocessing multiprocessing:systeem bevat meerdere processoren, twee processoren kunnen hetzelfde tijdstip elk een proces uitvoeren. multithreading: meerdere gelijktijdige threads voor uitvoering van instructies proces heeft meerdere programmatellers: voor elke thread specifieke teller alle threads delen alle bronnen a/h proces toegewezen alle threads binnen proces zijn evenwaardig (geen ouder-kind relatie) 2. Symmetrische multiprocessing - assymetrische multiprocessing: één master processor waar de kernel van het OS wordt uitgevoerd, andere processoren voor gebruikersprogramma's en hulpprogramma's. master verantwoordelijk voor scheduling. indien processor een dienst nodig heeft (bvb I/O oproep): verzoek sturen naar master, wachten tot dienst uitgevoerd is: master vormt bottleneck voor het systeem. vereist weinig aanpassingen aan het OS. - symmetrische multiprocessing: kernel uitgevoerd op elke processor. ofwel kernel opgebouwd als meerdere processoren, waardoor gedeelten van kernel parallel kunnen worden uitgevoerd door verschillende processoren. ofwel voert elke processor volledige kopie van OS uit. in beide gevallen kan scheduling van taken op elke processor worden uitgevoerd. hogere eisen aan OS: communicatie tussen processoren en synchroniseren van aanspraken op bronnen. ruime mogelijkheden op het gebied van beschikbaarheid en uitbreidingsmogelijkheden. performantie stijgt niet lineair met aantal processoren:niet altijd genoeg programma's die effectief van processor gebruik kunnen maken. 3. Modulair ontwerp - OS software moet regelmatig aangepast worden voor verbetering van fouten, invoeren van diensten of compatibiliteit met nieuwe hardware: noodzaak voor modulair ontwerp. - verschillende manieren voor verdeling in modules - hiërarchisch gescheiden lagen: interactie hardware op onderste niveau ; gebruikersinterface op hoogste niveau elk niveau verzorgt diensten voor het volgende niveau en verbergt implementatiedetails theoretisch model voor hiërarchisch OS in 13 niveaus (cfr OSI 7 model) volledige kernel blijft draaien in kernelmodus waardoor elke laag rechtstreeks toegang heeft tot hardware. Strikte volgorde bij lagenmodel onmogelijk. correcte oplossing waarbij interactie tussen twee niveaus door elke tussenliggende laag wordt doorgegeven: niet efficiënt. in OS geen concrete implementaties van dit gelaagd model. wel toegepast op schaal in bepaalde deelmodules, oa. I/O subsystemen - microkernelarchitectuur: client/server model: enkele essentiële functies vh OS worden toegewezen aan kernel. kernel wordt klein gehouden; andere diensten worden verzorgd door processen (~servers) die in gebruikersmodus worden uitgevoerd. serverprocessen hebben geen rechtstreekse toegang tot de hardware. Ze werken onderling samen adhv doorgeven van berichten via kernel. microkernel maakt modulaire ontwikkeling van kernel en servers wel mogelijk. nieuwe servers en meerdere servers in hetzelfde functionele gebied kunnen toegevoegd worden ; bestaande servers kunnen weggelaten worden (gereduceerde implementaties zoals Windows XP embedded) alle hardware afhankelijkheid zit in microkernel. geschikt voor gedistribueerde systemen: cluster van afzonderlijke computers: processen op verschillende computers sturen onderling berichten zonder te weten op welke machine deze draaien. nadeel: weinig performant door belasting van berichtenuitwisseling. huidig systeem: hoofdmodule (kernel) zorgt voor basisdiensten ; deelmodules geladen indien functionaliteit ervan noodzakelijk is. Deze modules hebben afgeschermde interfaces, en kunnen zelf nieuwe modules oproepen. Er is ook communicatie mogelijk tussen hoofdmodule en deelmodules. b) 1) Windows NT - volgens microkernelarchitectuur: NT bestaat uit een microkernel en een aantal modules Elke systeemfunctie wordt door slechts één component beheerd met een gestandaardiseerde interface elke module kan onafhankelijk van de anderen worden vervangen aantal modules worden in kernelmodus uitgevoerd (performantie: snellere toegang hardware): Executive (gelaagde structuur) Hardware Abstraction Layer: vertaling van algemene opdrachten naar instructies, geheugenmapping, configuratie bussen, afhandeling DMA microkernel: scheduling, synchronisatie processen, traps en interrupts, herstel na stroomonderbreking executieve diensten: NT modules in kernelmodus, geïmplementeerd door systeemthreads I/O manager (I/O verzoeken) Cache manager (schrijfcache beheren) Security Reference Monitor (toegangcontrole) Object Manager (objectbeheer) Proces Manager (proces/threadobjectenbeheer) Virtual Memory Manager (vertaling virtuele->fysieke geheugenadressen) modules in gebruikersmodus: omgevingssubsystemen en afschermingssubsystemen aparte adresruimte controle identiteit generatie toegangstokens versleuteling gegevens beveilingsbeleid (Local Security Authority Subsystem) zorgt voor interactie gebruiker dmv API elk programma: 1 omgevingssubsysteem (indien niet actief: dynamisch opgestart) POSIX subsysteem: UNIX compatibele software compileren en uitvoeren uitgevoerd als proces (Local Procedure Call: aanvraag (bericht) van client via Executive naar juiste server en van die server via Executive terug naar client). LPC beschikbaar via DLL's. - objectgeörienteerde principes zonder uitsluitend objectgeörienteerde methoden te gebruiken grotendeels geïmplementeerd in C/Assembler objecten krijgen een naam (padnaam in dezelfde abstracte naamruimte met 1 enkele root) in gebruikersruimte: toegang tot object indirect via gebruikersfuncties en een objecthandle (beheerd door Object Manager van Executive) objecten gebruikt voor gegevensstructuren binnen Executive (voor toegang in gebruikersruimte) toegang tot gegevens die gedeeld of beveiligd zijn (via Security Descriptors) kernelobjecten/dispatchterobjecten (semaforen, processen, threads) bestanden, geheugensegmenten, I/O poorten berichtenuitwisseling LPC mechanisme 2) UNIX - bestaat uit twee interfaces: kernel (isoleert hardware van gebruiker en toepassingen) gebruikersdiensten (shell, interfacesoftware, componenten C compiler) - kernel van oudere UNIX versies is weinig modulair opgebouwd (niets voorzien om gegevensstructuren te beschermen tegen gelijktijdige toegang door meerdere processoren) - bestaat ook uit twee subsystemen: procesbeheer: geheugenbeheer, scheduling en communicatie tussen processen bestandbeheer en I/O: lagere niveaus binnen dit subsysteem bevat machineafhankelijke code: routines voor interruptafhandeling en apparaatstuurprogramma's ontwikkeld in C -> vrij eenvoudig om UNIX naar nieuwe hardware over te brengen. 3) Linux - modulaire architectuur (kleine kern, aangevuld met andere modules): elke module kan onafhankelijk van elkaar worden geïmplementeerd, op voorwaarde dat externe interfaces van de routines ongewijzigd blijven. - hoofddoel: zoveel mogelijk functionaliteit met beperkte bronnen. - voldoen aan POSIX compatibiliteit - algemeen ontwerp van de kernel lijkt sterk op traditionele vorm UNIX: monolithische kernel modulaire benadering via Loadable Kernel Modules (stuurprogramma's, netwerkprotocol, bestandssysteem): dynamisch linken en verwijderen aan actieve kernel draaien in kernelmodus en dus volledige toegang tot hardware 1.3. Vraag A3 Procesbeheer: procestoestanden a) Teken het algemene (onafhankelijk van een specifiek besturingssysteem) procestoestand diagram. Beschrijf eveneens het eraan geassocieerde wachtrijsysteem. Uit welke entiteiten bestaan deze wachtrijen ? Verduidelijk dit begrip. b) Bespreek de zeven in a) optredende procestoestanden. c) Bespreek alle in a) mogelijke overgangen. d) In hoeverre wijkt het procestoestand diagram van UNIX af van het in a) beschreven algemene diagram. Bespreek deze afwijkingen. a) Wachtrij = lijst met processen die wachten op toewijzing van de scheduler Kan bestaan uit: (als er rekening wordt gehouden met prioriteiten) verzameling gelinkte lijsten, geïndexeerd door tabel van prioriteiten binaire bomen. Elke knoop in die boom verwijst naar max. 2 andere deelbomen, en heeft een kleinere (= lagere?) prioriteit dan die van al zijn subknopen. b) Procestoestanden: 1. Actief (running) = Enkel voor het proces dat op die moment wordt uitgevoerd op die processor. 2. Gereed (ready) = Kunnen uitgevoerd worden wanneer de scheduler hen de toestemming geeft. 3. Gereed–onderbroken = Het proces is gereed maar staat geswapt. (zie p.31) 4. Geblokkeerd (blocked) = Wachten op een bepaalde gebeurtenis (bv. I/O operatie) 5. Geblokkeerd–onderbroken = Een proces wachtend op een gebeurtenis wordt geswapt 6. Nieuw (new) = Dezen zijn al aangemaakt, maar nog niet toegevoegd aan de lijst met uitvoerbare processen. 7. Einde (exit) = Dezen zijn vrijgegeven, maar hun tabellen zijn nog niet afgebroken. Dit geeft ondersteunende programma’s de tijd om de benodigde informatie nog te verwerken. c) Overgangen: 1. nieuw -> gereed: Wordt beslist door scheduler van het OS. 2. gereed -> actief: De scheduler kiest een van de klaarstaande processen. 3. actief -> einde: Proces is voltooid of wordt afgebroken. 4. acief -> gereed: Ofwel heeft OS een tijdslimiet per proces ingesteld, ofwel stopt het proces vrijwillig. (door sleep() bv) 5. actief -> geblokkeerd: Proces moet op iets wachten (I/O, beïndigen kindproces) Meestal in de vorm van een systeemaanroep. 6. geblokkeerd -> gereed: De gebeurtenis waarop het wachtte is opgetreden. Na de introductie van de swap-techniek zijn er nog nieuwe overgangen: 1. geblokkeerd -> geblokkeerd–onderbroken: Als geen enkel proces op gereed staat of als er te weinig hoofdgeheugen vrij is => swappen 2. geblokkeerd–onderbroken -> gereed – onderbroken: Gebeurtenis waarop het geswapte proces wachtte, is opgetreden (OS detecteert dit dan zelf) 3. gereed–onderbroken -> gereed: Proces wordt teruggeswapt. Ofwel is er geen ready proces meer voorhanden in MEM, ofwel heeft dat proces hogere prioriteit dan al de andere ready processen. 4. gereed -> gereed–onderbroken: Soms enige optie om groot genoeg geheugenblok vrij te hebben. Of omdat een met hogere priotiteit voorhanden is. 5. actief -> gereed–onderbroken: bij preëmptief ingrijpen, om MEM vrij te maken. 6. geblokkeerd-onderbroken -> geblokkeerd: Pro-actief vanwege hoge prioriteit of omdat er juist MEM is vrijgekomen. 7. nieuw -> gereed-onderbroken en nieuw -> gereed: twee opties voor activeren nieuwe processen. Bij de eerste wijst men direct adresruimte en tabellen voor het beheer toe, maar swapt men onmiddelijk. Bij de tweede optie volgt men meer een just-in-time principe. Keuze tussen beide opties: job-scheduler (Windows NT and Unix hebben deze niet, plaatsen elk proces gwn in het geheugen) d) Procestoestand diagram van UNIX: afwijkingen 1. Twee verschillende soorten actief, om aan te geven of het om gebruikers- of kernelmodus gaat. Om naar een andere toestand te gaan moet elk proces eerst overgaan naar de kernelmodus. 2. Maakt voor gereed onderscheid tussen 2 deeltoestanden (worden echter door de scheduler als 1 behandeld). Preempted: Proces wordt in deze toestand geplaatst als de uitvoering in kernelmode door een interrupt wordt verstoord. 1.4. Vraag A4 Procesbeheer: creatie en wisselen van processen a) Verduidelijk het begrip PCB. b) Uit welke opeenvolgende stappen bestaat de creatie van een nieuw proces. c) Hoe worden in UNIX en Linux nieuwe processen of threads gecreëerd ? d) Bij welke diverse gebeurtenissen krijgt een besturingssysteem de controle over het computersysteem ? Leiden deze steeds tot een proceswisseling ? e) Bespreek stapsgewijs welke veranderingen aan de omgeving aangebracht worden bij een proceswisseling, en hoe dit gerealiseerd wordt. a) PCB (= Process Control Block) p.33 Een verzameling informatie over het beheer van een proces, bijgehouden in de geheugenruimte van het proces zelf. Het moet altijd beschikbaar blijven in het hoofdgeheugen (of het proces zelf nu geswapt is of niet bijv.) Ze kunnen enkel door instructies in de kernelmodus gemanipuleerd worden. De informatie die het bevat kan in drie categorieën ondergebracht worden: procesidentificatie(pid) = unieke numerieke code eigen aan elk proces, toegewezen door OS. processortoestandinformatie(tss) = Bevat de inhoud van alle processorregisters (oa programmateller). Wordt gebruikt bij het hervatten van het proces. Procesbesturingsinformatie = Extra informatie dat het OS nodig heeft voor het beheren van de verschillende processen. Bijv: -> scheduling- en toestandsinformatie (prioriteit, procestoestand, welke gebeurtenis het op aan het wachten is, ...) -> structurele informatie (hiermee worden procesblokken aan elkaar gekoppeld.) -> bronnen (welke zijn aangevraagd en welke al verkregen) -> privileges van het proces b) Stappen voor procescreatie: p.35 Toewijzing unieke procesidentificatie en nieuwe ingang in de primaire procestabel. Toewijzing voor alle elementen van het procesbeeld. Initialisatie van het procesbesturingsblok. Instelling van de juiste koppelingen (bv plaatsen in wachtrij gereed of gereed-onderbroken) Eventueel het aanmaken of uitbreiden van andere gegevensstructuren. c) Implementatie in UNIX en Linux: p.49 en p.51 Unix Alle processen stammen af van het ouderproces (init). De kindprocessen worden gecreërd in een boomstructuur met vertakkingen.Een kindproces wordt gecreëerd met de systeemaanroep fork(), en uitgevoerd met execve(). (Details hierover zie p.49-50, ik weet niet of we het zo gedetailleerd moeten kennen) Threads: Unix ondersteund geen multithreading Linux fork() wordt vervangen door clone(). Veel meer parameters mogelijk voor kopieren van ouder dan bij Unix. Threads: Linux maakt geen onderscheid tussen processen en threads. Ze worden beide 'tasks' genoemd Een Linux thread is een nieuwe task die dezelfde adresruimte heeft als de oudertask. d) Wanneer krijgt het OS de touwtjes in handen? p. 35 ev. 1. Interrupts: Veroorzaakt door gebeurtenis die zich buiten het op dat moment actieve proces afspeelt. Er wordt eerst overgeschakeld naar kernelmodus voor routine van interuptafhandeling. Belangrijste types interrupts: -> periodieke klokinterrupts (limiet op tijdsduur voor een proces) -> I/O-interrupts (verwittiging dat een I/O operatie is uitgevoerd) -> paginafouten (verwijzingen naar elementen die niet meer in hoofdgeheugen zitten) 2. Trap (= exception): Speciaal soort interrupt. Hangt samen met een fout die wordt gegenereerd binnen het op dat moment actieve proces. (bv als een prog in usermode iets probeert dat enkel in kernelmode mag) 3. Systeemaanroepen (= software-interrupts): Manier voor gebruikersprogramma's om geprivilegeerde instructies uit te voeren, door via systeemaanroepen diensten aan het OS te vragen.Ze worden veroorzaakt op expliciet verzoek van het actieve proces. (kunnen ofwel berichtgestuurd of proceduregestuurd zijn, hiervoor zie p. 37) Wanneer leidt dit nu tot proceswisseling? (zoiezo altijd: context- en moduswisseling) Dit hangt af van het type interrupt dat is opgetreden, de relatieve prioriteit van processen, ... (meer heb ik hier niet over gevonden, zie p.39 bovenaan) e) Stappen en realisatie van proceswisseling p.39 Opslaan van context van het proces dat wordt uitgevoerd. (= processortoestandinformatie van het PCB). Wordt in de hardware uitgevoerd, één van de processorregisters verwijst naar het geheugenadres van de processortoestandinformatie van dit proces. (ondertussen zit men in kernelmodus, was zoiezo al nodig voor de interruptafhandeling die aan de proceswisseling vooraf ging) Verplaatsen van het PCB van dit proces naar de juiste wachtrij. Selectie van ander proces als volgend actief proces. Bijwerken van PCB van nieuw proces (bv met betrekking tot procestoestand) Bijwerken van gegevensstructuren voor het geheugenbeheer, afhankelijk van hoe de virtuele adresvertaling beheerd wordt. (zie H4) Terugschakelen naar de gebruikersmodus en laden van processortoestandinformatie van het geselecteerde proces in de registers van de processor. 1.5. Vraag A5 a) Thread = eenheid van verdeling van processorinstructies. Binnen elk proces kunnen meerdere threads gebruikt worden, en dezen delen allemaal de bronnen die aan dat proces toegewezen zijn. Waarom zijn ze efficiënter? 1. Alle threads delen de toestand en de bronnen van dat proces. Ook de code wordt gedeeld (het voordeel hiervan is dat toepassingen meer actieve threads hebben binnen dezelfde adresruimte, waardoor de processor beter bezig gehouden kan worden). Dit komt zeker van pas bij multi- processorarchitecturen. 2. Communicatie tussen threads kan eenvoudig om gedeeld geheugengebruik worden gebaseerd zonder tussenkomst van de kernel. 3. Creëren en wisselen van threads binnen een proces vraagt aanzienlijk minder overhead dan de overeenkomstige handelingen op processen. 4. Blokeert één thread op een gebeurtenis, dan hoeft het proces daarom niet geblokeerd te worden. Hierdoor kan een programma ook verdergaan terwijl een deel ervan bezig is met een langdurige bewerking. b) Implementatie: Hoe? Nadelen, voordelen? User-level threads: Al het werk voor het threadbeheer wordt door de toepassing zelf uitgevoerd, met gebruik van een threadbibliotheek (= een verzameling routines waarmee threads kunnen worden gecreërd, vernietigd en gewisseld, en waarmee de uitvoering van threads gescheduled kan worden.) De kernel herkent user-level threads niets en blijft het proces als een geheel beschouwen.Het proces kan tijdens een threadwisseling met code in uitvoering door het OS onderbroken worden en later terug worden hervat. Voordelen: a. ondersteund op elk besturingssysteem b. threadwisselingen worden eficienter uitgevoerd omdat er niet naar kernelmodus moet worden overgeschakeld. Zo wordt de overhead van contextwisselingen vermeden. c. het scheduling algoritme kan specifiek aan de toepassing worden aangepast, zonder de scheduler van het besturingssysteem te storen. Nadelen: a. Wordt een thread geblokeerd door een langdurige aanroep, dan zijn alle threads van dat proces tegelijk geblokkeerd. (een groot voordeel van threads gaat hier dus verloren) b. Er kan maar een thread telkens actief zijn binnen het proces. Dus dit heeft geen nut op multi-processorsystemen - Kernel-level threads: Scheduling wordt door de kernel uitgevoerd op basis van threads, niet op bass van processen. Kernel houdt informatie bij voor het proces in het geheel en voor de individuele threads apart. Voordeel: Een geblokkeerde thread blokeert de andere threads van dat proces niet. Nadeel: Wel een modus- en contextwisseling vereist telkens. Hierdoor moeten ontwikkelaars oppassen met het aantal threads dat ze in een proces steken (soms op kernel-level al gelimiteerd). Opmerking: threadpool = als een proces start wordt niet meteen één, maar een bepaald aantal threads al aangemaakt en in een pool geplaatst waar ze wachten op werk. Is beter dan dat er telkens een nieuwe thread moet worden aangemaakt en afgebroken. (bv: Apache v2) - Combinatie van de vorige 2: Verschillende user-lever threads worden in groep gekoppeld aan een aantal kernel-level threads. Hiervoor moet de user-level bibliotheek communiceren met de kernel. Dit gaat via 'lichtgewicht processen'. Dit is een soort virtuele processor, waaraan een user-level thread gebonden kan worden. Voor-/Nadelen: a) Het creëren en wisselen van threads wordt uitgevoerd in de gebruikersruimte, en is dus efficiënter. b) Het blokkeren van een thread kan leiden tot blokkering van een aantal threads binnen dat proces, maar niet noodzakelijk van het hele proces. c) Meerdere threads van hetzelfde proces kunnen parallel uitgevoerd worden op multiprocessoren. c) Bespreek figuur p.47 Zie pagina 47(gewoon hiertussen voegen, is gewoon helemaal hetzelfde) d) Toestandsdiagrammen van Solaris tekeningen zie figuur 2.35 p.52 Solaris ondersteunt zowel processen als user-level threads en kernel-level threads. De user-level threads vormen op gebruikersniveau de basisinterface voor parallelle toepassingen, terwijl kernel-level thrads de entiteiten zijn die kunnen worden gescheduled op een van de processoren van het systeem. Er zijn ook verschillende systeemfuncties in Solaris die gebouwd zijn op specifieke kernel-lvel threads zonder aan lichtgewicht processen gekoppeld te zijn. (bv routines voor interruptafhandeling) Indien de user-level thread niet gebonden is aan één enkel lichtgewicht proces, dan wordt die enkel in de actieve toenstand effectief aan een lichtgewicht proces gekoppeld.Wanneer deze thread de status actief verlaat, wordt de thread die daarna actief wordt gemaakt aan het vrijgekomen lichtgewichtproces gebonden. User-level threads verlaten de status actief door: Een primitieve voor synchronisatie aan te roepen, hierdoor gaat de thread in slaapstand tot wanneer de voorwaarde voor synchronisatie is voldaan. Gestopt te worden door een ander actieve user-level thread (of door zichzelf) Preëmptief onderbroken te worden wanneer een andere thread met hogere prioriteit uitvoerbaar wordt. Zichzelf preëmptief te onderbreken en de controle over te dragen aan een andere (uitvoerbare)thread met dezelfde prioriteit. In de toestand actief van de user-level thread kan ook het corresponderende lichtgewicht proces verschillende toestanden aannemen. (zie tweede deel figuur) De actieve user-level thread zal enkel worden uitgevoerd als zijn bijhorend lichtgewicht proces ook in de status actief staat. Voert die thread een blokkerende handel uit, blijft die thread actief maar is het het lichtgewicht proces dat blokkeert. 1.6. Vraag A6 Gelijktijdigheid: algoritmes van Dekker en Peterson a) Construeer het algoritme van Dekker: behandel hierbij de opeenvolgende pogingen, en hun tekortkomingen. b) Construeer, vertrekkend van het algoritme van Dekker, het algoritme van Peterson, voor twee gelijktijdige processen. c) Toon stapsgewijs aan hoe het algoritme van Peterson kan aangepast worden om in wederzijdse uitsluiting door meer dan twee gelijktijdige processen te voorzien. Analyzeer de werking van dit algoritme. a) Eerste poging Proces P0 Proces P1 … … while(1){ while(1){ while(beurt != 0){ while(beurt != 1){ } } kritieke_sectie(); kritieke_sectie(); beurt = 1; beurt = 0; niet_kritieke_sectie(); niet_kritieke_sectie(); } } Wederzijdse uitsluiting voor één gedeelde globale variabele (beurt) Principe van co-routine: ontworpen om onderling de besturing van uitvoering te kunnen uitwisselen. o processen kunnen uitsluitend om beurt hun kritieke sectie gebruiken (tempo dus bepaald door langzaamste proces) o een proces wordt permanent geblokkeerd, indien het ander proces zou crashen vooraleer de beurtvlag gewijzigd wordt o tegengehouden proces doet niets productief totdat het toestemming krijgt zijn kritieke sectie uit te voeren (verbruikt processortijd tijdens wachten) Voortdurend testen tot deze variabele een welbepaalde variabele krijgt: ‘actief wachten’ Tweede poging Proces P0 Proces P1 … … while(1){ while(1){ while(vlag[1] != 0){ while(vlag[0] != 0){ } } vlag[0] = 1; vlag[1] = 1; kritieke_sectie(); kritieke_sectie(); vlag[0] = 0; vlag[1] = 0; niet_kritieke_sectie(); niet_kritieke_sectie(); } } globale variabele (beurt) vervangen door een tabel van globale variabelen (vlag) elk proces heeft zijn eigen vlag en kan de vlag van een ander proces controleren maar niet wijzigen (vlag: in kritieke sectie of niet) een proces wordt permanent geblokkeerd, indien het ander proces zou crashen in zijn kritieke sectie niet onafhankelijk van relatieve snelheden van uitvoering van processen (tegelijkertijd vaststellen dat het ander proces zich niet in kritieke sectie bevindt: geen wederzijdse uitsluiting) Derde poging Proces P0 Proces P1 … … while(1){ while(1){ vlag[0] = 1; vlag[1] = 1 while(vlag[1] != 0){ while(vlag[0] != 0){ } } kritieke_sectie(); kritieke_sectie(); vlag[0] = 0; vlag[1] = 0; niet_kritieke_sectie(); niet_kritieke_sectie(); } } tabel van globale variabelen (vlag): vlag wordt gewijzigd vooraleer de vlag van het ander proces wordt getest. het proces blijft geblokkeerd indien het ander proces zou crashen in zijn kritieke sectie. niet onafhankelijk van relatieve snelheden van uitvoering van processen o De beslissing om de kritieke sectie te mogen betreden wordt hierdoor continu uitgesteld (actief wachten op een gebeurtenis die nooit vervuld wordt: livelock situatie (vorm van deadlock waarbij de CPU voortdurend belast wordt) Vierde poging Proces P0 Proces P1 … … while(1){ while(1){ vlag[0] = 1 vlag[1] = 1; while(vlag[1] != 0){ while(vlag[0] != 0){ vlag[0] = 0; vlag[1] = 0; sleep(random()); sleep(random()); vlag[0] = 1; vlag[1] = 1; } } kritieke_sectie(); kritieke_sectie(); vlag[0] = 0; vlag[1] = 0; niet_kritieke_sectie(); niet_kritieke_sectie(); } } elk proces stelt zijn vlag opnieuw in ten gunste van het ander proces elke verandering van de relatieve snelheid van de twee processen doorbreekt de deadlocksituatie: kans op deadlock geminimaliseerd maar niet uitgesloten Uiteindelijke oplossing Proces P0 Proces P1 … … while(1){ while(1){ vlag[0] = 1 vlag[1] = 1; while(vlag[1] != 0){ while(vlag[0] != 0){ if (beurt == 1){ if (beurt == 0){ vlag[0] = 0; vlag[1] = 0; while(beurt == 1){ while(beurt == 0){ sleep(random()); sleep(random()); } } vlag[0] = 1; vlag[1] = 1; } } } } kritieke_sectie(); kritieke_sectie(); vlag[0] = 0; vlag[1] = 0; niet_kritieke_sectie(); niet_kritieke_sectie(); } } volgorde aanbrengen in de activiteiten van de twee processen (probleem van wederzijdse beleefdheid te voorkomen) globale variabele beurt geeft aan in welke volgorde de processen het recht hebben hun kritieke sectie te mogen binnengaan moeilijk uitbreidbaar tot meer dan twee processen b) Proces P0 Proces P1 … … while(1){ while(1){ vlag[0] = 1 vlag[1] = 1; beurt = 1; beurt = 0; while(vlag[1] != 0 && beurt == 1){ while(vlag[0] != 0 && beurt == 0){ sleep(random()); sleep(random()); } } kritieke_sectie(); kritieke_sectie(); vlag[0] = 0; vlag[1] = 0; niet_kritieke_sectie(); niet_kritieke_sectie(); } } vereenvoudiging van algoritme van Peterson. wederzijdse uitsluiting behouden: proces kan zijn kritieke sectie niet uitvoeren indien de vlag van het ander proces is gezet niet mogelijk dat beide processen tegelijkertijd in de while lus geblokkeerd geraken o beide processen hebben gemeld geïnteresseerd te zijn in het uitvoeren van de kritieke sectie o beide hebben de variabele beurt gezet (diegene die dat als laatste gedaan heeft, verleent voorrang aan het ander proces) proces kan ook niet de kritieke sectie monopoliseren: het laatste proces dat de kritieke sectie uitgevoerd heeft, verleent steeds voorrang aan het ander proces indien dit zou staan te wachten c) versie van twee processen aanpassen zodat beide processen dezelfde code gebruiken n=2 while(1){ // Proces i (0 ≤ i < n) j = 1; vlag[i] = j; last[j] = i; for(k = 0; k < n; k++){ if(k == i) continue; while(vlag[k] >= vlag[i] && last[j] == i){ sleep(random()); } } kritieke_sectie(); vlag[i] = 0; niet_kritieke_sectie(); } o één enkele wachtrij: processen die hebben aangegeven de kritieke sectie te willen betreden komen in de wachtrij terecht. vlag[i]: geeft aan of proces in wachtrij is opgenomen last[j] : proces dat als laatste toegevoegd is aan de wachtrij (vroeger beurt) o elk proces dat de kritieke sectie wil betreden moet zijn positie in de wachtrij vergelijken met de positie van het enige andere proces. o eerste element in de wachtrij: proces dat de kritieke sectie mag betreden of er zich nog in bevindt. Proces is eerste element in wachtrij als: een ander proces het laatste in de wachtrij is geen enkel ander proces zich in de wachtrij bevindt uitbreid naar een willekeurig aantal (n) concurrerende processen: while(1){ // Proces i (0 ≤ i < n) for(j = 1; j < n; j++){ vlag[i] = j; last[j] = i; for(k = 0; k < n; k++){ if(k == i) continue; while(vlag[k] >= vlag[i] && last[j] == i){ sleep(random()); } } kritieke_sectie(); vlag[i] = 0; niet_kritieke_sectie(); } o variabele j laten gaan van 1 tot n: n-1 n-1 opeenvolgende wachtrijen: niveau aangegeven door j processen die aangegeven hebben de kritieke sectie te willen betreden: in wachtrij 1 eerste element van wachtrij op hoogste niveau (n-1) mag kritieke sectie betreden of bevindt er zich in. Vlag[i]: geeft voor elk proces aan in welke wachtrij het zich bevindt Last[j]: geeft voor elke wachtrij j aan welk proces als laatste de wachtrij is binnengegaan Een proces mag de wachtrij van een bepaald niveau j verlaten (indien in eerste wachtrij: kritieke sectie uitvoeren) indien: Een ander proces het laatste in de wachtrij is Geen enkel ander proces zich in de wachtrij zelf of in één van de wachtrijen van de hogere niveaus bevindt waarborgt wederzijdse uitsluiting vermijdt livelocks en deadlocks vermijdt uitstel van onbepaalde duur geen garantie dat de processen in volgorde van aanvraag de kritieke sectie kunnen betreden 2. Reeks B 2.1. Vraag B1 Gelijktijdigheid: softwarebenaderingen voor wederzijdse uitsluiting a) Construeer het algoritme van Eisenberg & McGuire. Doe dit stapsgewijs, waarbij je de bedoeling van elke regel verduidelijkt. b) Construeer het algoritme van Lamport. Doe dit stapsgewijs, waarbij je de bedoeling van elke regel verduidelijkt. c) Waarom zijn softwarebenaderingen in de praktijk niet de aangewezen technieken voor wederzijdse uitsluiting ? d) Welke andere benaderingen zijn mogelijk ? Beschrijf kort hoe wederzijdse uitsluiting met behulp van deze benaderingen kan verzekerd worden. a) while(1){ // Proces i (0 ≤ i < n) repeat{ // (4) vlag[i] = -1; for(j = beurt ; j != i ; j = (j+1)%n){ // (1) while(vlag[j] != 0){ sleep(random()); } vlag[i] = 1; for (j = 0 ; j < n ; j++){ // (2) if (vlag[j] == 1 && j != i) break; } if (j == n && vlag[beurt] == 0) // (6) beurt = i; // (3) } until (j == n && beurt == i) // (6) kritieke_sectie(); j = (j+1)%n; // (5) while (vlag[j] == 0) // (5) j = (j+1)%n; // (5) beurt = j; // (5) vlag[i] = 0; niet_kritieke_sectie(); } De concurrerende processen worden cyclisch genummerd, met als doel de processen in volgorde de kans te geven de kritieke sectie te gebruiken. Het tabelelement vlag[i] geeft voor proces i aan of het proces niet in de kritieke sectie geïnteresseerd is (waarde 0), moet wachten om de kritieke sectie te mogen betreden (waarde -1) of er toelating voor heeft (waarde 1). De identificatie van het proces dat van de kritieke sectie gebruik maakt of dit als laatste heeft gedaan wordt bijgehouden door de variabele beurt. Hoe dichter de processen zich in de cyclische rij bevinden ten opzichte van dit proces, hoe groter de prioriteit is waarmee ze behandeld zullen worden. Een proces dat in de kritieke sectie geïnteresseerd is blijft in lus (1) wachten zolang een van de processen met een hogere prioriteit de kritieke sectie wil of mag binnengaan, of er effectief gebruik van maakt. Afhankelijk van de opgetreden proceswisselingen kan het zijn dat er meerdere processen de illusie krijgen dat ze de kritieke sectie mogen betreden. Dit wordt gecontroleerd in lus (2). Als dit niet het geval is mag het als enige proces doorgaan na aanpassing van de variabele beurt. Indien meerdere processen de toelating werd gegeven, dan moet elk van deze processen de volledige procedure herhalen (lus (4) ). Na deze herhaling zullen de minder prioritaire processen opnieuw in lus (1) opgehouden worden tot het meest prioritaire proces de kritieke sectie verlaat. Op het ogenblik dat een proces van de kritieke sectie gebruik maakt, is het met zekerheid het enige proces waarvan het vlag element de waarde 1 heeft. De optimalisatie (5) in het exit protocol, die men soms toepast, laat elk proces, na beëindiging van de kritieke sectie, zelf op zoek gaan naar de beste kandidaat voor de volgende cyclus. Indien die niet gevonden wordt blijft de beurt variabele ongewijzigd. Een 2e optimalisatie, aangeduid in het vet bij (6) geeft het beurt proces een laatste kans om (eventueel opnieuw) als eerste van de kritieke sectie gebruik te maken, ook al werd de intentie hiervoor laattijdig kenbaar gemaakt. b) while(1){ // Proces i (0 ≤ i < n) vlag[i] = 1; ticket[i] = ++nummer; // (1) vlag[i] = 0; // (4) for(j = 0 ; j <n ; j++){ if(j == i) continue; while(vlag[j] != 0) sleep(random()); // (2) while(ticket[j] != 0 && ticket[j] < ticket[i]) sleep(random()); // (5) if(ticket[j] == ticket[i] && j < i) // (3) while(ticket[j] == ticket[i]) sleep(random()); } kritieke_sectie(); ticket[i] = 0; niet_kritieke_sectie(); } Elk process I dat in de kritieke sectie geinteresseerd is, verhoogt het volgnummer, en kent zichzelf een kopie ervan toe (zie (1)), in het tabelelement ticket [i]. Het volgnummer van het proces wordt pas gewist na het verlaten van de kritieke sectie. Zolang er processen in het systeem zijn met een kleiner volgnummer, moet het proces blijven wachten (lus (2)). Vandaar ook de merkwaardige naam het bakkerij algoritme. Afhankelijk van de optredende proceswisselingen, kan hetzelfde volgnummer ten onrechte toegekend worden aan meerdere processen. Om deze situatie op te vangen, is er een afspraak nodig om de dan geldende volgorde vast te leggen. In (3) gebeurt dit, bij wijze van voorbeeld, op basis van procesidentificatie. Het verhogen van het volgnummer, en het nemen van een locale kopie ervan vereist een aantal machine instructies, waarbij ondermeer geheugenelementen van en naar registers gekopieerd worden. Dankzij de processorinformatie in het PCB blok heeft elk proces trouwens de illusie van een individuele set processorregisters. Indien precies tijdens verwerking van (1) een proceswisseling uitgevoerd wordt, dan bestaat de mogelijkheid dat opeenvolgende nieuwe aanvragen beschikken over foutieve volgnummerinformatie van het onderbroken proces. zo kan zelf aan een proces met hoger volgnummer, dan dit van het onderbroken proces, toegang verleend worden tot de kritieke sectie. Indien het onderbroken proces dan uiteindelijk geactiveerd wordt, krijgt ook dit de toelating tot de kritieke sectie, en is van wederzijdse uitsluiting geen sprake meer. Om dergelijk probleem te vermijden, worden nieuwe aanvragen opgehouden (lus (5)) zolang een vorige aanvraag de berekening van zijn volgnummer niet volledig afgewerkt heeft ( (4) ). c) ze laten de verantwoordelijkheid over aan de processen o in alle oplossingen met kritieke secties moeten alle processen zich houden aan de uitgewerkte regels van het protocol o als één proces vals speelt: mislukt wederzijdse uitsluiting o programmafouten moeilijk op te sporen (resultaten meestal niet reproduceerbaar door geen strikte volgorde van gebeurtenissen) hoge overhead door principe ‘actief wachten’ o er zouden bewerkingen moeten zijn die blokkeren in plaats van processortijd te verspillen wanneer de toegang tot een kritieke sectie ontzegt wordt deadlocks door ‘actief wachten’: stel dat P0 gedurende zijn kritieke sectie onderbroken wordt door P1 (dat in een actieve wachtlus zit EN een hogere prioriteit heeft als P0) dan zou P1 niet meer geactiveerd kunnen worden. Uithongering niet uitgesloten: indien een proces de kritieke sectie verlaat, wordt er willekeurig een ander proces geselecteerd: een proces zou de toegang oneindig lang ontzegd kunnen worden d) hardwarebenaderingen o uitschakelen interupts tijdens uitvoeren kritieke sectie: tijdelijk uitschakelen interrupts (geen proceswisseling mogelijk): alleen mogelijk als alle processen dit doen (uit: CLI en aan: STI instructie op Intel) geeft gebruikersprocessen de macht het systeem te bevriezen werkt niet voor meerdere processoren o atomaire instructies: voor meerdere processoren minimum twee acties kunnen uitvoeren die niet onderbroken kunnen worden): geen enkele andere processor mag de geheugenlocatie kunnen gebruiken, vooraleer de instructie klaar is veel overhead: bij wijziging eigen cache op processor moet deze op de andere cache ook gemerkt worden (cache invalidation) testset instructies test de waarde van het één enkel argument (bezet) o 0: vervangen door 1 en 1 als resultaat geven o Niet 0: niet wijzigen en 0 als resultaat geven Via testset instructie waarde controleren; in eerste geval kan dit proces zijn kritieke sectie uitvoeren Alle andere processen gaan over tot ‘actief wachten’ Atomaire instructie: uittesten van waarde van een geheugenlocatie en het manipuleren ervan exchange instructies atomaire instructie: wisselen van inhoud van twee geheugenlocaties, waarvan de adressen als argumenten vermeld zijn gedeelde globale variabele bezet ; lokale variabelen vlagi (geïnitialiseerd op 1: geen enkel proces vooralsnog de kritieke sectie mag benutten) bezet + ∑ vlagi = n vlag met waarde 0 geeft aan welk proces in zijn kritieke sectie bevindt (alleen als bezet gelijk is aan 0: bij uitvoeren zet het bezet gelijk aan 1) verlaat een proces zijn kritieke sectie: bezet terug op 0, zijn eigen vlag op 1. wederzijdse uitsluiting voor een willekeurig aantal processen primitieven in het operating systeem o signalen ter ondersteuning van wederzijdse uitsluiting en synchronisatie koppel van twee primitieven slaap() systeemaanroep: het aanroepende proces wordt geblokkeerd tot wanneer een willekeurig ander proces het wekt met behulp van de tweede systeemaanroep. wek(proces) algoritme zie cursus p 71 deze systeemaanroepen worden geïmplementeerd als signalen: drie systeemaanroepen ter beschikking gesteld: kill(proces,signaalcode): een proces kan een ander proces asynchroon melden dat er een specifieke gebeurtenis is opgetreden (31 verschillende gebeurtenis in POSIX-compatibele implementatie) signal(signaalcode,functie): o processen kunnen hiermee signalen registreren, waarmee ze aangeven wat er gedaan moet worden als een bepaald signaal gemeld wordt. o Alle threads ontvangen hetzelfde signaal: thread kan een signaal met een bepaalde code maskeren (alleen afgeleverd aan threads die ze niet gemaskeerd hebben) Pause(): proces stopt met uitvoering tot het een willekeurig nietgenegeerd signaal ontvangt. o Vermijden van actief wachten o Sigsuspend() en sigwait(): wachten tot een specifiek signaal ontvangen wordt o sequencers en eventtellers ticketsysteem twee types objecten aanbieden: sequencers en eventtellers o sequencer: incrementele teller (werking niet verstoord door proceswisselingen) o eventteller: wachtrij van geblokkeerde processen die op een bepaalde gebeurtenis wachten en vermijdt hierbij dat het proces actief moet wachten aantal systeemaanroepen om deze objecten te manipuleren operaties await(E,x): blokkeert de thread tot de eventteller >= waarde x o advance(E): incrementeert de eventteller met 1, brengt eventuele processen uit de wachtrij waarvoor de voorwaarde eventteller >= x nu wel vervuld zou zijn, over naar gerede toestand ticket(S): geeft de huidige waarde terug van de sequencer, en verhoogt nadien de waarde zodat bij een volgende operatie read of ticket een hogere waarde wordt teruggegeven. read(S/E): geeft de huidige waarde terug van een eventteller of van een sequencer. algoritme zie cursus p 73 (eerste algoritme) semaforen variabele (semafoor) gebruiken die het aantal weksignalen voor toekomstig gebruik kan tellen (oplossing raceprobleem) houdt net als eventteller een geheel getal en een wachtrij van geblokkeerde processen bij postieve waarde: één of meer weksignalen staan te wachten op verwerking negatieve waarde: aantal elementen in de corresponderen wachtrij van geblokkeerde processen slaap() en wek() zijn nu neer() en op() enkel door primitieve operaties aangesproken worden operatie neer() op een semafoor verlaagt de waarde van de semafoor met 1 o indien waarde negatief: proces opgenomen in wachtrij van geblokkeerde processen o inspecteren, veranderen en eventueel blokkeren: atomaire instructie operatie op() verhoogt de waarde met 1 o één proces uit de wachtrij gekozen en wordt deze uit zijn geblokkeerde neer() verlost o verhogen van semafoorwaarde en wekken van één proces: atomaire instructie verschillen met sequencers en eventtellers waardoor de eenvoud ervan niet altijd benaderd kan worden op() verwijdert slechts één proces uit de wachtrij advance() een selectieve deelverzameling semaforen: niet vastgelegd in welke volgorde de processen uit de wachtrij verwijderd worden await() systeemaanroep volledige controle over sortering wachtrij o priority inversion: processen met lagere prioriteit houden processen met hogere prioriteit op o FIFO queue bij systemen zonder prioriteitsniveaus Geen manier om de huidige waarde van een semafoor te weten te komen read() systeemaanroep tellende semaforen kunnen willekeurige waarde 0 aannemen gebruikt voor synchronisatie Linux: geïmplementeerd door kernel semaphores o op() en neer(): up() en down() systeemaanroepen Solaris: o sema_init() en sema_destroy() o neer(): sema_wait() en sema_trylock() o op(): sema_post() binaire semaforen (mutexen) kunnen enkel 0 of 1 zijn gebruikt voor wederzijdse uitsluiting Linux: geïmplementeerd door spin locks o spin_unlock, spin_lock en spin_trylock macro’s Solaris: o mutex_init() en mutex_destroy() o neer(): mutex_wait() en mutex_trylock() o op(): mutex_post() 2.2. Vraag B2 Gelijktijdigheid: hardwarebenaderingen wederzijdse uitsluiting / slapende kapper probleem a) Geef drie alternatieve hardwarebenaderingen voor wederzijdse uitsluiting, en hun voor- en nadelen. Verduidelijk de gebruikte concepten (ook al worden deze in een ander hoofdstuk in de cursus behandeld). b) Waarom zijn hardwarebenaderingen in de praktijk voor gebruikersprocessen niet de aangewezen technieken om wederzijdse uitsluiting te realiseren ? Worden de hardwarebenaderingen voor wederzijdse uitsluitingen dan nooit aangewend ? c) Hoe kan vermeden worden dat gebruikersprocessen rechtstreeks een beroep doen op hardwarebenaderingen ? d) Behandel in detail het probleem van de slapende kapper. Waarvoor staat dit probleem model ? Construeer de oplossing (op basis van semaforen) stapsgewijs (± 5 stappen), waarbij je de bedoeling van elke regel (in het bijzonder van de operaties op semaforen) verduidelijkt. Waarom wordt het probleem van de slapende kapper ook soms het probleem van de onrechtvaardige kapper genoemd a) zie B1 puntje d (hardwarebenaderingen) b) zie B2 puntje c OS kan beslissen om wachtlussen te vervangen door blokkerende threads Spinlock: threadwisselingen belastend: OS kiest om gedurende korte periodes toch actief wachten toe te passen Delayed lock: indien spin lock toch langer blijkt te duren dan verwacht: toch overgegaan tot blokkeren Adaptive locks: afhankelijk van de systeembelasting resulteren in threadwisselingen of spinlocks met eventuele uitgestelde threadwisseling ze worden voorzien omdat het besturingssysteem er primitieven (in de vorm van systeemaanroepen) voor wederzijdse uitsluiting en synchronisatie op zou kunnen baseren. Ze zijn eenvoudig en makkelijk uitbreidbaar: meerdere kritieke secties kunnen ondersteund worden, door elke kritieke sectie te definiëren met een eigen variabele Corresponderende instructies zijn geprivilegieerd zodat ze enkel rechtstreeks vanuit kernelmodus of onrechtstreeks via systeemaanroepen kunnen uitgevoerd worden. c) zie B1 puntje d (primitieven in het operating systeem) d) servertoepassing die slechts één aanvraag tegelijkertijd kan behandelen, en een wachtrij met beperkte grootte in stand houdt voor volgende aanvragen 1) Toestand kapper bijgehouden door binaire semafoor kappers, zodat klanten kunnen blokkeren indien kapper niet beschikbaar is. 2) Aantal wachtende klanten bijgehouden door variabele aantal en semafoor klanten die dezelfde waarde hebben. 3) Variabele aantal bewaakt door binaire semafoor w_u om wederzijdse uitsluiting te garanderen. 4) Kapper blokkeert de semafoor klanten tot er iemand binnenkomt. 5) Indien de binnenkomende klant mag wachten voert hij een op() instructie uit op de semafoor klanten om de kapper eventueel te wekken, vooraleer zelf te blokkeren op de semafoor kappers. STOELEN = 8; void geknipt(); semafoor w_u = 1; semafoor klanten = 0; void klant(void){ semafoor kappers = 0; neer(&w_u); aantal = 0; if(aantal < STOELEN){ void kapper() { aantal++; while(1){ op(&klanten); neer(&klanten); op(&w_u); neer(&w_u); neer(&kappers); aantal--; geknipt; op(&kappers); else{ op(&w_u); op(&w_u); knippen(); ) } } } Onrechtvaardige kapper: op(): één proces uit de wachtrij gekozen en wordt deze uit zijn geblokkeerde neer() verlost , dus klanten worden niet in volgorde afgehandeld. 2.3. Vraag B3 Gelijktijdigheid: alternatieve oplossingen voor het probleem van de eindige buffer a) Formuleer het probleem van de eindige buffer (producenten en consumenten) ? Welke problemen van gelijktijdige processen moeten hier opgelost worden ? Toon aan dat het probleem niet correct kan opgelost worden door gebruik te maken van eenvoudige besturingssysteem primitieven zoals signalen. b) Geef correcte oplossingen voor het probleem van de eindige buffer: sequencers en eventtellers, semaforen, monitors, doorgeven van berichten. Construeer de oplossingen telkens stapsgewijs, waarbij je de bedoeling van elke regel (in het bijzonder van de primitieve operaties) verduidelijkt. Hoe komt het dat deze verschillende technieken allen in staat zijn om een basisoplossing van een dergelijk probleem op te leveren ? c) Wat zijn sequencers en eventtellers, en welke operaties zijn er aan geassocieerd ? Waarom is een oplossing op basis van sequencers en eventtellers, niet alleen voor het probleem van de eindige buffer maar ook algemeen, superieur ten opzichte van de andere oplossingen ? Welke problemen (beschrijf deze !) kunnen hierbij vermeden worden ? a) twee parallelle processen gebruiken gemeenschappelijk een buffer met vaste grootte N, als een circulaire opslag. o 1e proces (producent) plaatst informatie in de buffer o 2e proces (consument) haalt die informatie eruit o Twee cursors geven de indices aan van het eerste vrije element in de buffer en het eerste element dat nog verwerkt moet worden Of: verzameling van N elementen o verdeeld in twee groepen (een groep ingevulde elementen, een groep vrije elementen) o waarbij de beide processen elementen van de ene groep overbrengen naar de andere. o De producent en consument processen werken aan individueel tempo onafhankelijk van elkaar: opslaan en opvragen niet strikt afwisselend Problemen: o Wederzijdse uitsluiting van de volledige circulaire buffer zou hier tot een veel te restrictieve oplossing leiden o Consument mag geen leeg element inlezen o Producent mag geen data opslaan als de buffer vol is o Synchronisatie Dwingend opleggen van een zodanige volgorde van gebeurtenissen die door concurrente processen uitgevoerd worden, dat de integriteit van de gegevens kan gegarandeerd worden Synchronisatie tussen de processen moet verzekeren dat cruciale operaties in de juiste volgorde uitgevoerd worden Signalen: Proces producent Proces consument … … while(1){ while(1){ produceer(&p) if(aantal == 0) slaap(); if(aantal == N) slaap(); c = b[out++] b[in++] = p; out %= N in %= N; aantal--; aantal++; if (aantal + 1 == N) wek(producent); if (aantal == 1) wek(concument); consumeer(c); } } o Wordt verstoord door een raceprobleem: omdat de toegang tot de gedeelde globale variabele niet afgeschermd is Indien consument proces geblokkeerd wordt nadat het getest heeft op aantal==0, zal hij zichzelf niet meer doen inslapen. Het producent proces ontwaakt, vult de buffer, wekt de consument (maar de consument gaat direct slapen) en de producent gaat zelf slapen. Ze zijn nu alle twee aan het slapen. sequencers van eventtellers Op basis van 2 eventtellers IN(geeft cumulatief aantal elementen weer dat in de buffer werd opgeslagen) en OUT (houdt het totaal aantal verwerkte elementen bij) Proces producent Proces consument in = 0; out = 0; while(1){ while(1){ produceer(&p) await(IN, out+1); await(OUT, in + 1 – N); c = b[out%N]; b[in%N] = p; out++; in++; advance(OUT); advance(IN); consumeer(c); } } semaforen Op basis van 3 semaforen: gevuld (aantal gevulde elementen in de buffer, geïnitialiseerd op 0), leeg (aantal lege elementen in de buffer, geïnitialiseerd op N) en binaire semafoor w_u (geïnitialiseerd op 1, niet nodig als er maar 1 consument en producent zijn). Proces producenti Proces consumenti … … while(1){ while(1){ produceer(&p); neer(&gevuld); neer(&leeg); neer(&w_u); neer(&w_u); c = b[out++]; b[in++] = p; out %= N; in %= N; op(&w_u); op(&w_u); op(&leeg); op(&gevuld); consumeer(c); } } b) monitors Proces producent … while(1){ produceer(&p); pc_monitor.plaats(); } plaats(){ if(aantal == k) wait(vol); b[in++] = p; in %= k; aantal++; if(aantal == 1) signal(leeg); } doorgeven van berichten Proces producent … … while(1){ produceer(&p); receive(consument); … send(consument, &p); } Proces consument … while(1){ pc_monitor.neem(); consumeer(); } Pc_monitor neem(){ if(aantal == 0) wait(leeg); c = b[out++]; out %= k; aantal--; if(aantal + 1 == k) signal(vol); } Proces consument … for(i = 0 ; i < k ; i++) send(producent); while(1){ receive(consument, &c); … send(producent); consumeer(c); } Semaforen gebruiken atomaire instructies, die een ondeelbaar geheel vormen. Tijdens het uitvoeren van deze instructies kan het proces niet onderbroken worden. o Monitors werken intern met semaforen o Doorgeven van berichten is volkomen equivalent aan gebruik van semaforen/monitoren o Oplossing raceprobleem: gebruik van een variabele die in staat is om het aantal weksignalen voor toekomstig gebruik te tellen o Sequencers en eventtellers: verstoring van volgorde van uitvoering wordt hier vermeden door een incrementele teller aan te bieden (die slechts via systeemaanroepen gemanipuleerd kunnen worden): in de praktijk nooit toegepast in OS’s: semaforen. c) ticketsysteem twee types objecten aanbieden: sequencers en eventtellers o sequencer: incrementele teller (werking niet verstoord door proceswisselingen) o eventteller: wachtrij van geblokkeerde processen die op een bepaalde gebeurtenis wachten en vermijdt hierbij dat het proces actief moet wachten aantal systeemaanroepen om deze objecten te manipuleren operaties o await(E,x): blokkeert de thread tot de eventteller >= waarde x o advance(E): incrementeert de eventteller met 1, brengt eventuele processen uit de wachtrij waarvoor de voorwaarde eventteller >= x nu wel vervuld zou zijn, over naar gerede toestand o ticket(S): geeft de huidige waarde terug van de sequencer, en verhoogt nadien de waarde zodat bij een volgende operatie read of ticket een hogere waarde wordt teruggegeven. o read(S/E): geeft de huidige waarde terug van een eventteller of van een sequencer. algoritme zie cursus p 73 (eerste algoritme) verschillen met sequencers en eventtellers waardoor de eenvoud ervan niet altijd benaderd kan worden o op() verwijdert slechts één proces uit de wachtrij advance() een selectieve deelverzameling o semaforen: niet vastgelegd in welke volgorde de processen uit de wachtrij verwijderd worden await() systeemaanroep volledige controle over sortering wachtrij priority inversion: processen met lagere prioriteit houden processen met hogere prioriteit niet op FIFO queue bij systemen zonder prioriteitsniveaus o Geen manier om de huidige waarde van een semafoor te weten te komen read() systeemaanroep ‘actief wachten’ probleem wordt vermeden door wachtrij van geblokkeerde processen 2.4. Vraag B4 Gelijktijdigheid: het probleem van de lezers en schrijvers. a) Formuleer het probleem van de lezers en schrijvers. Waarvoor staat dit probleem model ? b) Construeer de oplossing (op basis van semaforen) stapsgewijs (± 10 stappen), waarbij je de bedoeling van elke regel (in het bijzonder van de operaties op semaforen) verduidelijkt. Zorg eerst voor een oplossing waarbij lezer processen voorrang hebben op schrijver processen. Wijzig daarna deze oplossing zodat schrijver processen voorrang krijgen. Waarom is dit noodzakelijk ? a) Model voor toegang tot een databank, met een groot aantal processen die proberen erin te lezen en te schrijven. Het is aanvaardbaar dat er meer dan één proces tegelijkertijd in de databank leest, maar als er één proces in de databank aan het schrijven is, mag geen enkel ander proces toegang krijgen. b) 1) Specifieke toegang wederzijds uitgesloten door binaire semafoor bd. 2) Aantal lezers wordt bijgehouden door een globale variabele aantal_l, die voor wederzijdse uitsluiting beschermd wordt door andere binaire semafoor wu_aantal_l. 3) We maken gebruik van turnstile constructies waar een eerste wachtende proces staat geblokkeerd op de inwendige semafoor, terwijl alle andere wachtende processen geblokkeerd staan op een uitwendige semafoor. 4) Met een eerste turnstile legt men op dat het eerste geblokkeerde proces (lezer) ook als eerste terug wordt geactiveerd 5) Proberen voorrang geven aan schrijver processen. 6) Globale variabele aantal_s houdt aantal schrijvers bij, beschermd voor wederzijdse uitsluiting door binaire semafoor wb_aantal_s. 7) Tegenhouden van lezers indien tenminste 1 schrijver toegang wil hebben tot het gegevensgebied (mbv een turnstile en semafoor lees) 8) Invoeren van andere turnstile constructie met semafoor queue zorgt ervoor dat schrijvers slechts met 1 enkele lezer proces moeten concurreren. Slechts 1 lezer kan wachten op de lees semafoor terwijl de anderen moeten wachten op de queue semafoor vooraleer ze kunnen wachten op lees. int aantal_l = 0 aantal_s = 0; semafoor wu_aantal_l = 1, wu_aantal_s = 1; semafoor db = 1, lees = 1, queue = 1; void schrijver(void){ while(1){ produceer_data(); neer(&wu_aantal_s); aantal_s++; if (aantal_s == 1) neer(&lees) op(&wu_aantal_s); neer(&db); schrijf_db(); op(&db); neer(&wu_aantal_s); aantal_s--; if(aantal_s == 0) op(&lees); op(&wu_aantal_s); }} void lezer(void){ while(1){ neer(&queue); neer(&lees); neer(&wu_aantal_l); aantal_l++; if(aantal_l == 1) neer(&db); op(&wu_aantal_l); op(&lees); op(&queue); lees(&db); neer&wu_aantal_l(); aantal_l--; if(aantal_l==0) op(&db); op(q&wu_aantal_l); verwerk_data(); } } // (3) // (2) // (1) // (1) // (2) // (3) 2.5. Vraag B5 Gelijktijdigheid: semaforen en het probleem van de dining philosophers a) Waarom worden semaforen ingevoerd ? Wat zijn semaforen, en welke operaties zijn er aan geassocieerd ? Je hoeft het probleem van de eindige buffer (producenten en consumenten) niet te behandelen. b) Welke types semaforen zijn er, en waarvoor zijn deze specifiek bestemd ? c) In welk opzicht zijn semaforen als elementaire bouwstenen inferieur ten opzichte van sequencers en eventtellers ? Welke problemen (beschrijf deze !) kunnen bij het gebruik van semaforen optreden ? d) Behandel in detail het probleem van de dining philosophers. Waarvoor staat dit probleem model ? Construeer de oplossing (op basis van semaforen) stapsgewijs (± 5 stappen), waarbij je de bedoeling van elke regel (in het bijzonder van de operaties op semaforen) verduidelijkt. a) variabele (semafoor) gebruiken die het aantal weksignalen voor toekomstig gebruik kan tellen (oplossing raceprobleem) houdt net als eventteller een geheel getal en een wachtrij van geblokkeerde processen bij o postieve waarde: één of meer weksignalen staan te wachten op verwerking o negatieve waarde: aantal elementen in de corresponderen wachtrij van geblokkeerde processen slaap() en wek() zijn nu neer() en op() enkel door primitieve operaties aangesproken worden o operatie neer() op een semafoor verlaagt de waarde van de semafoor met 1 indien waarde negatief: proces opgenomen in wachtrij van geblokkeerde processen inspecteren, veranderen en eventueel blokkeren: atomaire instructie o operatie op() verhoogt de waarde met 1 één proces uit de wachtrij gekozen en wordt deze uit zijn geblokkeerde neer() verlost verhogen van semafoorwaarde en wekken van één proces: atomaire instructie b) tellende semaforen o kunnen willekeurige waarde 0 aannemen o gebruikt voor synchronisatie o Linux: geïmplementeerd door kernel semaphores op() en neer(): up() en down() systeemaanroepen o Solaris: sema_init() en sema_destroy() neer(): sema_wait() en sema_trylock() op(): sema_post() binaire semaforen (mutexen) o kunnen enkel 0 of 1 zijn o gebruikt voor wederzijdse uitsluiting o Linux: geïmplementeerd door spin locks spin_unlock, spin_lock en spin_trylock macro’s o Solaris: mutex_init() en mutex_destroy() neer(): mutex_wait() en mutex_trylock() c) verschillen met sequencers en eventtellers waardoor de eenvoud ervan niet altijd benaderd kan worden o op() verwijdert slechts één proces uit de wachtrij advance() een selectieve deelverzameling o semaforen: niet vastgelegd in welke volgorde de processen uit de wachtrij verwijderd worden await() systeemaanroep volledige controle over sortering wachtrij priority inversion: processen met lagere prioriteit houden processen met hogere prioriteit op FIFO queue bij systemen zonder prioriteitsniveaus o Geen manier om de huidige waarde van een semafoor te weten te komen read() systeemaanroep d) probleem van de etende filosofen processen die in concurrentie met elkaar, exclusieve toegang proberen te krijgen tot een beperkt aantal verschillende bronnen. Een aantal filosofen, met evenveel vorken, zitten aan een ronde tafel. Om te eten heeft een filosoof twee vorken nodig. Elke filosoof probeer eerst de linker en dan de rechter vork te nemen. Heeft de filosoof beide vorken, dan eet hij een tijdje, legt hierna beide vorken neer om vervolgens na te denken. Deadlock: indien alle vijf de filosofen tegelijkertijd de linkervork opnemen, dan kan geen enkele meer de rechtervork krijgen Verhongering: indien de filosofen gesynchroniseerd dezelfde acties blijven ondernemen Oplossing: 1) Geen enkele vork wordt opgenomen zolang ze niet beiden beschikbaar zijn, hiervoor wendt men tabel aan van binaire semaforen, met één element per filosoof. 2) Een globale tabel toestand houdt bij wie aan het eten is, aan het denken is of de vorken poogt te nemen. 3) Een filosoof mag enkel naar de etende toestand overgaan als geen van de buurtfilosofen aan het eten is. Dit realiseert men door op de individuele semafoor van de filosoof een op() operatie uit te voeren op voorwaarde dat de noodzakelijke voorwaarden zijn vervuld. Onmiddellijk daarna wordt een neer() operatie uitgevoerd op dezelfde semafoor. Indien de bronnen beschikbaar zijn, heeft deze opvolging van semafooroperaties geen negatief gevolg: De filosoof kan eten. Als ze niet allemaal beschikbaar zijn dan wordt enkel de neer() uitgevoerd, en blokkeert de filosoof hierop. 4) Beide buren moeten na het beëindigen van hun eetperiode controleren of ze hun buren kunnen laten ontsnappen aan hun eventueel geblokkeerde toestand. 5) De binaire semafoor w_u zorgt er voor dat elke manipulatie van de gedeelde tabel toestand als een kritieke sectie beschouwd wordt. N = 5; LINKS = (i-1)%N; RECHTS = (i+1)%N; DENKT = 0; HONGER = 1; EET = 2; void test(int i){ if( toestand[i] == HONGER && toestand [LINKS] != EET && toestand [RECHTS] != EET){ toestand [i] = EET; op(&vlag[i]); } } void neem_vork(int i){ neer(&w_u); toestand[i] = HONGER; test(i); op(&w_u); neer(&vlag[i]); } void vork_neer(int i){ neer(&w_u); toestand [i] = DENKT; test(LINKS); test(RECHTS); op(&w_u); } void filosoof(int i){ while(1){ denkt(); neem_vork(i); eet(); vork_neer(i); } } 2.6. Vraag B6 Gelijktijdigheid: monitors en het liftalgoritme a) Waarom worden monitors ingevoerd ? Wat zijn monitors, en welke operaties zijn er aan geassocieerd ? b) Welke conventies voor (bepaalde) monitor primitieven zijn er ? Welke conventie volgt de Java implementatie ? Geef enkele karakteristieken van de Java implementatie. c) Geef enkele varianten van (bepaalde) monitor primitieven en hun voor- en nadelen. d) Formuleer het liftalgoritme. Waarvoor (behalve in liften) wordt dit algoritme gebruikt ? e) Construeer stapsgewijs een implementatie van het liftalgoritme, op basis van monitors. Het is hierbij de bedoeling dat je de betekenis van elke regel (in het bijzonder van de monitor primitieven) verduidelijkt. a) Monitors worden ingevoerd om het correct schrijven van programma's te vereenvoudigen, wat met semaforen niet even vanzelfsprekend is. Monitors zijn synchronisatiebouwstenen van een hoger niveau dan semaforen. Ze bestaan uit een verzameling procedures, variabelen en data-structuren die worden gegroepeerd in een module. Gebruikersprocessen kunnen procedures binen de monitor declareren, en deze procedures aanroepen, maar krijgen geen rechtstreekse toegang tot de interne datastructuren van de monitor. (conf. OGP) Het gebruik van semaforen en monitors is volledig equivalent, maar de kans dat er fouten worden gemaakt door de programmeur is veel kleiner bij monitors. Ze zijn een eenvoudig instrument voor wederzijdse uitsluiting, aangezien er maar een proces tegelijk in de monitor kan 'zitten'. De gegevensvariabelen van de monitor zijn namelijk maar voor één proces tegelijk toegankelijk. Ze gebruiken de operaties: wait() = Als het proces zichzelf wil blokkeren wanneer het in de monitor zit. signal() = notify() = Zo wordt één enkel geblokkeerd proces gewekt. dit alles met behulp van conditievariabelen, welke een wachtrij representeert van processen die geblokeerd zijn op één of andere specifieke voorwaarde. Dezen verwijzen enkel naar een wachtrij en bevatten geen tellers om weksignalen voor toekomstig gebruik te verzamelen zoals semaforen. Daarom moet een wait() voor een signal()/notify() komen, anders gaat het signaal verloren. b) Conventies Om te voorkomen dat er na het uitvoeren van een signal() of notify() operatie twee actieve processen zich tegelijkertijd in de monitor zouden bevinden, zijn er twee mogelijkheden: Signal-and-exit conventie van Brinch Hansen: Hierbij mag signal() enkel als laatste opdracht gebruikt worden in een monitorprocedure (door compiler afgedwongen), waardoor een proces dat een signal() uitvoert onmiddellijk de monitor verlaat. Notify-and-continue conventie van Hoare: Hier blijft het proces dat de notify() operatie heeft uitgevoerd in de monitor tot het vrijwillig de monitorprocedure beïndigt, of zelf een wait() instructie uitvoert. Welke conventie volgt Java? Java biedt een mechanisme aan dat zeer goed lijkt op monitors. Elk object in Java is geassocieerd met een lock. Deze kan worden beschouwd als een individuele monitor, per object. De methoden van een object negeren de lock, tenzij de methode als synchronized gedeclareerd is. Het is ook mogelijk om een deel van de code van een methode als synchronized te merken. Om deze methodes aan te roepen moet men eigenaar van de lock zijn, een thread kan tegelijkertijd eigenaar zijn van de lock voor verschillende objecten. Bij aanroep door een niet-eigenaar blokkeert de thread en komt hij in de entryset van het object.Een lock wordt pas vrijgegeven als de thread de synchronised methode verlaat. Enkele karakteristieken: Alternatieven voor notify() (ik denk toch dat dit bedoeld wordt) In Java wordt ook (en vaak) notifyall() gebruikt. Ipv één bepaalde thread te wekken, wekt notifyall() alle threads tegelijk. Voordeel: Zo kunnen de processen zelf bepalen wie van hen de meest geschikte kandidaat is om te beschikken over de lock. Bijvoorbeeld om rekening te houden met prioriteiten. Nadeel: Er zal zo veel overhead veroorzaakt worden, omdat de meeste threads repetitief zichzelf na activering onmiddellijk opnieuw moeten blokkeren. (= denderende kudde fenomeen) d) Het liftalgoritme: formulering en toepassing Dit algoritme maakt gebruik van priority wait (wanneer aangeroepen wordt een argument meegegeven dat bepalend is voor de volgorde in de wachtrij: processen met kleinste prioriteit komen eerst in aanmerking om te verdwijnen) In de lift zijn 2 aanvragen mogelijk: oproepen(extern) en aanvragen(intern) om naar een verdieping gebracht te worden. Om de verplaatsing van de lift te optimaliseren wenst men een oplossing waarbij: de lift zolang mogelijk in één richting blijft bewegen. onderweg moet worden voldaan aan alle aanvragen. de zin van de lift wordt pas omgedraaid als er geen onvoltooide aanvragen in de originele zin meer zijn. eens de beslissing wordt genomen naar een bepaalde verdieping te gaan, wordt de verplaatsing niet onderbroken. Gebruik: Dit algoritme wordt gebruikt bij schijfscheduling (is dus enorm frequent gebruikt!) Bij schijfscheduling moet de mechanische zoektijd zo klein mogelijk zijn. Het liftalgoritme wordt gebruikt voor het berekenen van schijfarmverplaatsingen. e) Het liftalgoritme: Opstelling 1) Elke aanvraag om verplaatsing creëert een proces, dat éénmalig een monitorprocedure, aanvraag(), uitvoert. Deze voert de verplaatsing (alsook openen en sluiten deuren) uit. 2) De monitor heeft 2 wachtrijen, stijgt en daalt, die aanvragen ophouden tot ze uitgevoert kunnen worden, respectievelijk tijdens een beweging in stijgende of dalende richting. 3) Onmiddellijk na het verlaten van de entryqueue komen de aanvraag() processen in één van de twee wachtrijen terecht. In welke wachtrij, wordt beslist op basis van de huidige locatie van de lift en eindbestemming. De afstand tot eindbestemming bepaalt de prioriteit waarmee de aanvraag uiteindelijk zal vervuld worden. 4) Een 2e monitorprocedure, activeer(), wordt uitgevoerd telkens de deur van de lift gesloten wordt. Deze repetitieve procedure verlost het meest prioritaire aanvraag proces uit de wachtrij, behorend bij de actuele bewegingszin en keert de bewegingszin ook om als er geen in de wachtrij zitten. 5) De variabele vrij laat na elke periode van activiteit toe om de eerste aanvraag van de wachtrijen te laten omzeilen. aanvraag(int doel){ if(!vrij){ if(niveau < doel) wait(stijgt, doel); else if(niveau > doel) wait(daalt, -doel); else{ if(zin == STIJGEND) wait(stijgt, doel); else wait(daalt, -doel); } vrij = FALSE; niveau = doel; } activeer() { vrij = TRUE; if(zin == STIJGEND){ if(Qleeg(stijgt)){ zin = DALEND; signal(daalt); else signal(stijgt); else{ if(Qleeg(daalt)){ zin = STIJGEND; signal(stijgt); else signal(daalt); } } 3) Reeks C 3.1. Vraag C1 Gelijktijdigheid: deadlocks a) Definieer het begrip deadlock. Aan welke voorwaarden moet voldaan zijn opdat een deadlock zou kunnen optreden ? b) Bespreek de strategie detectie en opheffing van deadlocks. Behandel zowel het aspect detectie, als het aspect opheffing. Verduidelijk zowel hulpmiddelen uit de grafentheorie als numerieke technieken die hierbij van dienst kunnen zijn. Bespreek eveneens de praktische implementeerbaarheid van deze strategie. c) Vermeld drie andere benaderingen om met deadlocks om te gaan. Geef hiervan telkens een korte situering, zonder echter op details in te gaan. a) Een deadlock is een verzameling processen waarin elk proces van de verzameling wacht op een gebeurtenis, die alleen een ander proces in de verzameling kan veroorzaken. Omdat alle processen aan het wachten zijn kan geen enkel proces zo’n gebeurtenis veroorzaken. Voorwaarden voor een deadlock: Wederzijdse uitsluiting: het systeem bevat bronnen die slechts door één proces kunnen gebruikt worden. Vasthouden en wachten: een proces mag toegewezen voorzieningen vasthouden terwijl het wacht op de toewijzing van andere voorzieningen Geen preëmptieve verwijderingen: Een bron kan niet gedwongen verwijderd worden van een proces dat de bron vasthoudt. Alleen het proces dat de bron vasthoudt kan vrijwillig de bron afstaan. Cyclisch wachten: er bestaat een gesloten keten van processen waarin elk proces ten minste één voorziening vasthoudt die het volgende proces in de keten nodig heeft. b) Detectie: Benaderingen voor het detecteren van deadlocks beperken noch de toegang tot voorzieningen, noch de acties van processen: indien mogelijk wordt altijd voldaan aan alle verzoeken van processen om bronnen. Het deadlockprobleem wordt maar aangepakt als het zich effectief voordoet. Het is echter niet zo eenvoudig deadlocks te detecteren. Het proces dat zich in een deadlock bevindt weet dit immers zelf niet en ook het besturingssysteem weet dit niet. Resourcegraaf: Bij elke toestand van het systeem kunnen deadlocks pas worden vastgesteld door analyse van de resourcegraaf. Een resourcegraaf toont de aanvragen en toewijzingen van processen aan bronnen bij elke toestand van het systeem. In principe moet na elke aanvraag de resourcegraaf geanalyseerd worden. Indien van elke bron maar één element beschikbaar is => wacht-op-graaf: een mogen geen cycli voorkomen, anders DEADLOCK! Indien bronnen meerdere elementen beschikbaar hebben => graafreductie: een resourcegraaf kan worden gereduceerd indien er tenminste één procesknooppunt bestaat waarvoor alle uitgaande pijlen kunnen worden omgedraaid (= alle gewenste bronnen kunnen worden toegewezen) Indien volledige reductie mogelijk is => geen deadlocksituatie Numerieke reductiemethoden: Worden uitgevoerd mbv vectoren en matrices Vectoren: - aanwezige bronnen: totaal aantal elementen van elke bron - beschikbare bronnen: aantal vrije elementen van elke bron Matrices: - Toewijzingsmatrix: geeft voor elk proces i hoeveel exemplaren van elke bron j reeds zijn toegewezen - Aanvraagmatrix: geeft voor elk proces i hoeveel exemplaren het proces nog wil van elke bron j. Algoritme voor het detecteren van deadlocks: Tijdens de uitvoering van het algoritme worden processen gemarkeerd om aan te geven dat ze geen last hebben van een deadlock. 1) zoek een niet gemarkeerd proces i waarvoor geldt dat alle elementen op de i-de rij van A kleiner zijn dan de overeenkomstige elementen van B. Indien zo’n proces er niet is stopt het algoritme. Deze stap zoekt een proces op waaraan kan worden voldaan vanuit de momenteel beschikbare bronnen, en dat tot het einde kan uitgevoerd worden. 2) tel alle elementen van de i-de rij van T op bij de overeenkomstige elementen van B. Het geselecteerde proces wordt gedraaid tot het klaar is, en geeft alle bronnen, die het in zijn bezit heeft, terug. Het proces wordt gemarkeerd als afgelopen. Op het einde van het algoritme geven ongemarkeerde processen aan dat ze in een deadlock zijn. Opheffen Het controleren op deadlocks vraagt veel overhead en bovendien is het probleem er niet mee opgelost. Er zijn een aantal manieren om deadlocks op te heffen, maar bij elke methode gaat informatie verloren. Elk proces in een deadlock, één voor één afbreken, detectie algoritme toepassen tot alle deadlocks zijn verdwenen Tijdens het uitvoeren van processen checkpoints (volledige procesbeeld) opslaan in een bestand. Bij deadlock teruggaan naar vorige toestand zonder deadlock (rollback). Het werk dat na de checkpoint is uitgevoerd gaat verloren. Preëmptieve verwijdering van bronnen tot wanneer de deadlock niet meer bestaat. Praktische implementeerbaarheid: Veel overhead! c) Deadlocks vermijden door aanvragen van bronnen te analyseren en eventueel te vermijden. 1) Toestandsdiagrammen: veilige toestanden 2) Claimgrafen 3) Resourcetrajecten Deadlocks onmogelijk maken door een van de 4 voorwaarden uit te sluiten 1) resources exclusief toegewezen aan één proces, beschermd door wederzijdse uitsluiting 2) processen houden resources vast en wachten op andere resources 3) preëmptieve verwijdering van resources 4) cyclisch wachten: cyclus keten in de resourcegraaf Kans op deadlocks minimaliseren. Het volledig uitsluiten van deadlocks vraagt meestal teveel overhead. 3.2. Vraag C2 Gelijktijdigheid: deadlocks a) Verduidelijk aan de hand van resourcetrajecten het verschil tussen de strategieën vermijden van deadlocks en onmogelijk maken van deadlocks. b) Bespreek in detail de strategie vermijden van deadlocks. Verduidelijk zowel hulpmiddelen uit de grafentheorie als numerieke technieken die hierbij van dienst kunnen zijn. Bespreek eveneens de praktische implementeerbaarheid van deze strategie. c) Bespreek de alternatieve mogelijkheden in de strategie onmogelijk maken van deadlocks. Behandel telkens in hoeverre deze praktisch kunnen verwezenlijkt worden. d) Vermeld twee andere benaderingen om met deadlocks om te gaan. Geef hiervan telkens een korte situering, zonder echter op details in te gaan. a) Vermijden Onmogelijk maken Verticale as: Voortgang van proces Q Horizontale as: Voortgang van proces P Dikke zwarte lijnen: Het pad dat uiteindelijk gevolgd wordt door de processor. (Omwille van proceswisselingen kan het pad niet gewoon horizontaal of verticaal lopen) De extra horizontale en verticale lijnen duiden op het aanvragen en vrijgeven van een bron. De gearceerde gebieden duiden de momenten aan waarop beide processen dezelfde bronnen nodig hebben. Door wederzijdse uitsluiting kunnen deze gebieden niet betreden worden. Vermijden van Deadlocks: De scheduler beslist om het zwarte gebied niet te betreden = de scheduler verwerpt het verzoek van Q om B zolang A aan P toegewezen is. Onmogelijk maken van Deadlocks: Een proces geeft zijn bron vrij alvorens een andere bron te vragen. Deadlocks zijn onmogelijk. b) Voor het ontwerp van het algoritme voor vermijding van deadlocks hebben we claimgrafen nodig. Claimgrafen zijn een bijzonder soort resourcegrafen, die ook de potentiële aanvragen weergeven. Deze extra informatie zal het besturingssysteem nodig hebben om te weten welke bronnen een proces in de toekomst zal aanvragen en gebruiken. Het belangrijkste algoritme voor het vermijden van deadlocks is gebaseerd op het begrip veilige toestand. Dit is een toestand waarin het besturingssysteem bronnen kan toewijzen zonder te moeten vrezen voor deadlocks. Om te bepalen of een toestand veilig is moet het besturingssysteem, behalve de reeds toegewezen bronnen, ook weten wat elk proces in de toekomst zou kunnen aanvragen. Deze informatie zit precies vervat in de claimgraaf op dat ogenblik. Een toestand is veilig indien de claimgraaf volledig gereduceerd kan worden. Uit deze concepten kan de volgende praktische benadering voor het vermijden van deadlocks afgeleid worden: Verzoekt een proces om een bijkomende voorziening die beschikbaar is, veronderstel dan fictief dat aan dit verzoek voldaan wordt en pas de claimgraaf aan. Controleer vervolgens via graafreductie of het resultaat een veilige toestand is. Is het resultaat een veilige toestand, voldoe dan ook effectief aan het verzoek. Indien niet, blokkeer het proces totdat veilig aan het verzoek voldaan kan worden. (= het bankiersalgoritme) Numerieke technieken: Worden uitgevoerd mbv vectoren en matrices Vectoren: - aanwezige bronnen: totaal aantal elementen van elke bron - beschikbare bronnen: aantal vrije elementen van elke bron Matrices: - Toewijzingsmatrix: geeft voor elk proces i hoeveel exemplaren van elke bron j reeds zijn toegewezen - Aanvraagmatrix: geeft voor elk proces i hoeveel exemplaren het proces nog wil van elke bron j. Zoek in de aanvraagmatrix A een rij waarvan alle elementen kleiner of gelijk zijn dan de corresponderende elementen in B. Indien deze rij niet kan gevonden worden dan komt het systeem bij een maximaal gebruik van bronnen uiteindelijk in een deadlock, omdat geen van de processen kan worden afgemaakt. Tel de overeenkomstige elementen van de toewijzingsmatrix T op bij B, en markeer het proces als beëindigd Herhaal stappen 1 en 2 tot alle processen gemarkeerd zijn als beëindigd. Is dit niet mogelijk, dan is de oorspronkelijke toestand onveilig. Praktische implementeerbaarheid: Dit wordt in praktijk in geen enkel besturingssysteem gebruikt omdat het een onhaalbare zaak is om van elk proces a priori te weten te komen welke bronnen het ooit nodig zal hebben c) Het optreden van deadlocks is onmogelijk indien aan verzoeken om bronnen en aan de gelijktijdigheid van uitvoering van processen zondanige beperkingen worden opgelegd zodat aan één van de vier noodzakelijke voorwaarden voor het optreden van deadlocks niet wordt voldaan. 1) Wederzijdse uitsluiting vermijden: Geen wederzijdse uitsluiting implementeren is in praktijk niet mogelijk. Indien bronnen niet exclusief zouden worden toegewezen, zou er chaos ontstaan. 2) Vasthouden en wachten vermijden: deadlocks kunnen onmogelijk gemaakt worden door te voorkomen dat processen, die één of ander bron in bezit hebben, op nog meer bronnen wachten. Dit kan bereikt worden door te eisen dat alle bronnen tegelijk worden aangevraagd, voor de uitvoering. Indien een van de bronnen niet beschikbaar is, wordt geen enkele bron toegewezen en moet het proces wachten. Probleem: bronnen moeten a priori bekend zijn & uithongering mogelijk. Oplossing: proces bij elke aanvraag van een nieuwe bron verplichten om de bronnen, die het reeds in bezit heeft, vrij te geven. Of twee-fasen-lock protocol: fase 1: Groeifase: alle benodigde bronnen afgaan en op slot proberen zetten. Als er een niet op slot kan gezet worden, alles vrijgeven en opnieuw beginnen fase 2: Krimpfase: wijziging aanbrengen en slot verwijderen Kan niet voor alle processen gerealiseerd worden 3) Preëmptieve verwijdering van bronnen: Het verwijderen van een bron dat door een proces wordt vastgehouden is over het algemeen nog moeilijker te realiseren. De bron kan weggehaald worden op voorwaarde dat de toestand van de bron gemakkelijk is op te slaan. Bij printers en tapedrives is dit bijvoorbeeld niet mogelijk. Indien de bron is toegewezen aan een proces dat niet geblokkeerd is, zijn aanvullende checkpoint en rollbackprocedures vereist. 4) Cyclisch wachten doorbreken: Dit is de enigste mogelijkheid die in praktijk enigszins haalbaar is. De meest voor de hand liggende oplossing is een globale nummering invoeren voor alle bronnen. Processen kunnen op iedere moment een nieuwe bron aanvragen, maar de aanvragen moeten in volgorde van de nummering van de bronnen gebeuren. Bovendien moeten verschillende instanties van eenzelfde brontype in één keer aangevraagd worden. Met deze regels kan een resourcegraaf en een wacht-op-graaf nooit een cyclus bevatten. Alle processen worden sequentieel afgewerkt. Echter INEFFICIËNT: processen worden vertraagd en onnodig de toegang geweigerd tot bronnen. Dezelfde volgorde van bronnen voor elk proces is bovendien zeker niet optimaal. d) 1) Detecteren en opheffen van deadlocks (zie C1) 2) Kans op deadlocks minimaliseren. 3.3. Vraag C3 a) b) Technologische stap: Bij de eerste 2 manieren van geheugenbeheer is het noodzakelijk dat het proces volledig in het geheugen geladen wordt. Door invoering van pagineren in segmentatie wordt het echter mogelijk enkel die pagina’s (of segmenten) in het geheugen te laden die we op dat moment nodig hebben. Dit concept noemen we virtueel geheugen. Gevolgen: Het deel dat zich op een bepaald moment in het hoofdgeheugen bevindt noemen we de residente set. Op basis van de paginatabel (of segmenttabel) kan de processor vaststellen of alle geheugenverwijzingen betrekking hebben op locaties die zich in de residente set bevinden. Wordt een logisch adres (nu virtueel adres genoemd) tegengekomen dat zich niet in de residente set bevindt, dan genereert de processor een interrupt = paginafout. Op dat moment neemt het besturingssysteem de besturing over en plaatst een I/O-verzoek om de nodige pagina’s op te halen. Indien het gewenste stuk is binnengehaald wordt een I/O-interrupt gegenereerd, die opnieuw het besturingssysteem activeert, waardoor dit de paginatabellen kan bijwerken en het betrokken proces terug in de gerede toestand kan plaatsen. Voordelen: => Verbetering van systeemgebruik door: - Meer ruimte voor processen => kans is groter dat er minstens 1 proces in de gerede toestand zal zijn - Geheugenhoeveelheid dat een proces kan aanspreken kan groter zijn dan de totale hoeveelheid beschikbare hoofdgeheugen => programmeur moet geen rekening houden met het beschikbaren geheugen, geen overlay-technieken nodig. - Geheugen wordt efficiënter benut. I/O-operaties en bijgevolg tijd bespaard omdat ongebruikte stukken niet in en uit geheugen moeten geswapt worden. Nadelen: - Heel wat overhead bij paginafouten. Bij teveel paginafouten -> trashing: processor is meer bezig met swappen, dan uitvoeren van instructies. c) Paginering vereist een weloverwogen keuze voor de paginagrootte. - Kleine pagina’s -> weinig interne fragmentatie, maar wel groot aantal pagina’s per proces -> grote paginatabellen, waardoor er minder geheugen vrij blijft voor het proces. - Grotere pagina’s geven ook een efficiëntere blokoverdracht van gegevens uit secundair geheugen. - Bij een constante hoeveelheid toegewezen geheugen heeft de paginagrootte invloed op de frequentie waarmee paginafouten optreden. Relatief kleine paginagrootte is efficiënter. (order kb in praktijk) In veel computersystemen is de virtuele adresruimte die elk proces kan aanspreken groter dan de beschikbare hoeveelheid reëel geheugen. Hierdoor wordt de hoeveelheid geheugen die per proces alleen al voor paginatabellen vereist is te groot om in het hoofdgeheugen te kunnen worden geladen. Hiervoor bestaan 2 oplossingen: 1) Paginatabellen zelf in virtueel geheugen: Hiervoor kan een systeem met 2 niveaus gebruikt worden: forward-mapped page table Het paginanummer in het logisch adres wordt opgesplitst in 2 delen: 1. meest significante deel = index voor steeds residente paginatabel, en verwijst naar specifieke paginatabel 2. minst significante deel = index specifiek paginatabel, verwijst naar fysieke frame => 2 soorten paginafouten mogelijk. Een voor binnenhalen specifieke paginatabel en een voor het binnenhalen van de procespagina 2) Geïnverteerde paginatabel voor ganse systeem ipv procestabel voor elk proces. Een geïnverteerde paginatabel bevat een ingang per geheugenframe. Grootte paginatabel is enkel afhankelijk van de grootte van het geheugen. Omdat de geïnverteerde paginatabel niet is gesorteerd op paginanummer, maar op framenummer, en om te vermijden dat het paginanummer sequentieel moet opgezocht worden, past men hashing toe. Bij virtueel geheugenbeheer wordt het paginanummer en de procesidentificatie vertaald naar een hashwaarde, die gebruikt wordt als een index in een hashtabel. Deze tabel bevat een verwijzing naar één van de elementen in de geïnverteerde paginatabel die de specifieke hashwaarde oplevert. Alle elementen met dezelfde hashwaarde zijn in de geïnverteerde paginatabel aan elkaar gekoppeld in een circulaire lijst. Het framenummer kan berekend worden op basis van het adres van het element (ten opzichte van het beginadres van de geïnverteerde paginatabel) en hoeft bijgevolg niet expliciet opgeslagen te worden. In beide technieken veroorzaakt elke verwijzing naar een virtueel adres minimaal 2 geheugentoegangen: één voor het opvragen van de juiste paginatabel en één voor het opvragen van de gewenste gegevens. Om verdubbeling van de geheugentoegangstijd te vermijden wordt een hardwarecache in de MMU gebruikt die de meest recent gebruikte paginatabelingangen bevat. d) Paginering Segmentatie - onzichtbaarheid voor programmeur - gemakkelijker- gemakkelijker afhandeling van afhandeling groeiendevan - eenvoudigere algoritmen voor structuren geheugenbeheer groeiende structuren - eliminatie externe fragmentatie Nadelen - interne fragmentatie - externe fragmentatie => voordelen samenvoegen: besturingssystemen met zowel paginering als segmentatie. In gecombineerde paginering- segmentatie virtueel geheugensystemen wordt de adresruimte van een proces opgebroken in enkele segmenten, gekozen door de programmeur. Elk segment wordt verdeeld in pagina’s met vaste grootte, gelijk aan de grootte van een frame in het hoofdgeheugen. Voordelen e) Adresstructuur: Adresvertaling: Logische of virtueel adres = Segmentnummer + segmentpositie Segmentpositie = paginanummer + paginapositie Elk proces heeft zijn eigen segmenttabel, elk processegment zijn eigen paginatabel. Seg # = index voor segmenttabel => ‘p’-bit, grootte v. segment, geheugenadres Page # + geheugenadres = paginanummer = index paginatabel => framenummer Frame # (= fysiek beginadres) + offset (= afstand van beginadres tot instructie) => instructie 3.4. Vraag C4 a) Zie C3 deel a b) Technologische stap: Om externe en interne fragmentatie zoveel mogelijk te beperken kunnen we proberen de processen op te splitsen in stukjes die vrije ruimten in het geheugen kunnen opvullen. Het geheugen wordt dus niet meer als een aaneengesloten blok in het geheugen geladen. Hoe realiseren? 1) Paginering ~ vaste partitionering: - Het hoofdgeheugen in frames verdeelt = stukken met gelijke, relatief kleine, grootte - Procesbeelden in pagina’s verdeelt ter grootte van frames. Het besturingssysteem houdt een paginatabel bij voor elk proces. Deze bevat de framelocatie voor elke pagina van het proces. 2) Segmentatie ~ dynamische partitionering Programma’s worden verdeelt in blokken, echter niet met gelijke grootte. Het besturingssysteem houdt een segmenttabel bij voor elk proces, die de fysieke locatie van elk segment van het proces bevat. Voor- en nadelen: Voordelen: - interne (paginering) en externe (segmentatie) fragmentatie kleiner - segmentatie vergemakkelijkt sharing - meer processen in geheugen Nadelen: - ingewikkeldere adresvertalingen c) Een proces kan in de loop van de tijd verschillende partities bezetten. De locaties waarnaar een proces verwijst zijn dus niet vast. Daarom wordt onderscheidt gemaakt tussen: Logische adressen ~ het geheugenbeeld dat de programmeur heeft: verwijzingen naar geheugenlocaties, onafhankelijk van de huidige toewijzing van het proces aan het geheugen. Speciaal geval zijn de relatieve adressen. Hierbij worden alle adressen uitgedrukt tov één bepaald punt, meestal het begin van het programma Fysieke adressen ~ het geheugenbeeld dat het besturingssysteem & de processor heeft: Dit zijn de werkelijke locaties in het hoofdgeheugen. Logische adressen moeten vertaald worden in fysieke adressen vooraleer geheugentoegang mogelijk is. Deze omzetting wordt geregeld door het memory management unit op elk moment dat een instructie wordt uitgevoerd. Bij paginering: Het besturingssysteem houdt een paginatabel bij voor elk proces. Elke ingang in de paginatabel bevat het nummer van het frame in het hoofdgeheugen dat de corresponderende pagina bevat. Alle pagina hebben een gelijke grootte die een macht is van 2 => logisch verband tussen logische en relatieve adressen. Logisch adres = Relatief adres = paginanummer + relatieve positie binnen pagina MMU: - linker bits van logisch adres = paginnanummer - paginanummer is index procespaginatabel => framenummer - fysiek adres = framenummer en relatieve positie samenvoegen => globale actie: tijdens instructiecyclus paginanummer veranderen in framenummer. Bij segmentatie: Het besturingssysteem houdt een segmenttabel bij voor elk proces, die de fysieke locatie van elk segment van het proces bevat. Segmenten hebben verschillende groottes => geen logisch verband tussen logische en relatieve adressen. => adresvertaling minder gemakkelijk Logische adres = segmentnummer + relatieve positie MMU: - segmentnummer uit linkerbits van logisch adres - segmentnummer = index processegmenttabel => fysiek beginadres & lengte segm. - fysiek adres = begin adres en relatieve positie optellen. De lengte van het segment moet groter zijn dan de rechterbits van het logisch adres aangeven, zoniet is het logisch adres ongeldig en wordt een interrupt gegenereerd. d) Voordelen Paginering - gemakkelijke adresvertaling - verborgen voor programmeur Nadelen - interne fragmentatie Segmentatie - blokken van verschillende grootte - sharing - lengte segmenten kan wijzigen in de loop van de uitvoering - externe fragmentatie - ingewikkeldere adresvertaling e) Administratie van geheugen gebeurt door het besturingssysteem. Er wordt bijgehouden welke geheugenpartities reeds in gebruik zijn en welke nog beschikbaar zijn. 1) Bitmaps: Het geheugen wordt verdeeld in kleine allocatie-eenheden van gelijke grootte. Bij elke allocatieeenheid hoort een bit die aangeeft of het geheugen vrij of bezet is. Niet veel gebruikt, want tijdverslindend proces bij zoeken naar aaneengeschakelde allocatieeenheden die groot genoeg zijn voor een proces. 2) Gekoppelde lijsten: Het geheugen wordt voorgesteld als een gekoppelde lijst van segmenten die ofwel in gebruik zijn door een proces ofwel vrij zijn. De meest efficiënte oplossing plaatst aan het begin en het eind van een segment informatie met de status en de grootte van het segment en neemt enkel de vrije segmenten op in een dubbelgekoppelde lijst. 3) Buddysysteem: Gehele geheugenruimte die beschikbaar voor toewijzing wordt behandeld als een blok met groot 2y. Zolang de vereiste geheugenruimte kleiner is dan de helft van de beschikbare ruimte wordt die ruimte gesplitst in 2. Zo wordt de ruimte verder gesplitst tot het kleinst mogelijke blok wordt bereikt. Het buddysysteem houdt voortdurend een gekoppelde lijst bij van alle niet toegewezen blokken. Het buddysysteem versnelt samenvoeging, omdat het alleen moet zoeken in de lijst van blokken met dezelfde grootte. Interne fragmentatie helaas zeer groot! 3.5. Vraag C5 Geheugenbeheer: besturingssysteem software. a) Welke globale taken bij geheugenbeheer moeten worden vervuld door de computerhardware en in het bijzonder door de processor? de besturingssysteemsoftware? b) Welke (zes) specifieke vragen moet besturingssysteem software beantwoorden ? Ga hierbij zoveel mogelijk in detail voor wat de invulling ervan betreft, behalve voor het probleem van vervangingsstrategieën. Bij deze laatste moet je enkel de vraagstelling formuleren. Vermeld ook steeds hoe het probleem in Unix en in Windows NT wordt aangepakt. c) Bespreek paginabuffering. Bij welke vragen in b) komt deze techniek ter sprake ? d) Bespreek de werksetbenadering. Bij welke vraag in b) komt deze techniek ter sprake ? Hoe wordt de werksetbenadering in praktijk benaderd ? a) Processor: - genereren van paginafouten Besturingssysteem: - beslissen of een pagina of segment naar het secundair geheugen moet verplaatst worden of niet en de uitwerking ervan b) 1) Wanneer moet een pagina worden overgebracht naar het hoofdgeheugen? 2 alternatieven: 1. vraagpaginering: een pagina wordt pas overgebracht naar het hoofdgeheugen als hierom gevraagd wordt => veel paginafouten bij opstarten van proces 2. prepaginering: Meerdere aaneengesloten pagina’s, zoals ze opgeslagen zijn in het secundair geheugen, worden ineens binnengehaald, ook al is maar een pagina gevraagd. Unix: vraagpaginering Windows: prepaginering, aantal pagina’s is afhankelijk van het geheugen en de aard van de pagina’s. 2) Op welke plaats in het hoofdgeheugen moeten pagina’s geladen worden? Enkel relevant bij pure segmentatiesystemen. o o o o o First-fit: zoekt vanaf het begin van het geheugen en plaatst het segment in de eerste plaatst die groot genoeg is. Next-fit: zoekt vanaf de plaats van waar het geëindigd is. Plaatst het blok in de eerst geheugenplaats die groot genoeg is. Best-fit: zoekt het hele geheugen af naar de plaats die het best past. Het geheugen wordt snel verdeeld in blokken die te klein zijn voor nieuwe processen. Worst-fit: wijst het grootst beschikbare blok toe, in de hoop veel gaten te krijgen die nog voldoende groot zijn voor nieuwe processen. Optimal-fit: Schakelt dynamisch tussen de vier basisalgoritmen. 3) Hoeveel paginaframes moeten worden toegewezen aan elk actief proces? o Hoe kleiner de hoeveelheid geheugen die wordt toegewezen aan elk proces, hoe groter het aantal processen dat kan worden geladen, hoe groter de kans dat er een proces zich in de gerede toestand bevind. o Een te klein aantal pagina’s per proces zal de frequentie van paginafouten doen toenemen. o Een overtoewijzing van geheugen aan een proces levert geen merkbare verbeteringen 2 mogelijke strategiën: 1. Vaste toewijzing: aantal pagina’s wordt toegekend bij creatie proces 2. Variabele toewijzing: paginaframes worden dynamisch toegewezen of onttrokken aan processen om de paginafoutfrequentie te verlagen. Hierbij moet echter het gedrag van de processen worden beoordeeld wat overhead veroorzaakt. Unix: variabele toewijzing Windows: variabele toewijzing 4) Lokale of globale vervanging? Wanneer we een pagina moeten vervangen, omdat een nieuwe pagina moet worden geladen zijn er 2 mogelijkheden: o o Lokale vervanging: alleen de pagina’s van het proces dat een fout veroorzaakte komen in aanmerking voor vervanging Globale vervanging: alle pagina’s in het hoofdgeheugen komen in aanmerking voor vervanging. Gebruik van paginabuffering en werksetbenadering Unix: variabele toewijzing met globale vervangingsstrategie Windows: variabele toewijzing met lokale vervanginsstrategie 5) Wanneer moet een gewijzigde pagina worden weggeschreven naar het secundair geheugen? 2 strategieën: 1. vraagopschoning: een pagina wordt alleen weggeschreven naar het secundair geheugen als deze is geselecteerd voor vervanging. 2. opschoning vooraf: pagina’s worden in batches weggeschreven ook al zijn hun paginaframes nog niet aan vervanging toe. Dit leidt tot verspilling als de kans groot is dat de pagina’s later opnieuw zullen worden gewijzigd. Daarom wordt vaak paginabuffering gebruikt. 6) Welke specifieke pagina’s in het hoofdgeheugen moeten worden vervangen wanneer een nieuwe pagina moet worden geladen? c) Parallel met de paginatabellen per proces worden op systeemniveau twee intermediaire lijsten van slachtofferframes bijgehouden: de lijst van vrije frames en de lijst van vrije frames met gewijzigde pagina’s, die als cache werken voor te vervangen pagina’s. Beslist een besturingssysteem om een pagina uit te swappen, dan wordt die pagina nog niet direct verwijderd of naar het secundaire geheugen geschreven. Enkel de ingang in de paginatabel wordt verwijderd en onderaan één van de lijsten van slachtofferframes bijgeplaatst, naar gelang de pagina gewijzigd is of niet. Moet er een nieuwe pagina geladen worden dan wordt de pagina bovenaan de lijst effectief verwijderd of vanuit het hoofdgeheugen naar het secundair geheugen verplaatst. Doordat een pagina nog even beschikbaar blijft in een intermediaire lijst kunnen we tijd besparen wanneer er toch nog naar die pagina verwezen zou worden. Komt aan bod bij vraag 4 & 5 d) Bij combinatie variabele toewijzing met lokale vervangingsstrategie (vraag 3 & 4 van b) selecteert het besturingssysteem te vervangen pagina’s uit de residente set van het actieve proces dat de fout veroorzaakt heeft. Hierdoor moeten alternatieve technieken gebruikt worden om de waarschijnlijke toekomstige behoeften, meer bepaald de ideale grootte van de residente set, van deze actieve processen in te schatten. Hiervoor gebruikt men soms de werksetbenadering. De werkset is de verzameling pagina’s die nodig is voor de uitvoering van een proces gedurende een bepaalde periode. Als die set in het hoofdgeheugen staat treden geen paginafouten op. Periodiek worden de pagina’s uit de residente set van een proces die niet voorkomen in de werkset, verwijderd. Een proces wordt enkel efficiënt uitgevoerd als de in het hoofdgeheugen residente set de werkset kan bevatten: zijn er te weinig frames, dan zullen er veel meer verwijzingen zijn die paginafouten opleveren. Indien de som van de werksetgroottes van alle processen groter is dan het totaal aantal beschikbare frames, dan treedt trashing op. Het besturingssysteem kan beslissen om een of meer processen volledig uit het geheugen te swappen. De werksetstrategie kan bijgevolg automatisch en dynamisch het aantal actieve programma’s en hun geheugenverbruik beheren, en kan bovendien trashing vermijden. Praktisch gebruik: Het nauwkeurig bepalen van de werksetgrootte vraagt veel overhead, daarom wordt de foutenfrequentie van elk proces bijgehouden: daalt deze onder een drempelwaarde dan kunnen frames van het proces aan andere processen worden toegekend. Wordt de foutenfrequentie te hoog dan heeft het proces meer frames nodig. Windows NT gebruikt een andere methode: bij elke paginafout wordt de werksetgrootte van dat proces vergroot, op voorwaarde dat een drempelwaarde niet wordt overschreden. Pas bij overschrijding leidt een paginafout tot paginavervanging - 3.6. Vraag C6 Geheugenbeheer: vervangingsstrategieën. a) Waar in de besturingssysteem software voor geheugenbeheer situeren deze vervangingsstrategieën zich ? In welke situaties (omschrijf deze !), buiten dit specifieke domein van geheugenbeheer, zijn eveneens vervangingsstrategieën noodzakelijk ? Wie zorgt in deze situaties voor de implementatie ervan ? b) Bespreek de principes van de diverse vervangingsstrategieën, met inbegrip van de eventuele varianten. c) Vergelijk en bespreek de efficiëntie van deze strategieën (onderling en met de optimale strategie), aan de hand van volgend voorbeeld: . . . Bovenaan het schema worden de achtereenvolgende pagina's vermeld die nodig zijn voor de uitvoering van een proces waaraan drie geheugenframes zijn toegekend. Bepaal voor de diverse strategieën de inhoud van de paginatabel op elk ogenblik. Duidt eveneens telkens aan wanneer paginafouten optreden. d) Welke (reële) strategie is globaal de meest efficiënte ? Is dit steeds het geval (geef eventueel een tegenvoorbeeld) ? Hoe kan deze strategie in hardware en in software (eventueel benaderend) worden geïmplementeerd ? a) Op het moment er geheugen moet vrijgemaakt worden moet een er pagina’s uit het hoofdgeheugen geswapt worden. Welke pagina’s dit best zijn wordt bepaald door de vervangingsstrategie. Ook op hoger lagen van de geheugenhiërarchie zijn vervangingstrategieën nodig. Hiervoor kan het hardwaremechanisme van de LRU gebruikt worden. b) Optimale strategie: vervangt de pagina’s waarvoor het het langst zal duren voor ze opnieuw opgevraagd zullen worden. In praktijk niet realiseerbaar. Last Recently used: vervangt de pagina in het geheugen waarnaar het langst niet verwezen is. Niet geschikt voor processen met een cyclisch verwijzingspatroon, waarbij het paginabereik groter is dan de hoeveelheid geheugen. In praktijk moeilijk te implementeren: het is in principe nodig om een gesorteerde lijst of stapel van pagina’s bij te houden, waarbij de recentst gebruikte pagina voorop staat en de minst recente achteraan. De lijst moet softwarematig bij elke verwijzing naar het geheugen aangepast worden wat veel te lang duurt. Oplossing: hardwaremechanisme Er wordt voor een machine met N paginaframes een matrix van NxN bits bijgehouden die geïnitialiseerd zijn op 0. Telkens een paginaframe X gebruikt wordt, maakt de hardware eerst in een rij X alle bits 1 en vervolgens in kolom X alle bits 0. Op elk moment geeft de binaire waarde van de rij dan aan hoe recent de overeenkomstige pagina dan gebruikt is. Niet bruikbaar op niveau van hoofdgeheugen – secundair geheugen, maar wel in hogere lagen van de geheugenhiërarchie. Not Frequently Used: Aangezien het hardwaremechanisme niet beschikbaar, wordt een licht afwijkend software-algoritme gebruikt. In plaatst van het algoritme na elke geheugenverwijzing toe te passen, wordt het slechts periodiek toegepast. Er wordt voor elke pagina een softwareteller bijgehouden. Wanneer een paginafout optreedt wordt de pagina verwijderd waarvan de teller het kleinst is. First In, First Out: behandelt de frames die zijn toegewezen aan een proces als een circulaire buffer, voegt nieuwe pagina’s toe aan het eind van de buffer en verwijdert pagina’s die zich al het langst in het geheugen bevinden. Pagina’s die langdurig nodig zijn voor het proces worden dan ook herhaaldelijk in en uit het hoofdgeheugen gepagineerd. Presteert zeer slecht! Anomalie van Belady: meer frames beschikbaar, toch meer fouten! Klokstrategie: Ook wel tweede-kans-algoritme genoemd. Aan elk frame wordt een extra usebit gekoppeld, die in de paginatabel opgeslagen wordt. ~ NFU-algoritme waarbij de teller maar uit 1 bit bestaat. De usebit wordt op 1 ingesteld bij het laden in het hoofdgeheugen en wanneer de pagina gebruikt wordt. De frames die kandidaat zijn voor vervangen worden behandeld als een circulaire buffer (FIFO) Moet een pagina vervangen worden dan zoekt het besturingssysteem adhv een pointer naar een frame waarvoor de usebit 0 is. Komt het hierbij frames tegen waar de usebit 1 is dan worden die op 0 ingesteld. Het verschil met FIFO is dat de recent geadresseerde frames worden overgeslagen. o Kan geoptimaliseerd worden door het aantal usebits te verhogen. (Linux) o Ook kan men de dirty bit bij de selectie betrekken => pagina’s die niet recent gebruikt zijn en niet gewijzigd zijn worden verwijdert. Wordt bij een eerste rondje niets gevonden dan worden de pagina’s opnieuw overlopen en wordt naar pagina’s gezocht die gewijzigd zijn maar niet recent gebruikt zijn. Ook dit rondje kan niets opleveren. => derde-kans-algoritme (Mac) o In Unix wordt nog een andere verfijning gebruikt voor het beheren van gebruikersprocessen. (Voor kernelprocessen wordt een niet-virtueel buddysysteem gebruikt.) De paginering wordt gedeeltelijk uitgevoerd door een specifiek proces, de paginadeamon. Unix houdt een dubbelgekoppelde lijst bij waarin alle vrije paginaframes staan. Bij een fout wordt het eerste element uit de lijst verwijdert en wordt de benodigde pagina in dat frame ingelezen. De paginadeamon onderzoekt regelmatig of het aantal vrije frames gelijk is aan de parameter lotsfree. Als er niet voldoende frames vrij zijn dan wordt er geswapt. Er wordt bij het swappen geen rekening gehouden met bij welk proces de pagina’s behoren => globaal vervangingsalgoritme. Omdat een volledige doorgang van het klokalgoritme vrij lang blijkt te duren wordt een klokalgoritme met 2 wijzers (pointers) gebruikt. De voorste wijzer zet de usebit op 0 terwijl de achterste pagina’s voor verwijdering selecteert c) d) LRU benadert in het voorbeeld het best de optimale strategie. LRU is echter niet geschikt voor processen met een cyclisch verwijzingspatroon en is in praktijk moeilijk te implementeren. NFU benadert LRU. FIFO is het snelste proces. Aangezien het klokalgoritme gebaseerd is op NFU en FIFO is dit globaal het meest efficiënte. Dit algoritme kan in software worden geïmplementeerd. Dit proces kan aangeroepen worden door verschillende gebeurtenissen: Op tijdbasis (bv elke seconde) Door de scheduler, indien de paginafoutenfrequentie te hoog oploopt Indien de lijst van vrije frames te klein wordt