1.
Inleiding .................................................................................. 10
1.1. 1.2. 1.3.
Operating Systems
1.3.1. 1.3.2.
1.4. 1.4.1. 1.4.2. 1.4.3.
Het operating system als een virtuele machine ............................................. 15 Het operating system als een 'Resource manager'......................................... 15
Kort historisch overzicht ................................................................16 Tijdlijn ..................................................................................................... 16 De eerste generatie ( 1945 –1955 ): vacuümbuizen ...................................... 20 De tweede generatie ( 1955 - 65 ): transistoren en batchsystemen................ 22
1.4.3.1. 1.4.3.2. 1.4.3.3.
De programmeur bedient de computer niet meer. ............................................... 22 Automatic job sequencing ................................................................................ 22 Off-line verwerking ......................................................................................... 23
1.4.4.1. 1.4.4.2. 1.4.4.3.
Spooling ........................................................................................................ 25 Batch verwerking met multiprogramming .......................................................... 26 Multitasking systemen ( time sharing ) .............................................................. 26
1.4.4.
1.4.5. 1.4.6. 1.4.7.
2.
Waarover praten we? ....................................................................10 Talen en virtuele machines.............................................................11 Twee benaderingswijzen. ...............................................................14
De derde generatie ( 1965 – 1980 ): geïntegreerde schakelingen. .................. 24
Personal computer systemen ( 1970 tot heden ). .......................................... 27 Parallelle systemen ................................................................................... 28 Verspreide systemen ................................................................................. 28
Modellen voor bedrijfssystemen .............................................. 29
2.1. 2.2. 2.3. 2.4. 2.5. 2.6.
2.6.1.
Monolitische systemen...................................................................29 Gelaagde systemen.......................................................................31 Het Client-Server model ................................................................32 Virtuele machines .........................................................................33 De systeem architectuur van een traditioneel UNIX systeem. ..............35 Systeem architectuur van WINDOWS 2000.......................................37 Algemene Architectuur .............................................................................. 37
2.6.1.1. 2.6.1.2. 2.6.1.3.
2.6.2. 2.6.3.
De Hardware Abstraction Layer................................................................... 38 De NT Kernel............................................................................................ 38
2.6.3.1.
3.
2.6.4. 2.6.5. 2.6.6. 2.6.7.
Vatbaar voor uitbreiding. ................................................................................. 37 Overdraagbaarheid ......................................................................................... 38 Betrouwbaarheid en veiligheid. ......................................................................... 38
De De NT De
Objecten........................................................................................................ 39
NT Executive ....................................................................................... 39 Native API ........................................................................................... 39 Subsystems......................................................................................... 39 WIN32 API .......................................................................................... 40
Enkele Basisconcepten............................................................. 41
3.1.
3.1.1. 3.1.2.
Interrupts....................................................................................41 Omschrijving ............................................................................................ 41 Implementatie van het interruptmechanisme................................................ 42
3.1.2.1. 3.1.2.2.
Probleem 1 .................................................................................................... 42 Probleem 2 .................................................................................................... 43
3.1.3.1. 3.1.3.2.
Interrupts en de instructiecyclus. ...................................................................... 43 Meervoudige interrupts.................................................................................... 46
3.1.5.1. 3.1.5.2. 3.1.5.3. 3.1.5.4. 3.1.5.5.
De toetsenbord hardware................................................................................. 49 De toetsenbord handler van het BIOS ............................................................... 49 De toetsenbordbuffer ...................................................................................... 50 De BIOS-toetsenbord-interrupt......................................................................... 50 Het DOS niveau .............................................................................................. 50
3.1.6.1. 3.1.6.2. 3.1.6.3.
Interrupt Dispatching ...................................................................................... 52 Hardware Interrupt Processing ......................................................................... 53 Software Interrupt Request Levels (IRQLs) ........................................................ 54
3.1.3. 3.1.4. 3.1.5.
3.1.6.
Hardware interrupts .................................................................................. 43 System Calls ............................................................................................ 47 Samenspel tussen hardware, BIOS, DOS en toepassingsprogramma’s ............. 49
Case Study : WINDOWS 2000 Trap Dispatching............................................ 51
Operating systems
1
3.1.6.4. 3.1.6.5. 3.1.6.6.
3.2.
4.
6.4.1. 6.4.2. 6.4.3. 6.4.4. 6.4.5. 6.4.6. 6.4.7.
Shell ...........................................................................................60 Pseudoparallellisme.......................................................................61 Omschrijving van het begrip 'proces' ...............................................62
4.2.1. 4.2.2. 4.2.3.
concurrentie:............................................................................................ 63 competitie: .............................................................................................. 63 synchronisatie: ......................................................................................... 64
4.3.1. 4.3.2.
Processen in UNIX..................................................................................... 64 Processen in MS – DOS.............................................................................. 67
4.3.
4.4.
4.4.1.
4.5.
*.COM bestanden ........................................................................................... 67 *.EXE bestanden ............................................................................................ 68 Het Program Segment Prefix ............................................................................ 68 Stub.............................................................................................................. 68 TSR programma’s ........................................................................................... 69
Procestoestanden en transities in UNIX........................................................ 71
Threads.......................................................................................73 Multitasking in Windows 95 ........................................................................ 76
4.6.1.
Implementatie van processen in WIN2000 ................................................... 79
4.6.
Implementatie van processen.........................................................77
4.6.1.1. 4.6.1.2. 4.6.1.3. 4.6.1.4. 4.6.1.5. 4.6.1.6. 4.6.1.7.
Data Structures .............................................................................................. 79 Performance Counters ..................................................................................... 82 Relevant Functions.......................................................................................... 83 Relevant Tools................................................................................................ 83 Samenvatting................................................................................................. 84 Flow of CreateProcess ..................................................................................... 86 Samenvatting................................................................................................. 87
7.
Probleemstelling ...........................................................................88 Kritische secties en mutual exclusion ...............................................91 Wederzijdse uitsluiting met busy waiting. .........................................92
5.3.1. 5.3.2. 5.3.3. 5.3.4. 5.3.5.
Interrupts uitschakelen .............................................................................. 92 Reservatie van de gemeenschappelijke resources ........................................ 93 Strikt om beurten ..................................................................................... 94 De oplossing van Peterson ......................................................................... 94 Test and Set Lock ( TSL )........................................................................... 96
5.4.1. 5.4.2. 5.4.3. 5.4.4. 5.4.5. 5.4.6.
Slaap en wek............................................................................................ 97 Semaforen ............................................................................................... 99 Eventtellers ............................................................................................ 101 Het doorgeven van berichten.................................................................... 102 Het gebruik van semaforen om messaging te implementeren. ...................... 104 Het probleem van de dining philosophers ................................................... 107
5.4.
Wederzijdse uitsluiting door blokkeren van processen. .......................97
Round Robin........................................................................................... 111 Priority Scheduling .................................................................................. 112 Shortest Job First.................................................................................... 113 Scheduling op twee niveaus ..................................................................... 114 Beleid versus mechanisme ....................................................................... 115
6.3.1. 6.3.2.
Beschrijving ........................................................................................... 115 Code van de MINIX scheduler ................................................................... 116
6.3.
De MINIX-scheduler .................................................................... 115
Operating systems
2
Voluntary Switch .......................................................................................... Preemption .................................................................................................. Quantum End ............................................................................................... Termination .................................................................................................
130 131 132 132
6.4.10.1. 6.4.10.2. 6.4.10.3. 6.4.10.4.
Priority Boosting After I/O Completion ............................................................. Boosts After Waiting for Events and Semaphores .............................................. Priority Boosts for Foreground Threads After Waits............................................ Priority Boosts for CPU Starvation ...................................................................
134 134 135 135
Scheduling Scenarios............................................................................... 130
Invoer en uitvoer ................................................................... 137
8.
I/O Hardware ............................................................................. 137
7.1.1. 7.1.2. 7.1.3.
Blok en karakter devices .......................................................................... 137 Device controllers ................................................................................... 137 Direct Memory Access ( DMA ).................................................................. 140
7.2.1.
Device drivers ........................................................................................ 144
7.3.1. 7.3.2. 7.3.3.
Design Goals .......................................................................................... 146 I/O System Components.......................................................................... 146 Device Drivers in Windows 2000 ............................................................... 148
7.3.
I/O Software .............................................................................. 142 I/O System in Windows 2000 ....................................................... 146
7.3.3.1. 7.3.3.2.
8.1.1. 8.1.2.
Systemen zonder swapping .......................................................... 155 Monoprogramming zonder swapping ......................................................... 155 Multiprogramming................................................................................... 156
8.1.2.1. 8.1.2.2.
8.2.
8.2.1.
8.3.
8.3.1. 8.3.2. 8.3.3. 8.3.4.
Multiprogramming met variabele partities .................................................. 159 Memory management met behulp van bit maps................................................ 160 Memory management met gelinkte lijsten........................................................ 160 Memory management met het buddy-systeem. ................................................ 162
Virtueel geheugen....................................................................... 164 Paginatie ............................................................................................... 164 Paginatabellen ........................................................................................ 167 Paginatabellen met meerdere niveaus ....................................................... 168 Het vervangen van pagina's bij een page fault............................................ 169
8.3.4.1. 8.3.4.2. 8.3.4.3.
8.3.5.
De toekenning van geheugenruimte aan processen. .......................................... 156 Relocatie en protectie. ................................................................................... 157
Swapping .................................................................................. 159
8.2.1.1. 8.2.1.2. 8.2.1.3.
9.
Types of Device Drivers ................................................................................. 148 Structure of a Driver ..................................................................................... 152
Geheugenbeheer.................................................................... 155
8.1.
Probleemstelling ......................................................................... 110 Implementaties .......................................................................... 110
6.2.1. 6.2.2. 6.2.3. 6.2.4. 6.2.5.
6.4.8.1. 6.4.8.2. 6.4.8.3. 6.4.8.4.
7.2.
Process Scheduling ................................................................ 110
6.1. 6.2.
Quantum Accounting ..................................................................................... 127 Controlling the Quantum................................................................................ 128
7.1.
Interproces Communicatie (IPC) ............................................. 88
5.1. 5.2. 5.3.
Overview of Windows 2000 Scheduling ...................................................... 120 Priority Levels......................................................................................... 121 Relevant Tools ........................................................................................ 124 Real-Time Priorities ................................................................................. 124 Interrupt Levels vs. Priority Levels ............................................................ 125 Thread States......................................................................................... 126 Quantum ............................................................................................... 127
6.4.9. Context Switching ................................................................................... 133 6.4.10. Priority Boosts .................................................................................... 133
Toestanden van processen .............................................................70
4.5.1.
Thread Scheduling in Windows 2000.............................................. 120
6.4.7.1. 6.4.7.2.
6.4.8.
Proces hiërarchie ..........................................................................64
4.3.2.1. 4.3.2.2. 4.3.2.3. 4.3.2.4. 4.3.2.5.
6.
6.4.
Processen ................................................................................ 61
4.1. 4.2.
5.
Windows 2000 and Real-Time Processing........................................................... 56 Software Interrupts......................................................................................... 56 Exception Dispatching ..................................................................................... 57
Not-Recently-Used algoritme (NRU) ................................................................ 169 First-In, First-Out algoritme (FIFO) ................................................................. 170 Least-Recently-Used algoritme (LRU) .............................................................. 170
Segmentering......................................................................................... 171
Bestandsystemen................................................................... 174
9.1.
9.1.1. 9.1.2. 9.1.3.
Bestanden ................................................................................. 174
Naamgeving ........................................................................................... 175 Bestandsstructuren ................................................................................. 176 Toegang tot files ..................................................................................... 176
Operating systems
3
9.1.4. 9.1.5.
Attributen .............................................................................................. 177 Bewerkingen op bestanden ...................................................................... 178
9.2.1. 9.2.2.
Hiërarchische directory systemen.............................................................. 178 Padnamen.............................................................................................. 180
9.3.1. 9.3.2. 9.3.3. 9.3.4. 9.3.5.
Bijhouden op disk ................................................................................... 180 De 'boekhouding' van bestanden............................................................... 181 Gelinkte lijsten op disk ............................................................................ 181 Gelinkte lijsten in het interne geheugen ..................................................... 182 Werken met i-nodes ................................................................................ 183
9.2.
9.3.
Directories ................................................................................. 178 Implementatie van file systems .................................................... 180
Operating systems
4
Operating systems
5
Operating systems
6
Operating systems
7
Operating systems
8
Operating systems
9
1.
Inleiding 1.1.
1.2.
Waarover praten we?
Wanneer we het hebben over 'bedrijfssystemen', kunnen we voorlopig de volgende eenvoudige omschrijving hanteren:
Een bedrijfssysteem is de verzameling software die een computer operationeel maakt.
Talen en virtuele machines
Er is een belangrijk verband tussen een taal en een virtuele machine. Elke machine heeft een machinetaal, bestaande uit alle instructies die de machine kan uitvoeren. Het komt erop neer dat een machine een taal definieert. Op dezelfde manier definieert een taal een machine – namelijk de machine die alle in de taal geschreven programma’s kan uitvoeren. De door een bepaalde taal gedefinieerde machine kan natuurlijk enorm ingewikkeld en duur zijn om rechtstreeks uit elektronische schakelingen op te bouwen, maar we kunnen ons die machine toch voorstellen. Een machine met C, Pascal of COBOL als machinetaal zou werkelijk zeer complex zijn, maar is zeker denkbaar. virtuele computer Mn met machinetaal Tn
Een computer is slechts operationeel of bedrijfsklaar wanneer er in zijn intern geheugen instructies zitten die precies zeggen hoe alle componenten functioneren en samenwerken. Zonder deze instructies is de hardware een dood ding. Het toevoegen van een bedrijfssysteem wordt dikwijls weergegeven zoals in de afbeelding. Door het laden van zo een bedrijfssysteem wordt van de hardware een virtuele machine gemaakt. Virtueel in die zin dat de hardware, uitgebreid met het O.S. wel degelijk een ander toestel vertegenwoordigt, niettegenstaande er aan de fysische eigenschappen (grootte, gewicht, kleur,...) niets veranderd is.
Hardware
bedrijfssysteem
Een computer met n niveaus kan worden beschouwd als n verschillende virtuele machines, elk met een andere machinetaal. Alleen programma’s geschreven in taal T1 kunnen rechtstreeks door de elektronische schakelingen worden uitgevoerd. De eerste methode om een programma geschreven in T2 uit te voeren is daarin elke instructie te vervangen door een equivalente serie instructies in T1. Het resulterende programma bestaat geheel uit T1 instructies. De computer voert het nieuwe T1-programma dan uit in plaats van het oude T2programma. Deze techniek wordt vertaling genoemd.
virtuele computer M4 met machinetaal T4
Figuur 1-1
Hardware
bedrijfssysteem
Gebruikers werken met een nog 'grotere' virtuele machine. Rond de laag van het bedrijfssysteem wordt een volgende laag programmatuur toegevoegd: de gebruikerssoftware. Naargelang de toepassing is de virtuele machine uit de afbeelding nu een tekstverwerker, een beeldverwerker of wat dan ook geworden.
virtuele computer M3 met machinetaal T3
virtuele computer M2 met machinetaal T2
De meeste moderne computers bestaan uit meerdere niveaus. Het is in het geheel niet ongebruikelijk dat een machine zes niveaus heeft.
In figuur 1-2 staat hiervan een andere, meer systematische manier weergeven.
Gebruikerssoftware
Niveau 0 is de werkelijke hardware van de machine. De schakelingen daarin voeren de programma’s in de machinetaal van niveau 1 uit. Op dit laagste niveau, dat van de digitale logica, zijn poorten de interessantste objecten.
Figuur 1-2
Operating systems
Een andere techniek is een programma in T1 te schrijven dat programma’s in T2 als invoerdata accepteert en ze uitvoert door de instructies een voor een te analyseren en de equivalente serie T1-instructies uit te voeren. Het is dan niet nodig om eerst een nieuw programma in T1 te genereren. Deze techniek wordt interpretatie genoemd. Het programma dat dit doet heet dan een interpretator.
10
Operating systems
echte computer M1 met machinetaal T1
Figuur 1-3
11
Het volgende niveau is niveau 1, dat het echte machinetaalniveau is. In tegenstelling tot niveau 0, waar er geen sprake is van een programma in de vorm van een serie uit te voeren instructies, bestaat er op niveau 1 wel degelijk een programma, dat een microprogramma wordt genoemd en dat tot taak heeft de instructies van niveau 2 te interpreteren. Elke machine van niveau 1 heeft één of meer microprogramma’s die erop kunnen lopen. Elk microprogramma definieert impliciet een taal van niveau 2, en dus een virtuele machine die deze taal als machinetaal heeft. Dit niveau wordt het conventionele machineniveau genoemd.
niveau 5
probleemgerichte taal
vertaling ( compiler ) niveau 4
Het derde niveau is meestal een gemengd niveau. De meeste instructies in de taal komen ook voor in de taal van niveau 2, meestal uitgebreid met een verzameling nieuwe instructies, een andere geheugenorganisatie, de mogelijkheid om twee programma’s parallel uit te voeren en een aantal andere mogelijkheden. De nieuwe faciliteiten die op niveau 3 zijn toegevoegd worden uitgevoerd door een interpretator die loopt op niveau 2. Deze interpretator wordt van oudsher een besturingssysteem of operating system genoemd. De instructies van niveau 3 die gelijk zijn aan die op niveau 2 worden rechtstreeks door het microprogramma uitgevoerd, niet door het besturingssysteem. Niveau 4, het assembleertaalniveau, is een feite een symbolische vorm voor een van de talen op lager niveau. Dit niveau levert een methode waarmee mensen programma’s kunnen schrijven voor ede niveaus 1, 2 en 3 in een vorm die niet zo onprettig is als de taal van de virtuele machines zelf. Programma’s in assembleertaal worden eerst vertaald naar een taal van niveau 1, 2 of 3 en dan geïnterpreteerd door de passende virtuele of feitelijke machine. Het programma dat deze vertaling uitvoert wordt een assembler genoemd. Niveau 5 bestaat uit talen die bedoeld zijn om gebruikt te worden door applicatieprogrammeurs die een probleem willen oplossen. Zulke talen worden hogere talen genoemd. Er bestaan honderden verschillende talen: BASIC, COBOL, FORTRAN, PASCAL, C … In deze talen geschreven programma’s worden meestal naar niveau 3 of 4 vertaald door vertalers die bekend staan als compilers
De sleutel tot goed begrip van computers is dat ze zijn opgebouwd uit een serie niveaus elk gebouwd op zijn voorganger.
assembleertaal
vertaling ( assembler )
niveau 3
besturingssysteemmachine
gedeeltelijke interpretatie
niveau 2
conventionele machine
interpretatie ( microprogrammering ) niveau 1
microprogrammering
microprogramma’s worden rechtstreeks door de hardware uitgevoerd niveau 0
digitale logica
Elk niveau representeert een aparte abstractie waarin verschillende objecten en verschillende operaties beschikbaar zijn.
Figuur 1-4
Operating systems
12
Operating systems
13
1.3.
Het is niet zo eenvoudig om juist te bepalen wat een O.S is en welke taken er expliciet bij horen. Niettemin wordt dit meer en meer belangrijk. Tegen Microsoft lopen bijvoorbeeld verschillende rechtszaken omdat het gerecht ervan uit gaat dat MS teveel functionaliteit in Windows vervat en op die manier aan oneerlijke concurrentie doet.
Twee benaderingswijzen.
Een computer systeem kan ruwweg verdeeld worden in 4 componenten: o o o o
1.3.1.
De hardware Het Operating system De toepassingprogramma’s De gebruikers
Een operating system is een belangrijk onderdeel van vrijwel elk computer systeem.
Gebruiker 1
compiler
Gebruiker 2
assembler
Een bedrijfssysteem kan bekeken worden, vertrekkende van de visie van een gebruiker: we bekijken de virtuele machine uit figuur 1-1 van buitenaf. Een computer is een héél ingewikkeld ding, veel te ingewikkeld voor een menselijke gebruiker. Het zou dan ook compleet onmogelijk zijn zo'n toestel te bedienen als men geconfronteerd werd met de inwendige werking. Willen we toch dat een computer bruikbaar is, dan moet die interne werking volledig afgeschermd worden, anders gezegd: mensen moeten geconfronteerd worden met de abstractie van de computer. Het is de taak van het O.S. die abstractie te verzorgen.
Gebruiker n
text editor
Het operating system als een virtuele machine
database
1.3.2.
Het operating system als een 'Resource manager'
Het hierboven beschreven concepuele model, waarbij het operating system hoofdzakelijk wordt gezien als software die aan de gebruiker een geschikte interface biedt, is een top-down visie. Een andere manier om het operating system te beschouwen is de bottom-up methode. We bekijken het O.S. vertrekkende van binnenuit.
Operating system
We zien dan dat een werkende computer gevormd wordt door een uitgebalanceerde samenwerking tussen alle onderdelen en randapparaten.
Computer hardware
Figuur 1-5 De hardware – de CPU, het geheugen en de I/O devices - vormen de basis van computer diensten. De toepassingsprogramma’s – tekstverwerkers, rekenbladen edm. – bepalen de manier waarop de gebruikers deze computer diensten kan gebruiken om zijn “problemen” op te lossen. Het operating system controleert en coördineert het gebruik van de hardware tussen de verschillende toepassingen en hun gebruikers. We kunnen het O.S dan ook vergelijken met een staatsoverheid. Zoals elke overheid vervult het geen bruikbare functie van zichzelf.
Als een computer meer gebruikers heeft wordt de noodzaak tot beheer en bescherming van het geheugen, I/O resources en andere onderdelen zelfs nog duidelijker. Dit is noodzakelijk omdat gebruikers regelmatig (dure) onderdelen zoals magneetband-drives en dergelijke gemeenschappelijk moeten gebruiken. Het bijhouden van wie welke resource gebruikt, het toewijzen en vrijgeven van resources en het vervullen van een bemiddelende rol indien verzoeken van gebruikers of programma’s botsen zal dan ook de taak van het O.S. zijn. In deze optiek wordt een bedrijfssysteem daarom ook een resource manager genoemd. Het is de tweede benaderingswijze die in deze cursus meestal aan bod zal komen omdat het op die manier achteraf veel eenvoudiger wordt een willekeurig concreet systemen te leren gebruiken.
Het verschaft een omgeving waarin andere programma’s nuttig werk kunnen doen.
Operating systems
14
Operating systems
15
Circa 1800 AD Charles Stanhope invents the Stanhope Demonstrator
1.4.
Kort historisch overzicht
1822 AD Charles Babbage's Difference Engine
De bedoeling is enkele stappen te bekijken in de evolutie van computersystemen omdat een aantal hedendaagse aspecten beter begrepen kunnen worden tegen hun historische achtergrond.
1829 AD Sir Charles Wheatstone invents the accordion 1829 AD The first American typewriter patent 1830 AD Charles Babbage's Analytical Engine
1.4.1.
Tijdlijn
1834 AD Georg and Edward Scheutz's Difference Engine 1834 AD Tally sticks: The hidden dangers
350 Million Years BC The first tetrapods leave the oceans 1837 AD Samuel Morse invents the electric telegraph 30,000 BC to 20,000 BC Carving notches into bones 1847 AD to 1854 AD George Boole invents Boolean Algebra 8500 BC Bone carved with prime numbers discovered 1857 AD Sir Charles Wheatstone uses paper tape to store data 1900 BC to 1800 BC The first place-value number system 1860 AD Sir Joseph Wilson Swan's first experimental light bulb 1000 BC to 500 BC The invention of the abacus 1865 AD Lewis Carroll writes Alice's Adventures in Wonderland 383 BC to 322 BC Aristotle and the Tree of Porphyry 1867 AD The first commercial typewriter 300 BC to 600 AD The first use of zero and negative numbers 1868 AD First skeletons of Cro-Magnon man discovered 1274 AD Ramon Lull's Ars Magna 1869 AD William Stanley Jevons invents the Jevons' Logic Machine 1285 AD to 1349 AD William of Ockham's logical transformations 1872 AD Lewis Carroll writes Through the Looking-Glass 1434 AD The first self-striking water clock Circa 1874 AD The Sholes keyboard 1500 AD Leonardo da Vinci's mechanical calculator 1876 AD Lewis Carroll writes The Hunting of the Snark 1600 AD John Napier and Napier's Bones 1876 AD George Barnard Grant's Difference Engine 1621 AD The invention of the slide rule 1878 AD The first true incandescent light bulb 1625 AD Wilhelm Schickard's mechanical calculator 1878 AD The first shift-key typewriter 1640 AD Blaise Pascal's Arithmetic Machine 1879 AD Robert Harley publishes article on the Stanhope Demonstrator 1658 AD Pascal creates a scandle 1880 AD The invention of the Baudot Code 1670 AD Gottfried von Leibniz's Step Reckoner 1881 AD Allan Marquand's rectangular logic diagrams 1714 AD The first English typewriter patent 1881 AD Allan Marquand invents the Marquand Logic Machine 1726 AD Jonathan Swift writes Gulliver's Travels 1883 AD Thomas Alva Edison discovers the Edison Effect 1735 AD The Billiard cue is invented 1886 AD Lewis Carroll writes The game of Logic 1761 AD Leonhard Euler's geometric system for problems in class logic 1886 AD Charles Pierce links Boolean algebra to circuits based on switches 1800 AD Jacquard's punched cards 1890 AD John Venn invents Venn Diagrams
Operating systems
16
Operating systems
17
1890 AD Herman Hollerith's tabulating machines
1957 AD IBM 610 Auto-Point Computer
Circa 1900 AD John Ambrose Fleming invents the vacuum tube
1958 AD First integrated circuit
1902 AD The first teleprinters
1962 AD First field-effect transistor
1906 AD Lee de Forest invents the Triode
1962 AD The "worst" computer bug
1921 AD Karel Capek's R.U.R. (Rossum's Universal Robots)
1963 AD MIT's LINC Computer
1926 AD First patent for a semiconductor transistor
1970 AD First static and dynamic RAMs
1927 AD Vannevar Bush's Differential Analyser
1971 AD CTC's Datapoint 2200 Computer
Circa 1936 AD The Dvorak keyboard
1971 AD The Kenbak-1 Computer
1936 AD Benjamin Burack constructs the first electrical logic machine
1971 AD The first microprocessor: the 4004
1937 AD George Robert Stibitz's Complex Number Calculator
1972 AD The 8008 microprocessor
1937 AD Alan Turing invents the Turing Machine
1973 AD The Xerox Alto Computer
1938 AD Claude Shannon's master's Thesis
1973 AD The Micral microcomputer
1939 AD John Vincent Atanasoff's special-purpose electronic digital computer
1973 AD The Scelbi-8H microcomputer
1939 AD to 1944 AD Howard Aiken's Harvard Mark I (the IBM ASCC)
1974 AD The 8080 microprocessor
1940 AD The first example of remote computing
1974 AD The 6800 microprocessor
1941 AD Konrad Zuse and his Z1, Z3, and Z4
1974 AD The Mark-8 microcomputer
1943 AD Alan Turing and COLOSSUS
1975 AD The 6502 microprocessor
1943 AD to 1946 AD The first general-purpose electronic computer -- ENIAC
1975 AD The Altair 8800 microcomputer
1944 AD to 1952 AD The first stored program computer -- EDVAC
1975 AD Bill Gates and Paul Allen found Microsoft
1945 AD The "first" computer bug
1975 AD The KIM-1 microcomputer
1945 AD Johann (John) Von Neumann writes the "First Draft"
1975 AD The Sphere 1 microcomputer
1947 AD First point-contact transistor
1976 AD The Z80 microprocessor
1948 AD to 1951 AD The first commercial computer -- UNIVAC
1976 AD The Apple I and Apple II microcomputers
1949 AD EDSAC performs it's first calculation
1977 AD The Commodore PET microcomputer
1949 AD The first assembler -- "Initial Orders"
1977 AD The TRS-80 microcomputer
Circa 1950 AD Maurice Karnaugh invents Karnaugh Maps
1979 AD The VisiCalc spreadsheet program
1950 AD First bipolar junction transistor
1979 AD ADA programming language is named after Ada Lovelace
1952 AD G.W.A. Dummer conceives integrated circuits
1980 AD Danny Cohen writes "On Holy Wars and a Plea for Peace"
1953 AD First TV Dinner
1981 AD The first IBM PC
Operating systems
18
Operating systems
19
taak op een automatische manier zou kunnen laten verlopen. Dat invoeren van het programma in het geheugen, het laten uitvoeren en de constructie ervan gebeurde door één en dezelfde persoon. Een programmeur was dan ook iemand die volledig vertrouwd was met de hardware van de betreffende machine.
1997 AD The first Beboputer Virtual Computer
1.4.2. De eerste generatie ( 1945 –1955 ): vacuümbuizen De prikkel voor de elektronische computer was de tweede wereldoorlog. De Duitse generaals in Berlijn zonden commando’s via radio naar de duikboten. De Engelsen konden die commando’s onderscheppen en deden dat ook. Het probleem was dat deze berichten gecodeerd waren met een apparaat dat Enigma heette. Om een gecodeerd bericht te ontcijferen was er een enorme hoeveelheid rekenwerk nodig. Wilde dit rekenwerk nog enig nut afwerpen dan moest het zeer snel na het onderscheppen van het bericht worden uitgevoerd. Om deze berichten te ontcijferen richtte de Engelse regering een laboratorium op, onder leiding van de beroemde wiskundige Alan Turing, dat een elektronische computer bouwde: de Colossus. In het midden van de jaren veertig slaagden verschillende onderzoekers aan Amerikaanse universiteiten erin allemaal machines te maken die met vacuümbuizen werkten. Eén van hen was John Von Neumann die een ontwerp voor een “moderne computer” bedacht dat bekend staat als de Von-Neumann-machine, en nog steeds, zelfs een halve eeuw later, de grondslag is voor bijna alle digitale computers.
Centrale Verwerkings Eenheid (CVE)
besturing
Invoer
Samen met de ontwikkeling van nieuwe hardware (CPU's, geheugen, kaartlezers, tape-recorders, regeldrukkers, ...) ontstonden de eerste 'hogere' programmeertalen zoals COBOL en FORTRAN. Om de verdere evoluties beter te kunnen situeren is het misschien nuttig eens het verloop van een werksessie te bekijken: het laten uitvoeren van een FORTRAN programma. Het ontwikkelen zelf van het programma laten we hier even buiten beschouwing en we vertrekken op het ogenblik dat het programma via een speciaal daarvoor ontworpen machine reeds op ponskaarten is gezet. o
De operator/programmeur neemt de tape met de FORTRAN compiler, monteert deze op de tape drive en laadt de compiler in het geheugen van de computer.
o
Het FORTRAN programma (op kaart) wordt gelezen door een kaartlezer en op tape gezet.
o
Deze tape dient als input voor de FORTRAN compiler. Deze produceert code van het programma in assembler.
o
De operator/programmeur neemt de tape met de assembler, monteert hem en laadt de assembler in het geheugen.
o
Het programma in assembler dient nu als input voor de assembler, die de uiteindelijke objectcode van het programma produceert.
o
Deze code wordt gelinkt met de nodige bibliotheekroutines (die staan ook ergens op een of andere tape): het programma staat nu in uitvoerbare vorm op tape.
o
Het programma wordt in het geheugen geladen en uitgevoerd door de computer.
aritmetiek en logica
Geheugen
Let wel: dit scenario wordt gevolgd bij een foutvrij programma! Bij het ontdekken van een fout worden deze werkzaamheden telkens onderbroken en na het corrigeren van de fout opnieuw gestart.
Uitvoer
We zien dat de tijd nodig om een job voor te bereiden (set-up time) en te begeleiden zeer groot is in vergelijking met de werkelijke computertijd. In gans het gebeuren is de CPU , het duurste onderdeel, het meeste van de tijd werkeloos.
Figuur 1-6
Bij deze allereerste computers -gebaseerd op een vacuümbuizentechnologie was het gebruikersprogramma dat uitgevoerd werd verantwoordelijk voor de werking van het ganse computersysteem. Die programma's moesten manueel, instructie per instructie in het geheugen geplaatst worden: er was immers niets aanwezig dat die Operating systems
20
Operating systems
21
1.4.3. De tweede generatie ( 1955 - 65 ): transistoren en batchsystemen. Met de invoering van de transistor in het midden van de jaren vijftig veranderde het toneel volledig. Computers werden betrouwbaar genoeg om te kunnen worden geproduceerd en verkocht aan betalende klanten met de verwachting dat ze lang genoeg zouden functioneren om er iets nuttig mee te kunnen doen. Gezien de hoge kosten van de apparatuur is het niet verbazend te merken dat men al spoedig zocht naar manieren om tijdverlies tegen te gaan. Enkele evoluties markeren een merkbare verbetering in deze situatie:
1.4.3.1. De programmeur bedient de computer niet meer. Naarmate het programmeren zelf een evolutie onderging en de te bedienen hardware uitgebreider werd, drong een scheiding van de functies 'programmeur' en 'operator' zich op. Los van de rendementswinst door specialisatie, maakte deze scheiding een principieel andere werking mogelijk: de zogenaamde batchverwerking. We bekijken in zo een vernieuwde situatie weer een scenario: o
Alle programmeurs binnen een bedrijf brengen hun programma's naar de operator. Er zijn programma's in COBOL en er zijn programma's in FORTRAN geschreven.
o
De operator sorteert de programma's. Hij/zij maakt twee 'hoopjes' (batches) met jobs: één voor alle COBOL code en één voor de FORTRAN programma's.
o
De computer wordt voorbereid op FORTRAN code op de eerder beschreven wijze.
o
Alle FORTRAN programma's worden uitgevoerd.
o
Hetzelfde gebeurt voor de batch van COBOL jobs.
zo'n monitor in embryonale vorm een aantal faciliteiten te zien zijn die we in hedendaagse systemen terug zullen vinden. •
Een binaire lader is een programmaatje dat als functie heeft het overbrengen van binaire code van een randapparaat naar het inwendige geheugen.
•
Bij het overschakelen van een job naar een andere moet de residente monitor uiteraard weten naar welke dat moet gebeuren. Dat werd verwezenlijkt door tussen de batch van aangeboden programma's speciale kaarten te steken (control cards) die tot doel hadden tegen de monitor dingen te vertellen in de stijl van 'hier is het einde van job A, nu moet job B opgeladen en opgestart worden. Deze opdrachten, die dus niet behoren tot een welbepaalde job dienden als commando's om het systeem te besturen (to control) en moesten als dusdanig begrepen worden door het systeem. Daarvoor moest in de monitor een gedeelte aanwezig zijn dat die besturingsopdrachten vertaalde, begreep en uitvoerde: de control card interpreter. We zien hier duidelijk het embryo van wat later zal uitgroeien tot de huidige commandovertolkers (meer daarover in het volgende hoofdstuk).
Het is duidelijk dat de verhouding tussen CPU time en set-up time gevoelig verbetert. Figuur 1-7 1.4.3.2. Automatic job sequencing Bij de werkwijze zoals hiervoor beschreven, is het echter nog steeds de operator die in de 'COBOL configuratie' de ene COBOL job na de andere oplaadt en laat uitvoeren. De automatisering hiervan kunnen we beschouwen als de eerste stap naar hetgeen we nu een 'operating system' noemen en werd belichaamd in een zogenaamde residente monitor (monitor: iets of iemand die de ganse boel organiseert en in het oog houdt). Het betreft een klein programma dat steeds in het geheugen aanwezig is (resident) en dat er voor zorgt dat bij beëindiging van een job automatisch de volgende wordt opgeladen en opgestart. We gaan hier even verder op in omdat in
Operating systems
22
1.4.3.3. Off-line verwerking Voor het gebruik van tapes zag een typische computerconfiguratie er uit zoals in afbeelding 1-8. Dat houdt in dat de werking van de CPU bepaald wordt door de Operating systems
23
snelheid van de kaartlezer en die van de regeldrukker. Dat legt uiteraard ernstige beperkingen op wat betreft het rendement van de dure CPU. Deze werkt immers veel sneller dan beide randapparaten zodat in de praktijk de CPU het meeste van de tijd werkeloos is en zit te wachten op beide toestellen.
computers worden gebouwd die kleiner, sneller en goedkoper waren dan hun voorgangers.
1.4.4.1. Spooling
Kaartlezer
CPU
De ontwikkeling van disks bracht nog een verdere verbetering van deze batchverwerking met zich mee. De winst lag hem niet zozeer in de grotere snelheid van de disks in vergelijking met tapes, maar wel in de fundamenteel andere benaderingswijze van beide randapparaten. Daar waar een tape sequentieel benaderd moet worden (wanneer de band ten einde is, moet hij teruggespoeld worden om iets te lezen dat aan het begin staat), kan een disk steeds op een willekeurige plaats gelezen of beschreven worden. Daardoor kan de invoer van een job overlapt worden door de uitvoer van een andere, waardoor de CPU nogmaals beter benut kan worden.
Printer
Figuur 1-8
Deze werkwijze noemt men spooling (Simultaneous peripheral operation on-line) en ze wordt voorgesteld in figuur 1-10. Simpel gezegd, komt het er op neer dat een disk dienst doet als buffer voor zowel invoer als uitvoer, dit alles met de bedoeling de CPU zo optimaal mogelijk te benutten door hem niet op te houden door de tijdrovende I/O operaties.
Met de komst van tape drives, kon deze situatie merkbaar verbeterd worden vermits de snelheid waarmee input vanaf een tape aan de CPU geleverd kan worden veel groter is dan de input, komende van een kaartlezer. Hetzelfde geldt voor de uitvoer: de CPU kan zijn resultaten vlugger 'kwijt' aan een tape dan aan een printer. Zo'n situatie staat in afbeelding 1-9 en wordt off-line verwerking genoemd.
.
De werking gebeurt als volgt: o o o o
Figuur 1-9 Een batch met jobs (samen met de control cards) wordt op tape gezet. De tape wordt aangeboden aan de CPU. Die handelt alle jobs af en schrijft daarbij de resultaten van al die jobs op een andere tape. Die resultaten worden vanaf de tape uitgeprint.
Figuur. 1-10
1.4.4. De derde generatie ( 1965 – 1980 ): geïntegreerde schakelingen. De uitvinding van het geïntegreerde circuit maakte het mogelijk tientallen transistors op één chip te zetten. Door deze compactere methode konden er
Operating systems
24
Operating systems
25
Na het voorgaande is de basisgedachte vrij eenvoudig te begrijpen.
1.4.4.2. Batch verwerking met multiprogramming Het werken met een tape houdt in dat de jobs uitgevoerd moeten worden in de volgorde waarin ze op de tape staan. Wanneer ze zich echter op een disk bevinden zijn we niet meer gebonden aan die volgorde en dat opent weer nieuwe perspectieven om de CPU zo optimaal mogelijk te benutten. Naar gelang de omstandigheden van het ogenblik kan namelijk gekozen worden aan welke job gewerkt wordt door de CPU. De basisgedachte van deze werkwijze die multiprogramming wordt genoemd, is de volgende:
o
Alle uit te voeren jobs bevinden zich op disk.
o
Uit die pool van jobs worden er een aantal geselecteerd en die worden in het interne geheugen geplaatst. ( Zie figuur 1-11 ). Het maken van deze keuze gebeurt niet door de operator maar door het monitorprogramma (operating system). Die bewerking heet job scheduling en we zullen daar later nog op terugkomen. De eerste job wordt gestart en uitgevoerd tot op het moment dat de uitvoering niet verder kan afgehandeld worden. Die onderbreking kan allerlei oorzaken hebben: een randapparaat dat moet worden aangekoppeld, invoer die door de gebruiker moet worden gegeven.
o
o
Nu kiest de monitor een andere job die zich in het interne geheugen bevindt, en laat de CPU daar aan werken. Het maken van dit soort keuze noemt men CPU scheduling.
o
Zoals bij multiprogramming worden er uit de pool van jobs een aantal geselecteerd en in het geheugen geplaatst.
o
Eveneens zoals bij het voorgaande wordt er van job verwisseld wanneer de uitgevoerde job niet meer verder kan.
o
Los hiervan wordt er telkens na een (korte) vastgelegde tijd eveneens verwisseld. Door dat verwisselen volgens menselijke normen razendsnel te laten gebeuren, lijkt het voor de gebruikers net alsof ze elk afzonderlijk over het ganse systeem beschikken.
1.4.5.
Personal computer systemen ( 1970 tot heden ).
Door het goedkoper worden van de hardware verschenen rond 1970 computers op de markt waarvan het gebruik bedoeld was voor één menselijke gebruiker. Enkele data op een rijtje:
Meerdere jobs in het geheugen.
1974
Altair 8800
1975 1976 1977
° Microsoft Apple 1 Apple 2
1977 1977
VisiCalc Commodore
1981
IBM PC
256 bytes RAM geen keyboard maar een switch-panel Bill Gates en Paul Allen ontwikkelen BASIC 16 KB ROM 4 KB RAM keyboard en display Het eerste rekenblad 14 KB ROM 4 KB RAM keyboard, display én tape drive 64 KB geheugen
Figuur 1-11 De eisen, gesteld aan zo'n systeem liggen anders dan bij deze die we tot hiertoe bekeken. Daar waar tot hiertoe het optimaliseren van het CPU-gebruik het voornaamste streefdoel was, ligt dat bij PC's enigszins anders: de gebruiker vraagt een vlotte en eenvoudige bediening en korte responstijd. Dat de CPU daarbij het meeste van de tijd niets doet zal hem of haar een zorg wezen.
Door deze werkwijze wordt de tijd dat de CPU niets te doen heeft nog verder beperkt.
1.4.4.3. Multitasking systemen ( time sharing ) Veel programma's zijn van die aard dat ze tijdens de uitvoering inbreng vereisen van een menselijke gebruiker, ze zijn interactief. Wanneer nu veel interactieve jobs aangeboden worden, is het logisch de vorige werkwijze nog een stapje verder door te trekken. We komen dan tot de hedendaagse multitasking of time sharing systemen.
Operating systems
26
Door een combinatie van technologische beperkingen en de doelgroep, ontbraken op die toestellen een aantal van de concepten die we tot hiertoe reeds tegenkwamen. Beveiliging van files, geheugenmanagement en multitasking vinden we dan ook niet terug in de bedrijfssystemen van die toestellen. Gaandeweg zijn echter ook deze aspecten, afkomstig van 'grote' computersystemen doorgedrongen tot de sfeer van de PC's zodat, mee door het veelvuldig koppelen van PC's in verspreide systemen en netwerken, het concept van 'personal computer' in veel situaties vervaagt of zelfs geheel verdwijnt.
Operating systems
27
1.4.6.
2.
Parallelle systemen
In alle hiervoor genoemde gevallen is er sprake van één processor. Tegenwoordig wordt in steeds meer toepassingen gebruik gemaakt van meerdere processoren die naast mekaar ( parallel ) werken. Onnodig te zeggen dat een O.S. voor zo'n systeem veel ingewikkelder wordt
1.4.7.
Verspreide systemen
Tot in de helft van de jaren '80 waren computersystemen gecentraliseerd: één centrale computer met daarmee verbonden één of meerdere terminals. Op dit ogenblik is die situatie drastisch veranderd, in die zin dat veel systemen bestaan uit een conglomeraat van verspreide subsystemen die elk hun deel van de totale te verrichten arbeid voor zich nemen.
Modellen voor bedrijfssystemen
In een hedendaags O.S. vinden we doorgaans de volgende grote onderdelen terug: o o o o o o o
process management: scheduling, interprocess communicatie, ... input/output: het verzorgen van alle in- en uitvoer memory management: het beheer van het geheugen file system: het leveren en beheren van een bestandenstructuur. beheer van secundaire geheugens: floppy disks, hard disks, CD-ROM's,... protectie: het beveiligen van data communicatie: allerlei netwerkfuncties
Al die onderdelen kunnen op verschillende wijzen georganiseerd zijn (of niet!) tot het grote geheel dat het operating system vormt. We bekijken hier enkele van de meest klassieke en courante modellen.
2.1.
Monolitische systemen
Een monolithisch operating system (en trouwens elk stuk monolithische software) wordt gekarakteriseerd door het ontbreken van een structuur. Het O.S. bestaat uit een verzameling van procedures die onderling allen zichtbaar en aanroepbaar zijn. Wanneer het O.S. een taak moet uitvoeren wordt simpelweg de daartoe geschikte procedure aangeroepen. De constructie en het onderhoud van zo'n systeem is uiteraard onderhevig aan alle gebreken, verbonden met alle ongestructureerde software. Dikwijls zijn zo'n systemen ontstaan als kleine programmaatjes waar in de loop der tijd steeds meer stukjes zijn aangekleefd. MS-DOS is daar een mooi voorbeeld van. Schematisch wordt de werking van zo'n systeem geschetst in afbeelding 2-1. Om bij het gebruik van deze methode het eigenlijke objectprogramma van het operating system op te bouwen compileert men alle afzonderlijke procedures – of de bestanden waarin deze procedures staan – en maakt er met de linker één objectfile van. Wat het verbergen van informatie betreft – dat gebeurt hier in wezen helemaal niet. Elke procedure is voor elke andere procedure zichtbaar. Maar ook in monolithische systemen kan althans enige structuur aanwezig zijn. De systeemaanroepen die door het operating system worden aangeboden, worden aangevraagd door parameters op bepaalde vaste plaatsen te zetten, bijvoorbeeld in registers of op de stack en dan een speciale trapinstructie uit te voeren. Een gebruikersprogramma moet beroep doen op het O.S. Dat gebeurt door een zogenaamde system call of kernel call (meer hierover in een volgend hoofd-stuk). De controle wordt daarbij overgedragen aan het O.S. De meeste CPU's kennen twee Operating systems
28
Operating systems
29
manieren van opereren: user mode en kernel mode. Een gebruikersprogramma werkt in user mode: op dat ogenblik zijn slechts een gedeelte van de CPU-instructies toegelaten (I/O bijvoorbeeld niet). Het operating system werkt in kernel mode: alle instructies kunnen dan uitgevoerd worden.
o
o
o
gebruikersprogramma B
Het O.S. onderzoekt de meegegeven parameters van de system call om te bepalen welke procedure moet uitgevoerd worden.
Gelaagde systemen
Een generalisatie van de methode in figuur 2-3 is het operating system te organiseren als een hiërarchie van lagen, elke laag opgebouwd op de laag eronder.
Laag 5 Laag 4 Laag 3
Eén van de manieren om de complexiteit van een groot stuk software in het algemeen onder controle te houden, is gebruik te maken van information hiding via opeenvolgende abstractielagen.
gebruikersprogramma A
kernelaanroep
Via een tabel met verwijzingen naar alle uitvoerbare procedures wordt de bedoelde bewerking uitgevoerd.
Laag 2 Laag 1
4
1
De controle wordt terug overgedragen aan het gebruikersprogramma.
Laag 0 Figuur 2-3
De basisgedachte achter zo'n lagenmodel is heel eenvoudig :
3
Een laag N maakt gebruik van de diensten, geleverd door de onderliggende laag N-1. De interne werking van die laag N-1 is voor N echter volledig verborgen.
service procedures
2
Figuur 2-1
Figuur 2-4 toont de gelaagde structuur van het MINIX operating system. Wanneer een gebruikersprogramma beroep doet op diensten van het O.S., bijvoorbeeld het file system, dan wordt die vraag door de interfaces van alle onderliggende lagen doorgegeven en behandeld en dit tot bij de hardware. Het antwoord van die hardware 'klimt' als het ware terug op door de verschillende lagen tot bij het gebruikersprogramma. Voor de verdere studie van bedrijfssystemen zullen we regelmatig gebruik maken van dit model.
Deze organisatie suggereert niettemin een elementaire structuur voor het operating system: o o o
2.2.
Een hoofdprogramma dat de aangevraagde serviceprocedure aanroept. Een aantal serviceprocedures die de systeemaanroepen uitvoeren. Een aantal utilityprocedures die de serviceprocedures assisteren. hoofdprocedure
.
serviceprocedures
Figuur 2-4 Figuur 2-2 utility - procedures
. Operating systems
30
Operating systems
31
2.3.
hardware. De communicatie met andere processen verloopt dan wel volgens het normale berichtenmechanisme.
Het Client-Server model
Tegenwoordig is er een trend om zoveel mogelijk functies van het O.S. te plaatsen in hogere lagen, met name in user mode en de kernel zo klein mogelijk te houden (figuur 2-5). De gebruikelijke methode is dat de meeste functies van het operating system in gebruikersprocessen worden geprogrammeerd. Om een service aan te vragen, zoals een blok van een file lezen, stuurt een gebruikersproces ( het clientproces ) de aanvraag naar een serverproces dat dan het werk doet en vervolgens het antwoord terugstuurt.
Een andere manier is een minimale hoeveelheid mechanisme in de kernel in te bouwen, maar de beleidsbeslissingen aan de servers in de gebruikersruimte over te laten. De kernel kan dan bijvoorbeeld herkennen dat een bericht dat aan een bepaald speciaal adres wordt gestuurd, betekent dat de inhoud van dat bericht in de I/O device registers voor een bepaald e schijf moet worden geplaatst om het lezen van een bepaalde schijf te starten. In dit voorbeeld inspecteert de kernel de bytes in het bericht zelfs niet eens om vast te stellen of ze correct en zinvol zijn. De kernel kopieert ze blindelings naar de deviceregisters van de schijf. Er moet uiteraard een sluitende methode worden gebruikt om ervoor te zorgen dat alleen bevoegde processen zulke berichten kunnen sturen.
In dit model zorgt de kernel alleen voor de communicatie tussen de clients en servers. Door het operating system in stukken te verdelen die elk één facet van het systeem afhandelen, zoals fileservice, processervice, terminalservice of geheugenservice wordt elk deel klein en hanteerbaar. En omdat alle servers als processen in user modus lopen en niet in kernel modus hebben ze geen rechtstreekse toegang tot de hardware nodig. Als er een bug in de fileserver optreedt, kan dat tot een storing in de fileservice leiden, maar de machine als geheel kan meestal blijven doorwerken. Deze architectuur is bijzonder aantrekkelijk indien die verschillende onderdelen zich niet op één en dezelfde machine bevinden: de berichten worden dan via het verbindende netwerk verzonden. Wat de cliënt betreft maakt het immers totaal niet uit of de server zich lokaal of ergens op het netwerk bevindt. In beide gevallen gebeurt hetzelfde: er is een aanvraag verstuurd en er komt een antwoord terug.
User Modus
client proces
Kernel Modus
client proces
proces server
terminal server
file server
memory server
Kernel
De scheiding tussen mechanisme en beleid is een belangrijk concept. Het komt in allerlei aspecten van operating systems telkens weer terug.
2.4.
Figuur 2-5 De gegeven voorstelling is echter niet helemaal realistisch. Sommige functies van een operating system zijn moeilijk, zoniet onmogelijk vanuit gebruikersprogramma’s te doen. Een manier om dit op te lossen is enkele kritische processen ( zoals drivers voor I/O devices ) toch in kernel modus te laten lopen, mét volledige toegang tot de
Operating systems
Figuur 2-6
32
Virtuele machines
We zagen reeds dat twee van de functionaliteiten die door een bedrijfssysteem moeten worden aangeboden zijn: (a) multiprogramming en (b) een 'uitgebreide' machine die makkelijker te programmeren is dan de naakte hardware. Door deze twee aspecten strikt te scheiden is men (met VM/370 van IBM als eerste) gekomen tot het ontwerp uit afbeelding: Het O.S. (in dit geval VM/370) produceert qua functionaliteit een aantal exacte kopijen van de onderliggende hardware zodat elk programma werkt alsof het beschikt over een volledige machine (hardware). Dat maakt dat op elk van die
Operating systems
33
virtuele afzonderlijke machines ook andere bedrijfssystemen kunnen geplaatst worden!
2.5. De systeem architectuur van een traditioneel UNIX systeem.
Deze virtuele machines zijn dus géén machine-uitbreidingen, met files en andere mogelijkheden, maar exacte kopieën van de kale hardware, met kernel / user modus, I/O, interrupts en alles wat de echte machine verder nog heeft.
Figuur 2-8 geeft een algemeen beeld van de Unix architectuur. De onderliggende hardware wordt omringd door de software van het besturingssysteem. Dit besturingssysteem wordt vaak de systeemkernel of gewoon de kernel genoemd om te benadrukken dat het besturingssysteem is geïsoleerd van de gebruiker en toepassingen. Unix is echter ook uitgerust met een aantal gebruikersdiensten en interfaces, die eveneens kunnen beschouwd worden als een onderdeel van het systeem. Deze kunnen worden gegroepeerd tot de shell, andere interfacesoftware en de onderdelen van de Ccompiler. De laag daarbuiten bestaat uit gebruikerstoepassingen en de gebruikersinterface voor de C-compiler.
De virtuele machines van VM/370. Figuur 2-7
UNIX opdrachten en bibliotheken
Interface voor systeemaanroepen
Kernel
Hardware
Toepassingen van gebruikers
Figuur 2-8
Het volgende overzicht ( Figuur 2-9 ) geeft een meer gedetailleerd overzicht van de kernel: o o o
Operating systems
34
Gebruikersprogramma’s kunnen diensten van het besturingssyteem aanroepen, rechtstreeks of via bibliotheekprogramma’s. De interface voor systeemaanroepen vormt de grens met de gebruiker en geeft software op het hogere niveau toegang tot specifieke kernelfuncties. Aan de andere kant bevat het besturingssysteem primitieve routines die directe interactie met de hardware hebben.
Operating systems
35
o
Tussen deze twee interfaces is het systeem verdeeld in twee hoofdonderdelen: een subsysteem voor procesbeheer en een subsytseem voor bestandsbeheer en I/O. Het subsysteem voor procesbeheer is verantwoordelijk voor het geheugenbeheer, de scheduling en verdeling van processen én de synchronisatie en communicatie tussen processen. Het bestandsysteem wisselt gegevens uit tussen geheugen en externe apparaten als een stroom van tekens of als blokken. Hiervoor worden uiteenlopende apparaatstuurprogramma’s gebruikt ( device drivers ). Voor de overdrachten in blokken wordt de benadering met schijfcache toegepast: in het hoofdgeheugen bevindt zich een systeembuffer tussen de gebruikersadresruimte en de externe apparaten.
Gebruikersprogramma’s
2.6.
Systeem architectuur van WINDOWS 2000
2.6.1.
Algemene Architectuur
Windows NT is een commercieel OS, voor het eerst op de markt gekomen in juli 1993. Het is een modern systeem, gebaseerd op processen en threads. Het kent bovendien een object georiënteerd model. De doelstellingen voor Windows NT waren : Het diende volgende kenmerken te hebben : vatbaar voor uitbreiding, overdraagbaar, betrouwbaar en veilig.
Bibliotheken
2.6.1.1. Vatbaar voor uitbreiding. Er zijn tenminste twee aspecten aan de uitbreidbaarheid verbonden. Het eerste slaat op de verschillende OS configuraties. Een Windows NT toestel kan geconfigureerd worden als “werkstation” of als “server”. In de verschillende configuraties gebruikt het OS dezelfde fundamentele broncode, maar verschillende onderdelen zijn verenigd bij de compilatie. Dit laat toe om NT te optimaliseren naar gelang de verschillende manieren waarop het zal gebruikt worden, zonder dat je daarvoor verschillende OS moet bouwen.
Interface voor systeemaanroepen
Subsysteem voor bestanden
Subsysteem voor procesbeheer IPC Scheduling Geheugenbeheer
Buffercache
Teken
Het tweede en meer belangrijke aspect betreft de manier waarop de OS software is gestructureerd. In de benadering die gebruikt is bij het ontwerp van NT, zijn alleen de meest essentiële OS functionaliteiten vervat in een kleine “kern”. Deze kern wordt dikwijls “microkernel” genoemd. Bijkomende mechanismen worden dan geïmplementeerd boven op die microkernel. Deze aanpak heeft als voordeel dat sleutelmechanismen zorgvuldig kunnen ontworpen en getest worden. De NT Kernel verschaft essentiële “low-level” mechanismen als een abstractielaag van de hardware. De NT Executive is ontworpen als een abstractielaag van de NT Kernel. Deze verschaft mechanismen voor object en geheugenbeheer, proces beheer, bestand beheer en device beheer. Samen verschaffen de NT Kernel en Executive de meest essentiële elementen van het OS. Alhoewel ze ontworpen en geïmplementeerd zijn als aparte software modules, zijn ze in één bestand samen verpakt : NTOSKRNL.EXE.
Blok
Device drivers
Hardware control
Hardware
De volgende laag van abstractie is de “subsysteem” laag. Een “subsysteem” is een software module die gebruikt maakt van de diensten aangeboden door de Kernel en de Executive. Dit subsysteem weerspiegelt een ander OS. Deze subsystemen worden allemaal uitgevoerd in “user-mode”. Op deze manier ondersteunt MS verschillende omgevingen, zoals MS-DOS en WIN-16.
Figuur 2-9
.
Operating systems
36
Operating systems
37
2.6.1.2. Overdraagbaarheid
2.6.3.1. Objecten
Dit overlapt gedeeltelijk het aspect van uitbreidbaarheid. Subsystemen laten toe om Windows NT uit te breiden voor de meest uiteenlopende toepassingen en hun vereisten. Zo kunnen toepassingen die geschreven zijn voor andere OS toch overgedragen worden op NT. Naast het MS-DOS, WIN16 en POSIX subsysteem kunnen software ontwerpers hun eigen subsysteem ontwerpen dat tegemoet komt aan hun vereisten. Alleen het WIN32 subsysteem neemt een speciale plaats in omdat het een aantal uitbreidingen van de NT executive bevat die door de andere subsystemen gebruikt worden. Een ander aspect van overdraagbaarheid is het feit dat Window NT zelf kan overgedragen worden op de meest uiteenlopende hardware platforms. De ontwerpers van NT hebben zorvuldig de dingen die gemeenschappelijk zijn aan alle processoren en de zaken waarin ze van elkaar verschillen bestudeerd. Dit liet hen toe om een “hardware abstraction layer” te creëren. Deze software module isoleert de kernel (en de rest van het OS ) van hardware verschillen. Deze HAL wordt uitgevoerd met de processor in “supervisor” mode.
De NT Kernel definieert een set “object types” . Sommige kernel object types worden gebruikt door de kernel zelf om delen te vormen van het algemene OS, andere worden gebruikt door de NT Executive en de subsystemen als “bouwstenen”. Kernel objects kunnen niet gemanipuleerd worden door gebruikerssoftware, tenzij langs function calls. Ze worden onderverdeeld in twee categorieën.
2.6.4.
• • • • • • • • •
Deze aspecten worden gereflecteerd in het feit dat de verschillende onderdelen ( HAL, Executive, Kernel en subsystemen van elkaar strikt gescheiden zijn. Bovendien is NT ontworpen om te voldoen aan de standaard vereisten van een “betrouwbaar” OS. Zo is NT gecertificiëerd door het United States Computer Security Center als “C2” level.
De Hardware Abstraction Layer
De HAL is een software module die hardware functionaliteit vertaalt in een gestandaardiseerde set van gedragingen. Deze functies worden geëxporteerd door de HAL.DLL. Het OS roept dus functies aan in de HAL.DLL overal waar een specifiek hardware adres nodig is. Device interrupts hebben gewoonlijk een adres dat bepaald wordt door de architectuur van de CPU. De HAL interface laat NT toe om functies te gebruiken uit de HAL.DLL ipv de adressen zelf. Dit betekent dat het mogelijk is dezelfde broncode te gebruiken op een DEC toestel met ALPHA processor en op een INTEL systeem. Subsysteem en applicatie programmeurs moeten zich dus niet bezighouden met het type van processor-chip in de computer.
2.6.3.
De NT Kernel
De Kernel verschaft de basis voor multitasking en berekeningen. Hij doet dit zonder zich bezig te houden met enige strategie voor procesbeheer, geheugenbeheer enz… Hij verschaft een collectie van componenten die gebruikt kunnen worden door de clients van de Kernel. De Kernel biedt objecten en threads aan zijn clients. Om dit te kunnen verwezelijken beheert de Kernel hardware interrupts ,voert de processor scheduling uit en handelt de multiprocessor synchronisatie uit.
Operating systems
38
De NT Executive
De NT Executive is gebouwd bovenop de Kernel om de volledige set van NT gedragslijnen en diensten te implementeren. Dit omvat Process beheer, geheugen beheer, bestandsbeheer en device beheer. De NT executive is ontworpen op bron code niveau als een set modules.
2.6.1.3. Betrouwbaarheid en veiligheid.
2.6.2.
Control Objects. Dispatcher Objects
2.6.5.
Object Manager Process en Thread Manager Virtual Memory Manager Security Reference Manager I/O Manager Cache Manager LPC facility Runtime Functions Executive Support Functions
De Native API
De NT Executive en NT Kernel zijn ontworpen als afzonderlijke modules maar worden samen geïmplementeerd in één NTOSKRNL.EXE . Deze gecombineerde module samen met de HAL vervat het volledige NT OS. Ze biedt ongeveer 240 functies aan, waarvan de meeste niet gedocumenteerd zijn. Dus alleen Subsystems ontwikkelaars zouden hun software hierop mogen baseren. Deze set functies worden de Native API genoemd.
2.6.6.
NT Subsystems
In de Windows NT architectuur bieden subsystemen een laag van diensten aan bovenop de Native API. Er kunnen veel verschillende subsystemen naast elkaar, al dan niet verbonden, bestaan. In een gewone Windows NT configuratie is er standaard vervat: •
Het WIN32 subsysteem biedt een user-interface aan om het systeem te beheren.
•
Het WINLOGON Service Subsysteem. Deze wordt gebruikt om de identiteit van gebruikers te bekrachtigen en maakt daarvoor gebruik van de Security Reference Monitor die vervat zit in NT Executive.
Operating systems
39
3.
•
Een WIN16 subsysteem.
•
Een POSIX subsysteem kan toegevoegd worden.
3.1.
Elk subsysteem gebruikt de diensten die worden aangeboden door de Native API en biedt op zijn beurt diensten aan. In de filosofie van MS zijn de diensten die aangeboden worden door de subsystemen wél gedocumenteerd. Programmeurs die nieuwe software schrijven worden ervan verzekerd dat de diensten die de onderliggende API aanbiedt onveranderd zullen blijven terwijl de lagere lagen veranderd worden..
2.6.7.
Enkele Basisconcepten Interrupts
3.1.1.
Omschrijving
De uitvoering van een programma wordt bepaald door de instructies waaruit het is samengesteld en de waarden van de gebruikte variabelen. Onderstaande afbeelding illustreert dat met een eenvoudig voorbeeldje.
De WIN32 API
If x > y Then
De WIN32 API is de officiële OS interface voor alle MS OS: Windows NT, 95 , 98 , CE. Als gevolg hiervan kan een toepassing geschreven worden die op al die verschillende versies kan uitgevoerd worden. Verbeteringen aan één van de verschillende OS producten bieden nog steeds dezelfde diensten aan, maar wellicht in een betere kwaliteit. Wanneer een ontwikkelaar een programma schrijft voor een MS OS is alleen de WIN32 API gedocumenteerd beschikbaar. De WIN32 API biedt ongeveer 1000 function calls aan. Bedenk in dit verband dat de Native API er slechts 240 aanbiedt !
……… Else
WIN 2K Executive
User Modus
Figuur 3-1 System Support Processes
Service Processes
User Applications
Environment Subsystems
code
NTDLL.DLL
Van deze werkwijze wordt afgeweken door middel van het interruptmechanisme. Dat is een mechanisme dat een werkend programma tijdelijk onderbreekt om een ander programma uit te voeren. Na de beëindiging van dit laatste wordt het onderbroken programma terug in werking gezet precies op de plaats waar het onderbroken werd. Voor dat eerste programma is het net of er is niets gebeurd!
Executive API I/O Manager
LPC
Cache Manager
File System
Proces/Thread Manager
Virtual Memory Manager
Window Manager
Security Reference Manager
Object Manager
Device Drivers
Voor de werking van een computer is zo'n mechanisme noodzakelijk, onder andere omdat de CPU heel dikwijls taken moet uitvoeren die belangrijker zijn dan de werking van een bepaald gebruikersprogramma. Voorbeelden: • Elke seconde moet de stand van de inwendige klok bijgesteld worden. • Wanneer een disk drive meldt dat hij een lees- of schrijfoperatie gaat uitvoeren, dan is dat op dat ogenblik belangrijker dan het gebruikersprogramma. • Bij elke toetsaanslag genereert het klavier een interrupt om te signaleren dat er invoer voor handen is.
MicroKernel Hardware Abstraction Layer
Figuur 2-10
•
Operating systems
40
Een multitasking systeem is gebaseerd op het regelmatig onderbreken van programma's met de bedoeling om de andere aan de beurt te laten komen.
Operating systems
41
3.1.2.
Daartussenin werd de interrupt afgehandeld door de zogenaamde interrupt handler.
Implementatie van het interruptmechanisme
Bij de implementatie van zo'n mechanisme moeten essentieel de volgende twee problemen worden opgelost:
3.1.2.2. Probleem 2
Probleem 1: Het onderbroken programma moet na het afhandelen van de interrupt terug kunnen verder lopen, net of er niets gebeurd is.
Heel dikwijls wordt dit probleem opgelost door hardwarematig elke interrupt met een vast adres in het geheugen te verbinden. Bij het opstarten van het systeem worden de interrupt handlers in het geheugen geladen en de adressen waarop dat gebeurd is, worden ingevuld in die vaste plaatsen (de interrupt vector). Bij de IBM en compatibele PC’s omvat deze interrupt vector omvat 256 plaatsen van 4 bytes. Hij biedt dus in totaal ondersteuning voor 256 interrupts. Hij begint in de geheugenruimte bij het offsetadres 0 en beslaat dus in elke PC het eerste Kilobyte van het RAM geheugen.
Probleem 2: De afhandeling van een interrupt vereist dat er een bepaald programma moet worden uitgevoerd. Dat moet op dat ogenblik dan wel te vinden zijn!
Deze werkwijze waarborgt een grote flexibiliteit omdat nu zonder problemen andere interrupt handlers kunnen worden geïnstalleerd.
Principieel kunnen beide problemen als volgt worden opgelost:
3.1.3.
3.1.2.1. Probleem 1 Twee bewerkingen worden door het O.S. ter beschikking gesteld: o
Een save-instructie die alle gegevens met betrekking tot het onderbroken programma ergens bijhoudt (program counter, registers, ...)
o
Een restore-operatie die al deze gegevens terug op hun juiste plaats zet.
Hardware interrupts
Een apparaat zoals het toetsenbord dient met korte tussenpozen door de CPU telkens te worden opgevraagd, zodat het meteen op de invoer van de gebruiker kan reageren en deze invoer aan het draaiende programma kan doorgeven. Door dit onophoudelijke opvragen – het zogenaamde polling – wordt veel CPU tijd verkwist omdat normaal gezien de gebruiker veel minder vaak een toets indrukt dan dat het toetsenbord door de CPU wordt opgevraagd. Anderzijds, hoe minder de CPU het toetsenbord opvraagt, des te langer duurt het voordat een programma reageert op het indrukken van een toets. Dit is uiteraard niet zinvol, het systeem moet onmiddellijk reageren. Daarom volgt de PC in dit geval een andere procedure. De CPU vraagt de verschillende apparaten niet telkens op, de apparaten meldden zich bij de CPU telkens er iets is gebeurd waarop het systeem moet reageren. In deze context spreekt men van een hardware interrupt.
If x > y Then
Interrupt handler
3.1.3.1. Interrupts en de instructiecyclus.
……… Else
Figuur 3-2
Telkens een interrupt optreedt wordt de save-instructie uitgevoerd en vlak voor het terug opstarten van het onderbroken programma gebeurt de restore-operatie.
Operating systems
42
Figuur 3-3 Interrupts worden vooral gebruikt als middel om de verwerkingsefficiëntie te verhogen. De meeste externe apparaten zijn langzamer dan de processor. Stel dat een processor gegevens verzendt naar een printer volgens het schema van de instructiecyclus uit figuur 3-3. Na elke schrijfbewerking moet de processor pauzeren en niets doen totdat de printer de instructie verwerkt heeft. De Operating systems
43
lengte van pauze kan een orde van grootte hebben van enkele honderden of zelfs duizenden instructiecycli waarbij geen geheugen wordt gebruikt. Het zal duidelijk zijn dat dit een erg verspillend gebruik van de processor is. Figuur3-4 (a) illustreert deze situatie. Het gebruikersprogramma voert een reeks WRITE- aanroepen uit die zijn verweven met de verwerking. De codesegmenten 1, 2 en 3 verwijzen naar opeenvolgende instructies waarvoor geen I/O is vereist. De WRITE-aanroepen worden verzonden naar een I/0-programma, een systeemhulpprogramma dat de feitelijke I/0-bewerkingen zal uitvoeren. Het I/Oprogramma bestaat uit drie secties: •
•
•
Een instructiereeks, aangeduid als 4 in de figuur, voor het voorbereiden van de feitelijke I/0-bewerking. Dit kan bestaan uit het kopiëren van de uit te voeren gegevens naar een buffer en het voorbereiden van de parameters voor een apparaatopdracht. De feitelijke I/0-opdracht. Zonder het gebruik van interrupts moet het programma na het verzenden van deze opdracht wachten tot het I/0apparaat de gewenste functie heeft uitgevoerd. Het programma kan wachten door herhaaldelijk een testbewerking uit te voeren om te bepalen of de I/0bewerking gereed is. Een instructiereeks, aangeduid als 5 in de figuur, voor het voltooien van de bewerking. Dit kan bestaan uit het instellen van een vlag die het slagen of mislukken van de bewerking aangeeft.
Met interrupts kan de processor andere instructies uitvoeren terwijl een I/O bewerking bezig is. De besturingsvolgorde met interrupts wordt geïllustreerd in figuur3-4 (b). Net als hiervoor bereikt het gebruikersprogramma een punt waarop het een systeemaanroep uitvoert in de vorm van een WRITE-aanroep. Het I/Oprogramma dat in dat geval wordt aangeroepen, bestaat alleen uit de voorbereidingscode en de feitelijke I/O-opdracht. Nadat deze instructies zijn uitgevoerd, wordt de besturing teruggegeven aan het programma. Ondertussen is het externe apparaat bezig met het accepteren en afdrukken van gegevens uit het computergeheugen. Deze I/0-bewerking wordt gelijktijdig met instructies in het gebruikersprogramma uitgevoerd. Is het externe apparaat gereed om te worden bediend - dat wil zeggen: is het gereed om meer gegevens van de processor te accepteren - dan verzendt de I/0-module een signaal voor een interruptaanvraag (interrupt request) naar de processor. De processor reageert door de uitvoering van het huidige programma te onderbreken, een zijsprong te maken naar een programma voor het bedienen van het I/0apparaat (interruptafhandeling genoemd) en de oorspronkelijke uitvoering te hervatten nadat het apparaat is bediend. De punten waarop zulke interrupts optreden, zijn in figuur 3-4 (b) aangegeven met een kruisje. Vanuit het gebruikersprogramma gezien is een interrupt letterlijk niet meer dan een onderbreking: een onderbreking van de normale uitvoeringsvolgorde. Is de interruptverwerking voltooid, dan wordt de normale uitvoering hervat. Het gebruikersprogramma hoeft daarom geen speciale code voor interrupts te bevatten: de processor en het besturingssysteem zijn verantwoordelijk voor het onderbreken van het gebruikersprogramma en het vervolgens hervatten van het programma op hetzelfde punt.
Figuur 3-5 Figuur 3-4
Voor interrupts wordt een interruptcyclus toegevoegd aan de instructiecyclus, zoals figuur 3-5 laat zien. In de interruptcyclus controleert de processor of er interrupts zijn opgetreden, wat wordt aangeduid door de aanwezigheid van een interruptsignaal. Zijn er geen wachtende interrupts, dan gaat de processor verder met de ophaalcyclus en vraagt hij de volgende instructie van het huidige programma op. Is er wel een wachtende interrupt, dan onderbreekt de processor het huidige
Omdat de volledige uitvoering van de I/O-bewerking relatief lang kan duren, moet het I/O-programma lang wachten op het voltooien van de bewerking: daardoor wordt het gebruikersprogramma op het punt van de WRITE-aanroep gestopt.
Operating systems
44
Operating systems
45
programma en voert hij een routine uit voor een interruptafhandeling (interrupt handler).
processor of er nog meer interrupts zijn opgetreden. Deze benadering werkt netjes en eenvoudig, omdat interrupts in strikt sequentiële volgorde worden afgehandeld. Het nadeel van deze benadering is dat geen rekening wordt gehouden met de relatieve prioriteit of met situaties waarin de tijd kritiek is. Arriveert er bijvoorbeeld invoer via een communicatielijn, dan moet die snel worden verwerkt om ruimte te maken voor meer invoer. Is de eerste verzameling gegevens nog niet verwerkt als de tweede verzameling arriveert, dan kunnen gegevens verloren gaan omdat de bufferruimte van het I/0-apparaat kan overlopen.
Het interrupt-afhandelingsprogramma maakt meestal deel uit van het besturingssysteem. Doorgaans bepaalt dit programma het soort interrupt en voert het de benodigde acties uit. In het voorbeeld dat we hebben gebruikt, bepaalt het afhandelingsprogramma bijvoorbeeld welke I/0-module de interrupt heeft gegenereerd en kan het een zijsprong maken naar een programma dat meer gegevens naar die I/0-module schrijft. Is de routine voor de interruptafhandeling voltooid, dan kan de processor de uitvoering van het gebruikersprogramma hervatten op het punt van de onderbreking. Het is duidelijk dat dit proces enige overhead kent. Er moeten extra instructies worden uitgevoerd ( in de interruptafhandeling ) om het soort interrupt te achterhalen en te bepalen wat de juiste actie is. Desondanks kan de processor, wegens de relatief grote hoeveelheid tijd die zou worden verspild door eenvoudig te wachten op een I/O-bewerking veel efficiënter worden ingezet bij het gebruik van interrupts. 3.1.3.2. Meervoudige interrupts Tot zover is in de bespreking alleen ingegaan op het optreden van één interrupt. Stel nu echter dat er meerdere interrupts kunnen optreden. Een programma kan bijvoorbeeld gegevens ontvangen via een communicatielijn en resultaten afdrukken. o De printer genereert een interrupt na het beëindigen van elke afdrukopdracht. o De controller van de communicatielijn genereert een interrupt bij de ontvangst van elke eenheid gegevens. Deze eenheid kan één teken of een blok tekens zijn, afhankelijk van het soort communicatie. Hoe dan ook, er kan een interrupt voor de communicatie optreden terwijl een interrupt van de printer wordt verwerkt.
Figuur 3-6
Voor het afhandelen van meervoudige interrupts kunnen twee benaderingen worden gevolgd: De eerste is het uitschakelen van interrupts terwijl een interrupt wordt verwerkt. Een uitgeschakelde interrupt betekent dat de processor het desbetreffende interruptsignaal kan en zal negeren. Als er gedurende deze tijd een nieuwe interrupt komt, blijft deze in het algemeen wachten en wordt door de processor gecontroleerd nadat interrupts weer zijn toegelaten. Wordt een gebruikersprogramma uitgevoerd en treedt een interrupt op, dan worden de interrupts onmiddellijk uitgeschakeld. Is de routine voor de interruptafhandeling voltooid, dan worden de interrupts ingeschakeld voordat het gebruikersprogramma wordt hervat en controleert de Operating systems
46
Een tweede benadering is het definiëren van prioriteiten voor interrupts en toestaan dat een interrupt met een hogere prioriteit een interruptafhandeling met een lagere prioriteit onderbreekt. Een voorbeeld van deze tweede benadering is een systeem met drie I/O-apparaten: een printer, een schijf en een communicatielijn, respectievelijk met de oplopende prioriteit 2, 4 en 5. Figuur... toont een mogelijke volgorde. Een gebruikersprogramma begint op t = 0. Op t = 10 treedt een printerinterrupt op; gebruikersinformatie wordt op de systeemstack geplaatst en de uitvoering gaat verder bij de interruptafhandelingsroutine (interrupt service routine, ISR) van de printer. Terwijl deze routine nog wordt uitgevoerd, treedt op t = 15 een communicatie-interrupt op. Omdat de communicatielijn een hogere prioriteit heeft dan de printer, krijgt deze interrupt voorrang. De ISR voor de printer wordt onderbroken, de status daarvan wordt op de stack geplaatst en de uitvoering gaat verder bij de ISR voor de communicatie. Terwijl deze routine wordt uitgevoerd, treedt een schijfinterrupt op (t = 20). Omdat deze interrupt een lagere prioriteit heeft, wordt deze alleen maar vastgehouden en wordt eerst de ISR voor de communicatie volledig uitgevoerd. Is de ISR voor de communicatie voltooid (t = 25), dan wordt de vorige processorstatus hersteld, de uitvoering van de ISR voor de printer. Voordat ook maar één instructie van die routine kan worden uitgevoerd, geeft de processor echter voorrang aan de schijfinterrupt met de hogere prioriteit en wordt de besturing overgedragen aan de Figuur 3-7 ISR van de schijf. Pas als die routine is voltooid (t = 35), wordt de ISR voor de printer hervat. Is die routine voltooid (t = 40), dan keert de besturing ten slotte terug naar het gebruikersprogramma.
3.1.4.
System Calls
Gebruikersprogramma's communiceren met het O.S. door middel van system calls: het aanroepen en activeren van code uit het bedrijfssysteem. Dat gebeurt niet rechtstreeks maar via bibliotheekprocedures, geleverd door de taal waarin het gebruikersprogramma geschreven is. De taak van zo'n procedure is de system
Operating systems
47
calls er uit te laten zien als 'gewone' procedures. Ze zetten de parameters van de system call op de juiste locaties (dikwijls registers), en voeren dan een zogenaamde TRAP-instructie uit. Die TRAP-instructie draagt de controle over aan het operating system en zorgt er voor dat de juiste system call wordt uitgevoerd. Die doet het gevraagde werk, zet ergens in een register een statuscode om aan te geven of alles naar wens is verlopen, en geeft dan het bevel terug aan de bibliotheekprocedure via een RETURN FROM TRAP-instructie. De TRAP-instructie heeft o.a. voor gevolg dat overgeschakeld wordt van user naar kernel mode. Meer algemeen is een trap een interrupt die softwarematig gegenereerd wordt, hetzij door een gebruikersprogramma, hetzij door het systeem zelf in geval van een foute uitzonderingssituatie (een deling door nul bijvoorbeeld). Als illustratie bekijken we een I/O operatie bij MS-DOS zoals ze gepresenteerd wordt in een hogere programmeertaal. In de basisbibliotheek van de Modula-2 omgeving JPI wordt een teken van het klavier ingelezen door middel van de procedure RdKey(). In elke MS-DOS handleiding voor programmeurs vind je dat een tekeninvoer zonder echo op het scherm op de volgende wijze moet gebeuren: o o o o
Plaats de waarde 08h in het AH register van de CPU. Voer software-interrupt 21h uit. Deze roept de meeste DOS-serviceroutines aan. Het teken dat van het klavier afkomstig is staat nu in register AL. 1 2 3 4 5 6 7 8 9
3.1.5. Samenspel tussen hardware, BIOS, DOS en toepassingsprogramma’s Tot slot wil ik nog even laten zien hoe de verschillende niveaus van hardware, DOS, BIOS met elkaar zijn verweven om het toepassingsprogramma’s zo gemakkelijk mogelijk te maken met de PC hardware te communiceren. Laten we eens de weg volgen van een willekeurig teken vanaf de hardware – het toetsenbord – tot aan het toepassingsprogramma dat de ingevoerde tekens leest.
Toepassingsprogramma
Interrupt 21h DOS routine
3.1.5.1. De toetsenbord hardware Interrupt 16h BIOS toetsenbord interrupt
PROCEDURE RdKey() : CHAR; VAR R : Registers; BEGIN WITH R DO AH := 8; Lib.Dos(R); RETURN CHR(AL); END; END RdKey;
De hardware van het toetsenbord bestaat uit een toetsenbordprocessor in het toetsenbord die met een kabel is verbonden met de CPU. Het is zijn taak het toetsenbord te bewaken en het indrukken en loslaten van de toetsen aan het systeem te melden. De toetsenbord processor verbindt de toetsen niet met bepaalde tekens maar geeft elke toets een nummer. Als er een toets wordt ingedrukt, wordt het toetsnummer in de vorm van een zogenaamde make-code aan de CPU doorgegeven. Om de overdracht te initiëren verstuurt de toetsenbord controller een interruptsignaal aan de interrupt controller, dat bij IRQ1 aankomt. De CPU voert nu interrupt 09h uit.
In regel vijf wordt de waarde 8 in het AH register geplaatst. De zesde regel bevat de software-interrupt. In regel zeven wordt de waarde uit het AL register teruggegeven.
Om een functie van DOS via een software interrupt aan te roepen hoeft de aanroeper het adres van de routine in de geheugenruimte niet te kennen, maar slechts het nummer van de interrupt. De interrupt handler wordt door de CPU aangeroepen via de bovenvermelde interrupt – vector – tabel, waarin de adressen van de gewenste functies worden gehaald. Het aangegeven interrupt-nummer wordt daarbij
Operating systems
Hiermee wordt een ander voordeel van het interrupt mechanisme duidelijk. Een PC producent die een IBM compatibele PC wil maken mag uiteraard niet het hele ROMBIOS van IBM kopiëren. Hij mag wél in zijn eigen ROM-BIOS dezelfde functies implementeren. Deze functies worden met dezelfde interrupts aangeroepen en verwachten hun parameters in dezelfde registers! De routines, daarentegen, kunnen geheel of gedeeltelijk anders zijn.
Dos toetsenbord device driver
Het zijn dan ook die drie instructies die we in de code terugvinden: o o o
als index gebruikt. De tabel wordt bij de start van het systeem ingesteld, zodat de diverse interruptvectoren naar de betreffende functies van het ROM-BIOS verwijzen.
48
Toetsenbord buffer
Interrupt 9h BIOS toetsenbord handler
Toetsenbord processor
Figuur 3-8
3.1.5.2. De toetsenbord handler van het BIOS Daarmee zijn we op het volgende niveau aangekomen. Achter interrupt 09h zit een BIOS routine, de zogenaamde toetsenbord handler. Terwijl de interrupt handler wordt aangeroepen heeft de toetsenbordprocessor de code van de toets via de Operating systems
49
toetsenbordkabel naar poort 60h verzonden. Daar wordt nu de code door de BIOS handler gelezen en voorzien van het nummer van de ingedrukte of losgelaten toets. Het is de taak van deze handler om de code om te zetten in een teken uit de tekenrecord van de PC ( de ASCII code ). 3.1.5.3. De toetsenbordbuffer Is de ASCII code eenmaal vastgesteld dan wordt deze door de BIOS handler in de 16 byte grote toetsenbordbuffer van het BIOS geschreven. Als de toetsenbordbuffer vol is volgt een pieptoon. Het werk van de BIOS handler is daarmee gedaan en de processor kan zich weer aan het programma wijden dat bij het binnenkomen van de interrupt-request werd onderbroken. De tekens worden dus niet direct aan het lopende programma doorgegeven, maar eerst in de toetsenbordbuffer opgeslagen.
3.1.6.
Case Study : WINDOWS 2000 Trap Dispatching
Interrupts and exceptions are operating system conditions that divert the processor to code outside the normal flow of control. Either hardware or software can detect them. The term trap refers to a processor's mechanism for capturing an executing thread when an exception or an interrupt occurs and transferring control to a fixed location in the operating system. In Windows 2000, the processor transfers control to a trap handler, a function specific to a particular interrupt or exception. Figure 1 illustrates some of the conditions that activate trap handlers.
3.1.5.4. De BIOS-toetsenbord-interrupt We hebben hier te maken met interrupt 16h, waarachter drie BIOS functies schuilgaan met behulp van welke een teken uit de toetsenbordbuffer wordt gelezen en de status van het toetsenbord wordt vastgesteld. Deze drie functies worden vanuit een programma aangeroepen als het programma interrupt 16h als software interrupt veroorzaakt. Een toepassingsprogramma hoeft deze functies van interrupt 16h niet direct te gebruiken, maar kan ook gebruik maken van overeenkomstige DOS functies. 3.1.5.5. Het DOS niveau We komen nu op het DOS niveau. Hier worden tekens met behulp van de BIOS functies van interrupt 16h van het toetsenbord gelezen en in de interne DOS buffer opgeslagen. Daarmee hebben we het laatste niveau onder het toepassingsprogramma bereikt. We hebben hier te maken met diverse functies, waarmee via DOS interrupt 21h het toetsenbord kan worden bestuurd. Deze functies roepen de driver-functies aan en geven de geleverde tekens door aan het aanroepende programma.
Figure 1 Trap dispatching The kernel distinguishes between interrupts and exceptions in the following way. An interrupt is an asynchronous event (one that can occur at any time) that is unrelated to what the processor is executing. Interrupts are generated primarily by I/O devices, processor clocks, or timers, and they can be enabled (turned on) or disabled (turned off). An exception, in contrast, is a synchronous condition that results from the execution of a particular instruction. Running a program a second time with the same data under the same conditions can reproduce exceptions. Examples of exceptions include memory access violations, certain debugger instructions, and divide-by-zero errors. The kernel also regards system service calls as exceptions (although technically they're system traps). Either hardware or software can generate exceptions and interrupts. For example, a bus error exception is caused by a hardware problem, whereas a divide-by-zero exception is the result of a software bug. Likewise, an I/O device can generate an interrupt, or the kernel itself can issue a software interrupt (such as an APC or DPC, described later in this chapter).
Operating systems
50
Operating systems
51
When a hardware exception or interrupt is generated, the processor records enough machine state so that it can return to that point in the control flow and continue execution as if nothing had happened. To do this, the processor creates a trap frame on the kernel stack of the interrupted thread into which it stores the execution state of the thread. The trap frame is usually a subset of a thread's complete context. (Thread context is described in Chapter 6.) The kernel handles software interrupts either as part of hardware interrupt handling or synchronously when a thread invokes kernel functions related to the software interrupt. In most cases, the kernel installs front-end trap handling functions that perform general trap handling tasks before and after transferring control to other functions that field the trap. For example, if the condition was a device interrupt, a kernel hardware interrupt trap handler transfers control to the interrupt service routine (ISR) that the device driver provided for the interrupting device. If the condition was caused by a call to a system service, the general system service trap handler transfers control to the specified system service function in the executive. The kernel also installs trap handlers for traps that it doesn't expect to see or doesn't handle. These trap handlers typically execute the system function KeBugCheckEx, which halts the computer when the kernel detects problematic or incorrect behavior that, if left unchecked, could result in data corruption. (For more information on bug checks, see the section "System Crashes" beginning in Chapter 4.) The following sections describe interrupt, exception, and system service dispatching in greater detail. 3.1.6.1. Interrupt Dispatching Hardware-generated interrupts typically originate from I/O devices that must notify the processor when they need service. Interrupt-driven devices allow the operating system to get the maximum use out of the processor by overlapping central processing with I/O operations. A thread starts an I/O transfer to or from a device and then can execute other useful work while the device completes the transfer. When the device is finished, it interrupts the processor for service. Pointing devices, printers, keyboards, disk drives, and network cards are generally interrupt driven. System software can also generate interrupts. For example, the kernel can issue a software interrupt to initiate thread dispatching and to asynchronously break into the execution of a thread. The kernel can also disable interrupts so that the processor isn't interrupted, but it does so only infrequently—at critical moments while it's processing an interrupt or dispatching an exception, for example.
3.1.6.2. Hardware Interrupt Processing On x86 systems, external I/O interrupts come into one of the lines on an interrupt controller. The controller in turn interrupts the processor on a single line. Once the processor is interrupted, it queries the controller to get the interrupt request (IRQ). The interrupt controller translates the IRQ to an interrupt number, uses this number as an index into a structure called the interrupt dispatch table (IDT), and transfers control to the appropriate interrupt dispatch routine. At system boot time, Windows 2000 fills in the IDT with pointers to the kernel routines that handle each interrupt and exception. Windows 2000 maps hardware IRQs to interrupt numbers into the IDT, and the system also uses the IDT to configure trap handlers for exceptions. For example, the x86 exception number for a page fault (an exception that occurs when a thread attempts to access a page of virtual memory that isn't defined or present) is 0xe. Thus, entry 0xe in the IDT points to the system's page fault handler. Although the x86 architecture can support up to 256 IDT entries, the number of IRQs a particular machine can support is determined by the design of the interrupt controller the machine uses. Most x86 systems rely on either the i8259A Programmable Interrupt Controller (PIC) or a variant of the i82489 Advanced Programmable Interrupt Controller (APIC); the majority of new computers include an APIC. The PIC standard originates with the original IBM PC. PICs work only with uniprocessor systems and have 15 interrupt lines. APICs work with multiprocessor systems and have 256 interrupt lines. Intel and other companies have defined the Multiprocessor Specification (MP Specification), a design standard for x86 multiprocessor systems that centers on the use of APIC. To provide compatibility with uniprocessor operating systems and boot code that starts a multiprocessor system in uniprocessor mode, APICs support a PIC compatibility mode with 15 interrupts and delivery of interrupts to only the primary processor. The APIC actually consists of several components: an I/O APIC that receives interrupts from devices, local APICs that receive interrupts from the I/O APIC and that interrupt the CPU they are associated with, and an i8259A-compatible interrupt controller that translates APIC input into PIC-equivalent signals. The I/O APIC is responsible for implementing interrupt routing algorithms—which are software-selectable (the HAL makes the selection on Windows 2000)—that both balance the interrupt load across processors and attempt to take advantage of locality, delivering interrupts to the same processor that has just fielded a previous interrupt of the same type.
The kernel installs interrupt trap handlers to respond to device interrupts. Interrupt trap handlers transfer control either to an external routine (the ISR) that handles the interrupt or to an internal kernel routine that responds to the interrupt. Device drivers supply ISRs to service device interrupts, and the kernel provides interrupt handling routines for other types of interrupts.
Each processor has a separate IDT so that different processors can run different ISRs, if appropriate. For example, in a multiprocessor system, each processor receives the clock interrupt, but only one processor updates the system clock in response to this interrupt. All the processors, however, use the interrupt to measure thread quantum and to initiate rescheduling when a thread's quantum ends. Similarly, some system configurations might require that a particular processor handle certain device interrupts.
In the following subsections, you'll find out how the hardware notifies the processor of device interrupts, the types of interrupts the kernel supports, the way device drivers interact with the kernel (as a part of interrupt processing), and the software interrupts the kernel recognizes (plus the kernel objects that are used to implement them).
Most of the routines that handle interrupts reside in the kernel. The kernel updates the clock time, for example. However, external devices such as keyboards, pointing devices, and disk drives also generate many interrupts, and device drivers need a way to tell the kernel which routine to call when a device interrupt occurs.
Operating systems
52
Operating systems
53
3.1.6.3. Software Interrupt Request Levels (IRQLs) Although interrupt controllers perform a level of interrupt prioritization, Windows 2000 imposes its own interrupt priority scheme known as interrupt request levels (IRQLs). The kernel represents IRQLs internally as a number from 0 through 31, with higher numbers representing higher-priority interrupts. Although the kernel defines the standard set of IRQLs for software interrupts, the HAL maps hardwareinterrupt numbers to the IRQLs. Figure 2 shows IRQLs defined for the x86 architecture.
A kernel-mode thread raises and lowers the IRQL of the processor on which it's running, depending on what it's trying to do. For example, when an interrupt occurs, the trap handler (or perhaps the processor) raises the processor's IRQL to the assigned IRQL of the interrupt source. This elevation masks all interrupts at and below that IRQL (on that processor only), which ensures that the processor servicing the interrupt isn't waylaid by an interrupt at the same or a lower level. The masked interrupts are either handled by another processor or held back until the IRQL drops. Therefore, all components of the system, including the kernel and device drivers, attempt to keep the IRQL at passive level (sometimes called low level). They do this because device drivers can respond to hardware interrupts in a timelier manner if the IRQL isn't kept unnecessarily elevated for long periods. Because changing a processor's IRQL has such a significant effect on system operation, the change can be made only in kernel mode—user-mode threads can't change the processor's IRQL. This means that a processor's IRQL is always at passive level when it's executing user-mode code. Only when the processor is executing kernel-mode code can the IRQL be higher. Each interrupt level has a specific purpose. For example, the kernel issues an inter-processor interrupt (IPI) to request that another processor perform an action, such as dispatching a particular thread for execution or updating its translation lookaside buffer cache. The system clock generates an interrupt at regular intervals, and the kernel responds by updating the clock and measuring thread execution time. If a hardware platform supports two clocks, the kernel adds another clock interrupt level to measure performance. The HAL provides a number of interrupt levels for use by interrupt-driven devices; the exact number varies with the processor and system configuration. The kernel uses software interrupts (described later in this chapter) to initiate thread scheduling and to asynchronously break into a thread's execution.
Figure 2 Interrupt request levels (IRQLs) Interrupts are serviced in priority order, and a higher-priority interrupt preempts the servicing of a lower-priority interrupt. When a high-priority interrupt occurs, the processor saves the interrupted thread's state and invokes the trap dispatchers associated with the interrupt. The trap dispatcher raises the IRQL and calls the interrupt's service routine. After the service routine executes, the interrupt dispatcher lowers the processor's IRQL to where it was before the interrupt occurred and then loads the saved machine state. The interrupted thread resumes executing where it left off. When the kernel lowers the IRQL, lower-priority interrupts that were masked might materialize. If this happens, the kernel repeats the process to handle the new interrupts. Each processor's IRQL setting determines which interrupts that processor can receive. Because accessing a PIC is a relatively slow operation, HALs that use a PIC implement a performance optimization, called lazy IRQL, that avoids PIC accesses. When the IRQL is raised, the HAL notes the new IRQL internally instead of changing the interrupt mask. If a lower-priority interrupt subsequently occurs, the HAL sets the interrupt mask to the settings appropriate for the first interrupt and postpones the lower-priority interrupt until the IRQL is lowered. Thus, if no lower-priority interrupts occur while the IRQL is raised, the HAL doesn't need to modify the PIC. Operating systems
54
Mapping interrupts to IRQLs These IRQL levels aren't the same as the interrupt requests (IRQs) of the x86 system—the x86 architecture doesn't implement the concept of IRQLs in hardware. So how does Windows 2000 determine what IRQL to assign to an interrupt? The answer lies in the HAL. In Windows 2000, a type of device driver called a bus driver determines the presence of devices on its bus (PCI, USB, and so on) and what interrupts can be assigned to a device. The bus driver reports this information to the Plug and Play manager, which decides, after taking into account the acceptable interrupt assignments for all other devices, which interrupt will be assigned to each device. Then it calls the HAL function HalpGetSystemInterruptVector, which maps interrupts to IRQLs. The algorithm for assignment differs for the uniprocessor and multiprocessor HALs that Windows 2000 includes. On a uniprocessor system, the HAL performs a straightforward translation: the IRQL of a given interrupt vector is calculated by subtracting the interrupt vector from 27. Thus, if a device uses interrupt level 5, its ISR executes at IRQL 22. On a multiprocessor system, the mapping isn't as simple. APICs support over 100 interrupt vectors, so there aren't enough IRQLs for a one-toone correspondence. The multiprocessor HAL therefore assigns IRQLs to interrupt vectors in a round-robin manner, cycling through the device IRQL (DIRQL) range. As a result, on a multiprocessor system there's no easy way for you to predict or to know what IRQL Windows 2000 assigns to APIC IRQs. You can use the !idt kernel debugger command (shown earlier in the chapter) to view IRQL assignments for hardware interrupts. Operating systems
55
Predefined IRQLs Let's take a closer look at the use of the predefined IRQLs, starting from the highest level shown in Figure 2: • • •
• •
• • •
The kernel uses high level only when it's halting the system in KeBugCheckEx and masking out all interrupts. Power fail level originated in the original Microsoft Windows NT design documents, which specified the behavior of system power failure code, but this IRQL has never been used. Inter-processor interrupt level is used to request another processor to perform an action, such as dispatching a particular thread for execution, updating the processor's translation look-aside buffer (TLB) cache, system shutdown, or system crash. Clock level is used for the system's clock, which the kernel uses to track the time of day as well as to measure and allot CPU time to threads. The system's real-time clock uses profile level when kernel profiling, a performance measurement mechanism, is enabled. When kernel profiling is active, the kernel's profiling trap handler records the address of the code that was executing when the interrupt occurred. A table of address samples is constructed over time that tools can extract and analyze. The Windows 2000 resource kits include a tool called Kernel Profiler (Kernprof.exe) that you can use to configure and view profiling-generated statistics. See the Kernel Profiler experiment for more information on using Kernprof. The device IRQLs are used to prioritize device interrupts. (See the previous section for how hardware interrupt levels are mapped to IRQLs.) DPC/dispatch-level and APC-level interrupts are software interrupts that the kernel and device drivers generate. (DPCs and APCs are explained in more detail later in this chapter.) The lowest IRQL, passive level, isn't really an interrupt level at all; it's the setting at which normal thread execution takes place and all interrupts are allowed to occur. 3.1.6.4. Windows 2000 and Real-Time Processing
Deadline requirements, either hard or soft, characterize real-time environments. Hard real-time systems (for example, a nuclear power plant control system) have deadlines that the system must meet to avoid catastrophic failures such as loss of equipment or life. Soft real-time systems (for example, a car's fuel-economy optimization system) have deadlines that the system can miss, but timeliness is still a desirable trait. In real-time systems, computers have sensor input devices and control output devices. The designer of a real-time computer system must know worst-case delays between the time an input device generates an interrupt and the time the device's driver can control the output device to respond. This worst-case analysis must take into account the delays the operating system introduces as well as the delays the application and device drivers impose. 3.1.6.5. Software Interrupts Although hardware generates most interrupts, the Windows 2000 kernel also generates software interrupts for a variety of tasks, including these: •
• • • •
Non-time-critical interrupt processing Handling timer expiration Asynchronously executing a procedure in the context of a particular thread Supporting asynchronous I/O operations 3.1.6.6. Exception Dispatching
In contrast to interrupts, which can occur at any time, exceptions are conditions that result directly from the execution of the program that is running. Win32 introduced a facility known as structured exception handling, which allows applications to gain control when exceptions occur. The application can then: o o o
fix the condition and return to the place the exception occurred, unwind the stack (thus terminating execution of the subroutine that raised the exception), or declare back to the system that the exception isn't recognized and the system should continue searching for an exception handler that might process the exception.
This section assumes you're familiar with the basic concepts behind Win32 structured exception handling—if you're not, you should read the overview in the Win32 API reference documentation on the Platform SDK or chapters 23-25 in Jeffrey Richter's book Programming Applications for Microsoft Windows (fourth edition, Microsoft Press, 2000) before proceeding. Keep in mind that although exception handling is made accessible through language extensions (for example, the __try construct in Microsoft Visual C++), it is a system mechanism and hence isn't language-specific. Other examples of consumers of Windows 2000 exception handling include C++ and Java exceptions. On the x86, all exceptions have predefined interrupt numbers that directly correspond to the entry in the IDT that points to the trap handler for a particular exception. Table 3-2 shows x86-defined exceptions and their assigned interrupt numbers. Because the first entries of the IDT are used for exceptions, hardware interrupts are assigned entries later in the table, as mentioned earlier.
Table 1 x86 Exceptions and Their Interrupt Numbers Interrupt Number
Exception
0
Divide Error
1
DEBUG TRAP
2
NMI/NPX Error
3
Breakpoint
Initiating thread dispatching
Operating systems
56
Operating systems
57
4
hardware transfers control to the kernel trap handler, which creates a trap frame (as it does when an interrupt occurs). The trap frame allows the system to resume where it left off if the exception is resolved. The trap handler also creates an exception record that contains the reason for the exception and other pertinent information.
Overflow
5
BOUND/Print Screen
6
Invalid Opcode
7
NPX Not Available
8
Double Exception
9
NPX Segment Overrun
A
Invalid Task State Segment (TSS)
B
Segment Not Present
C
Stack Fault
D
General Protection
E
Page Fault
F
Intel Reserved
10
Floating Point
11
Alignment Check
If the exception occurred in kernel mode, the exception dispatcher simply calls a routine to locate a frame-based exception handler that will handle the exception. Because unhandled kernel-mode exceptions are considered fatal operating system errors, you can assume that the dispatcher always finds an exception handler. If the exception occurred in user mode, the exception dispatcher does something more elaborate. The Win32 subsystem has a debugger port and an exception port to receive notification of user-mode exceptions in Win32 processes. The kernel uses these in its default exception handling. Debugger breakpoints are common sources of exceptions. Therefore, the first action the exception dispatcher takes is to see whether the process that incurred the exception has an associated debugger process. If so, it sends the first-chance debug message (via an LPC port) to the debugger port associated with the process that incurred the exception. (The message is sent to the session manager process, which then dispatches it to the appropriate debugger process.)
All exceptions, except those simple enough to be resolved by the trap handler, are serviced by a kernel module called the exception dispatcher. The exception dispatcher's job is to find an exception handler that can "dispose of" the exception. Examples of architecture-independent exceptions that the kernel defines include memory access violations, integer divide-by-zero, integer overflow, floating-point exceptions, and debugger breakpoints. For a complete list of architectureindependent exceptions, consult the Win32 API reference documentation.
If the process has no debugger process attached, or if the debugger doesn't handle the exception, the exception dispatcher switches into user mode and calls a routine to find a frame-based exception handler. If none is found, or if none handles the exception, the exception dispatcher switches back into kernel mode and calls the debugger again to allow the user to do more debugging. (This is called the secondchance notification.)
The kernel traps and handles some of these exceptions transparently to user programs. For example, encountering a breakpoint while executing a program being debugged generates an exception, which the kernel handles by calling the debugger. The kernel handles certain other exceptions by returning an unsuccessful status code to the caller.
The default "debugger" on Windows 2000 is \Winnt\System32\Drwtsn32.exe (Dr. Watson), which isn't really a debugger but rather a postmortem tool that captures the state of the application "crash" and records it in a log file (Drwtsn32.log) and a process crash dump file (User.dmp), both found by default in the \Documents And Settings\All Users\Documents\DrWatson folder. To see (or modify) the configuration for Dr. Watson, run it interactively—it displays a window with the current settings.
A few exceptions are allowed to filter back, untouched, to user mode. For example, a memory access violation or an arithmetic overflow generates an exception that the operating system doesn't handle. An environment subsystem can establish framebased exception handlers to deal with these exceptions. The term frame-based refers to an exception handler's association with a particular procedure activation. When a procedure is invoked, a stack frame representing that activation of the procedure is pushed onto the stack. A stack frame can have one or more exception handlers associated with it, each of which protects a particular block of code in the source program. When an exception occurs, the kernel searches for an exception handler associated with the current stack frame. If none exists, the kernel searches for an exception handler associated with the previous stack frame, and so on, until it finds a frame-based exception handler. If no exception handler is found, the kernel calls its own default exception handlers. When an exception occurs, whether it is explicitly raised by software or implicitly raised by hardware, a chain of events begins in the kernel. The CPU Operating systems
58
Operating systems
59
3.2.
4.
Shell
In het eerste hoofdstuk zagen we hoe een operationele computer kan voorgesteld worden als een virtuele machine. Zo'n machine kan echter nog niet communiceren met gebruikers, vermits de buitenste laag -het operating system- enkel kan communiceren via system calls, en mensen niet praten in termen van system calls. Hetgeen nodig is, is een tolk, een commando-vertolker: een instantie tussen het O.S. en de gebruikers. Zo'n vertolker wordt meestal een shell-programma genoemd, omdat het als een dunne schil rond de eerder genoemde virtuele machine ligt. Let wel: de shell maakt deel uit van de zogenaamde 'systeemsoftware', maar niet van het operating system! Niets houdt een gebruiker tegen zelf een andere shell te schrijven of te installeren. Die gebruiker heeft echter geen toegang tot de code van het operating system; hij kan bijvoorbeeld niets veranderen aan de software die de harde schijf bestuurt. Van deze werkwijze wordt afgeweken bij Windows van Microsoft en bij MacOs van Apple. Daar maakt de commandovertolker onverbrekelijk deel uit van het O.S. Dat heeft voor gevolg dat je als gebruiker gebonden bent aan de geboden user interface: je kan geen andere kiezen; hoogstens kunnen een aantal parameters aangepast worden. Voor modale gebruikers levert dat meestal geen problemen op.
Processen
Een centraal idee, zo niet het belangrijkste concept binnen operating systems, is dat van processen. Om die reden is de voorstelling ervan niet gebeurd in het vorige hoofdstuk, maar is gans dit gedeelte er aan besteed. Zoals reeds eerder is gezegd, zullen we ons bij de studie van bedrijfssystemen dikwijls baseren op een gelaagd model. We zullen daarbij starten met de onderste laag, de Process manager. In essentie is het de taak van die module ervoor te zorgen dat alle bovenliggende lagen niet geconfronteerd worden met o.a. de volgende 'lastige dingen des levens': o o o o
interrupts; het feit dat de werking van programma's niet continu gebeurt, maar in een aaneenschakeling van korte tijdsintervallen; sommige programma's zijn belangrijker dan andere en verdienen dan ook een hogere prioriteit. alle machine-afhankelijke toestandjes;
4.1.
Pseudoparallellisme
Bij de systemen die we zullen bekijken, is er één CPU aanwezig en zitten er tegelijkertijd verschillende actieve programma's in het interne geheugen. De processor verdeelt zijn tijd over alle aanwezige programma's door telkens een aantal milliseconden aan elk van hen te werken en dan over te schakelen naar een volgende. Voor de gebruiker(s) lijkt het dan alsof alle programma's 'tegelijkertijd' lopen. Zo'n toestand noemen we pseudoparallellisme: het is net alsof alle programma's parallel lopen, terwijl het in werkelijkheid niet zo is, voor de eenvoudige reden dat er slechts één CPU aanwezig is. In afbeelding 4-1 ( A ) is dit schematisch voorgesteld.
Systeembeheerders, die allerhande ingrepen op de werking van het O.S. moeten uitvoeren, zijn echter ook beperkt in de keuze van die ingrepen: enkel diegene die via de grafische user interface (GUI) aangeboden worden, kunnen uitgevoerd worden! In veel gevallen zou een systeembeheerder dan ook flink gebaat zijn te kunnen beschikken over een tekst-georiënteerde shell.
1.
3.
CPU
B
CPU
4.
CPU
2.
5.
CPU
De werkelijke situatie ( A )
De virtuele situatie ( B )
B C
CPU
Figuur 4-1
Operating systems
60
Operating systems
61
proces 1: Swa bakt een taart.
Het zal duidelijk zijn dat het voor programmeurs volkomen onmogelijk is om code te schrijven met dit gegeven steeds voor ogen.
We kunnen verschillende grootheden onderscheiden: o Swa (CPU) o recept (programma) o ingrediënten (data) o oven, keukengerei (resources) o tijdsduur
Een programma wordt geschreven in een sequentiële optiek: na een instructie wordt de volgende instructie uitgevoerd door de CPU. Dat die zelfde CPU ondertussen ook nog een aantal andere programma's 'bediend' heeft moet onzichtbaar zijn voor een programmeur! De belangrijkste taak van de process manager zal er dan ook in bestaan om er voor te zorgen dat de situatie lijkt zoals afgebeeld in 4 -1 ( B ): het is net of elk programma beschikt over een 'eigen' CPU.
Swa is bezig. Zijn zoon Omerke komt de keuken binnengelopen met een wespesteek (interrupt!). Dit heeft voor gevolg dat:
Centraal binnen de werkwijze om deze vrij moeilijke taak binnen een operating system te verwezenlijken, is het concept van processen. In dit hoofdstuk zal dit begrip omstandig toegelicht worden omdat het de basis vormt van alle hedendaagse theorie in verband met bedrijfssystemen.
4.2.
⇒ proces 1 wordt stilgelegd ⇒ de toestand van proces 1 wordt geregistreerd ⇒ swa start een tweede proces op proces 2: Swa verzorgt een wespesteek.
Omschrijving van het begrip 'proces'
In de literatuur worden vaak vrij uiteenlopende definities gegeven voor dit centraal begrip; de volgende omschrijving is bruikbaar: Een proces is een programma in uitvoering, samen met alle mogelijke gegevens die er betrekking op hebben zoals de code, data, stack, program counter, registers. Het zijn al deze gegevens die moeten worden bijgehouden wanneer een proces in uitvoering, om welke reden ook, tijdelijk onderbroken wordt.
We kunnen weer verschillende grootheden onderscheiden: o Swa (CPU) o -cursus (programma) o kind, EHBO-kit,... (resources) o tijdsduur Wanneer proces 2 is afgelopen, wordt proces 1 terug geactiveerd op de plaats waar het onderbroken werd. Dit eenvoudig voorbeeldje kan ook dienst doen om enkele nieuwe begrippen te introduceren: concurrentie, competitie en synchronisatie.
Bij de uitvoering van een proces wordt een serie instructies uitgevoerd die een eenheid vormt en wordt niet naar de serie instructies van een ander proces gesprongen. Gegevens en stacksecties van het proces worden gelezen en geschreven, maar gegevens en stacksecties van andere processen kunnen niet gelezen of geschreven worden. Processen communiceren met elkaar en met de buitenwereld via system-calls.
4.2.1.
Bij een eerste kennismaking met deze materie is dikwijls het verschil tussen een proces en een programma niet direct duidelijk. Hopelijk brengt het volgende triviale voorbeeldje enige verlichting.
concurrentie:
Processen zijn concurrent ('samen-lopend') wanneer ze ogenschijnlijk tegelijkertijd plaatsgrijpen. In werkelijkheid kan dat echter niet vermits er slechts één processor aanwezig is. Naar buiten toe is het echter alsof ze 'samen lopen' omdat de CPU beurtelings zeer snel eventjes aan elk van de processen werkt. Zo'n toestand is voor de gebruikers aangenaam maar vergt de oplossing van een hele reeks delicate problemen die je waarschijnlijk wel kunt aanvoelen wanneer we nog even in het gezelschap blijven van Swa en Omerke.
4.2.2.
competitie:
Wanneer p1 en p2 beide dezelfde resources nodig hebben (schaar, mes, gootsteen,...) zal Swa wel één en ander moeten organiseren.
Operating systems
62
Operating systems
63
4.2.3.
synchronisatie:
Wanneer een derde proces p3 gecreëerd wordt, namelijk het nuttigen van een maaltijd, is een vorm van synchronisatie tussen p1 en p3 niet ongewenst. Editor Shell
4.3.
Proces hiërarchie
spel
Opererating systems die met het concept van een proces werken, moeten een of andere manier leveren om alle benodigde processen te maken. In zeer eenvoudige systemen of in systemen die bedoeld zijn voor het draaien van één toepassing, is het misschien mogelijk om alle processen die ooit nodig zijn aanwezig te laten zijn wanneer het systeem wordt gestart. Maar in de meeste systemen is er een manier nodig om processen al doende naar behoefte te maken en op te ruimen.
Shell Login3 Login2 Login1
4.3.1.
Processen in UNIX
INIT
Als voorbeeld bekijken we wat er gebeurt bij het opstarten van een UNIX-achtig bedrijfssysteem. Unix is een multiprogramming systeem. Er kunnen dus meerdere onafhankelijke processen tegelijk lopen. Elke gebruiker kan meerdere actieve processen tegelijk hebben, zodat er op een groot systeem honderden of zelfs duizenden processen lopen. Processen kunnen gecreëerd en vernietigd worden door andere processen zodat een hiërarchische structuur ontstaat van 'ouders' en 'kinderen'. Het moeder- (of vader-) proces van alle andere is Init, proces 1, een proces dat aanwezig is op de boot-disk en steeds bij het opstarten geactiveerd wordt. Init kijkt hoeveel terminals aangesloten zijn en creëert voor elke terminal een login proces. Zodra iemand inlogt, wordt een shell gemaakt om commando's te ontvangen. Die commando's kunnen op hun beurt dan weer nieuwe processen laten ontstaan. INIT
Login1
Login2
Afbeelding 4 - 3 schetst de situatie waarbij drie terminals aangesloten zijn en op één van beide is iemand bezig met een spelletje, een ander met een editor. Wanneer dat spel beëindigd is, wordt dit gesignaleerd aan het ouderproces (in dit geval de shell) die het kindproces vernietigt. In een actief computersysteem wordt deze procesboom dus gans de tijd opgebouwd en weer afgebroken. Op het moment dat geen enkele gebruiker ingelogd is, blijven enkel de verschillende login processen over en dit tot op het ogenblik dat de systeembeheerder voor welke reden dan ook het ganse systeem stillegt. Voor alle duidelijkheid: wanneer we de situatie uit afbeelding 4 - 2 bekijken vanuit het oogpunt van het inwendige geheugen, dan geeft dat het plaatje 4 - 3.
Figuur 4-3
In praktische termen is een proces op een UNIX systeem de entiteit die wordt gecreëerd door de systeem aanroep fork. Elk proces behalve proces 0 - Proces 0 is een speciaal proces dat “met de hand” wordt gemaakt als het systeem wordt geboot - wordt gecreëerd doordat een ander proces de systeemaanroep fork uitvoert. Deze systeemanroep maakt een exacte kopie van het originele proces. Elk proces heeft één ouderproces, maar een proces kan vele kindprocessen hebben. De kernel geeft elk proces aan met een procesnummer: de proces id of PID. Ouder en kind hebben elk hun eigen geheugeninhoud. Als de ouder vervolgens een van zijn variabelen wijzigt is dat voor het kind niet zichtbaar en omgekeerd. Het feit dat de geheugeninhoud, variabelen, registers en alle andere dingen in het ouder en kindproces identiek zijn leidt tot een probleempje: hoe weten de processen welk van beide de oudercode moet uitvoeren en welk de kindcode? De systeemaanroep fork levert aan het kind een 0 af, aan de ouder de PID van het kindproces. pid = fork();
// als de fork lukt, is pid > 0 voor de ouder.
if (pid < 0)
// fork is mislukt
else if (pid > 0)
Login3 Figuur 4-2
Shell
Shell
Een procesboom
else
{ } { }
spel
/* hier komt de code voor de ouder. /* hier komt de code voor het kind. exec
Editor Tijdens een systeemaanroep exec laadt de kernel een executeerbare file in het geheugen. Deze bestaat uit minstens drie delen: tekst, data en de Afb. 3 - 4 stack.
Operating systems
64
Operating systems
65
Het tekstsegment bevat de machine-instructies waaruit de uitvoerbare code van het programma bestaat. Het wordt geproduceerd doordat de compiler en de assembler het programma naar machinecode vertalen. Het datasegment bevat opslagruimte voor de variabelen, strings, arrays en andere data van het programma. Het bestaat uit twee delen: de geïnitialiseerde data en de ongeïnitialiseerde data. Om historische reden heet dit laatste deel BSS*. Het geïnitialiseerde deel van het datasegment bevat variabelen en compilerconstanten die een beginwaarde moeten hebben wanneer het programma wordt gestart. In C is het bijvoorbeeld mogelijk een string te declareren en deze tegelijk een beginwaarde te geven. Wanneer het programma wordt gestart, verwacht het dat de string zijn beginwaarde heeft. Om deze constructie te implementeren wijst de compiler aan de string een plaats in de adresruimte toe en zorgt ervoor dat deze plaats bij het starten van het systeem de juiste string bevat. Vanuit het OS gezien verschilt geïnitialiseerde data niet veel van programmatekst. Beide bestaan uit bit patronen die door de compiler zijn geproduceerd en die in het geheugen moeten staan wanneer het programma begint.
hardware fout op en plaatst het operating system de ondergrens van het stacksegment enkele duizenden bytes lager. De stackpointer geeft de actuele grootte van de stack aan.
stack
~ ~
Wanneer een programma start is zijn stack niet leeg. Deze bevat alle omgevingsvariabelen ( shell variabelen ) en ook de opdrachtregel die in de shell getypt is om het programma aan te roepen. Op deze manier kan het programma zijn argumenten vinden. wanneer bijvoorbeeld de opdracht cp van naar wordt getypt wordt het programma cp gestart met op de stack de string “cp van naar”, zodat het programma de naam van de twee files waarmee het moet werken kan terugvinden.
~ ~
4.3.2.
MS – DOS is geen multiprogramming-systeem zoals UNIX en kan niet tegelijk meerdere onafhankelijke processen in de machine verwerken. Maar het is ook geen monoprogramming-systeem. Het zit er ergens tussenin. Wanneer het systeem wordt geboot, wordt er één proces gestart, namelijk de command.com. Dit proces wacht op invoer. Wanneer er een regel wordt getypt start command.com een nieuw proces, geeft dat gelegenheid om te draaien en wacht tot het klaar is.
BSS data
Tot hier nog geen verschil met UNIX, waarin de shell normaal gesproken ook wacht tot een opdracht klaar is vooraleer de prompt op het scherm te zetten. Het verschil zit erin dat elke gebruiker een shell mag schrijven die niet wacht, maar onmiddellijk de prompt geeft. En als de gebruiker bij de standaardshell een & na de opdracht typt wacht de shell niet. Een ouderproces hoeft in Unix niet te wachten. Het kan parallel met het kindproces draaien.
tekst
de virtuele adresruimte van een proces
Het bestaan van ongeïnitialiseerde data is eigenlijk Figuur 4-4 niet meer dan een optimalisatie. Wanneer een globale variable niet expliciet wordt geïnitialiseerd, zegt de semantiek van C dat de beginwaarde 0 is. Dit zou men kunnen implementeren door een stuk van de binaire file te nemen dat precies zo groot is als het aantal bytes data en deze allemaal te initialseren, ook diegenen die als standaardwaarde 0 krijgen. Maar om ruimte te sparen in de uitvoerbare file wordt dit niet gedaan? De ongeïnitialiseerde variabelen staan allemaal bij elkaar na de geïnitialiseerde, zodat de compiler alleen een woord in de header hoeft te plaatsen, dat het aantal toe te wijzen bytes vermeldt. In tegenstelling tot het tekstsegment kan het datasegment wél veranderen. Programma’s wijzigen voortdurend hun variabelen en bovendien moeten veel programma’s dynamisch ruimte toewijzen tijfdens de uitvoering. UNIX maakt dit mogelijk door toe te staan dat het datasegment kleiner en groter wordt tijdens de allocatie en de-allocatie van geheugen.. Er is een systeem aanroep ter beschikking waarmee een programma de grootte van zijn datasegment kan instellen. Het derde segment is het stacksegment. Op de meeste machines begint het bovenaan de virtuele adresruimte en groeit naar omlaag in de richting van 0. Als de stack onder de onderkant van het stacksegment komt, treedt er normaal een *
De naam BSS is afkomstig van ‘block started by symbol’, gebruikt op de IBM 7090.
Operating systems
Processen in MS – DOS
66
In MS - DOS kunnen een ouder en een kind niet parallel draaien. Wanneer een ouder een kind genereert, wordt het ouderproces automatisch opgeschort tot het kind klaar is. De ouder kan daar niets tegen doen. Er kunnen zo willekeurig veel processen in het geheugen staan, maar slechts één daarvan is actief. Alle andere zijn opgeschort en wachten tot het kind klaar is. Daarom is MS – DOS , ook al kunnen er meerdere processen in het geheugen staan, geen echt multiprogramming systeem. MS – DOS heeft twee soorten uitvoerbare, binaire bestanden waardoor er twee iets verschillende soorten processen zijn. 4.3.2.1. *.COM bestanden Een file met de extensie .com is een gewone uitvoerbare file. Deze heeft geen header en slechts één segment. De uitvoerbare file is byte voor byte een exacte weergave van de uitvoerbare code. De file wordt ongewijzigd in het geheugen geladen en uitgevoerd. Een proces dat vanuit een .com bestand wordt gestart, heeft één segment met tekst, data en stack. Het is maximaal 64K groot, maar het krijgt wel al het beschikbare geheugen. De stack staat bovenaan het segment van 64K. Als het proces niet van plan is kinderen te generen, kan het op deze manier worden uitgevoerd. Als het wél kinderen wil genereren, moet het het ongebruikte deel van het geheugen met een systeemaanroep teruggeven. Als er geen geheugen wordt vrijgemaakt mislukt het genereren van het kind doordat er te weinig geheugen is. Operating systems
67
zijn eigen core image in een bestand op de schijf schrijven, zijn geheugenallocatie verkleinen, zodat alleen de stub nog plaats heeft en pas dan de fork uitvoeren.
4.3.2.2. *.EXE bestanden Het andere type uitvoerbare file is de .exe file. Een proces dat vanuit zo’n file wordt gemaakt, kan een tekstsegment, een datasegment, een stacksegment en zoveel extra segmenten als nodig hebben. In tegenstelling tot .com files bevatten .exe relocatie informatie zodat ze tijdens het laden op een willekeurige plaats kunnen worden geplaatst. 4.3.2.3. Het Program Segment Prefix De eerste 256 bytes van elk proces in MS – DOS vormen een speciaal datablok: het PSP ( Program Segment Prefix ). Het PSP wordt door het O.S. opgebouwd op het moment waarop het proces wordt gemaakt. Voor .com files wordt het PSP gerekend als deel van de adresruimte van het proces en kan worden geadresseerd met adressen tussen 0 en 255. Daarom begint elk .com proces op adres 256, niet op adres 0 . Maar .exe processen worden boven hun PSP geplaatst, zodat hun adres 0 de eerste byte boven het PSP is. Dit voorkomt dat er 256 bytes van de adresruimte worden verspild. Het PSP bevat de grootte van het programma, een pointer naar het omgevingsblok, de opdrachtstring, een pointer naar het PSP van de ouder en andere informatie. 4.3.2.4. Stub
Figuur 4-5
4.3.2.5. TSR programma’s Wanneer een proces is afgelopen, wordt het geheugen van het proces doorgaans teruggenomen en verdwijnt het proces voorgoed. Maar MS – DOS heeft ook een andere manier waarop een proces kan eindigen. Hierbij krijgt het systeem de opdracht het geheugen niet terug te nemen, maar het proces voor het overige als afgelopen te beschouwen. Op het eerste gezicht ziet het er niet aantrekkelijk uit om een dood proces te hebben dat niet kan worden geactiveerd. Maar we hebben gezien dat processen hun eigen interrupthandlers kunnen installeren. Bijvoorbeeld een toetsenbordhandler die bij elke toetsenbordinterrupt wordt aangeroepen. Deze handler kan in het onbereikbare programma staan. De handler kijkt snel of de toetsaanslag de speciale toets of toetsencombinatie is die de TSR ( Terminate and Stay Resident ) code activeert. Indien dit zo is, komt het TSR programma tot leven en doet wat het moet doen. toepassingsprogramma’s
Editor toestenbord
sneltoets
doe iets
Stub
TSR - programma
andere toets PSP
MS DOS
COMMAND.COM COMMAND.COM In MS – DOS erft een kind de open files van het ouderproces en de bijhorende filepositie, tenzij de files geopend zijn in een speciale modus die dit tegengaat. Als een PSP PSP kind in een file leest of schrijft ziet de ouder, wanneer het kind klaar is, de nieuwe MS - DOS MS - DOS filepositie en kan deze aan het volgende kind meegeven. Files die het kind zelf heeft Interruptvectoren Interruptvectoren geopend worden automatisch gesloten wanneer het kind klaar is. Een kind kan ook een exit status aan zijn ouder meegeven. Wanneer een proces een kind genereert meerdere processen in het geheugen bestaat de kans dat er niet genoeg geheugen is voor het kind en verder afstammelingen. Om de kans dat dit gebeurt te verkleinen, worden veel processen die kinderen kunnen krijgen in twee stukken gebouwd: een “stub” en het eigenlijke programma. Alvorens met een fork een kindproces te genereren kan het ouderproces
Operating systems
terugkeer van interrupt
68
interruptvectoren
Figuur 4-6 De werking van TSR programma’s
Operating systems
69
4.4.
1. Een proces blokkeert omdat het tot de ontdekking komt dat het niet verder kan, bijvoorbeeld omdat de nodige invoer nog niet voorhanden is.
Toestanden van processen
Een proces in uitvoering kan om verschillende redenen onderbroken worden. Zo kan het gebeuren dat een proces gegevens moet verwerken die afkomstig zijn van een ander proces dat die gegevens nog niet kan leveren. Op dat ogenblik kan het eerste niet verder en moet een ander proces de kans krijgen te (her)starten. Men zegt dat het proces blokkeert∗. Een andere reden kan zijn dat de voorziene CPU tijd voor het actieve proces opgebruikt is; het is dan de beurt aan een ander. Om deze verandering te verwezenlijken voert het Operating System een zogenaamde wisseling van procescontext uit. De context van een proces bestaat uit de inhoud van de adresruimte en de inhoud van de hardwareregisters en kernel datastructuren die bij het proces horen.
2. Het proces is in uitvoering in kernel mode. 3. Het proces is niet in uitvoering maar staat om te gaan lopen zodra de kernel het de beurt geeft.
Deze toestanden met de verschillende overgangen ertussen worden op een klassieke manier weergegeven in afbeelding 4- 8. De reden waarom er twee toestanden van non-activiteit bestaan, is om de processen die toch niet verder kunnen (geblokkeerd) geen processortijd toe te kennen: die zou dan toch maar verspild zijn! We komen hierop terug in de bespreking van de scheduling-mechanismen.
4. Het proces slaapt en bevindt zich in het hoofdgeheugen. 5. Het proces staat klaar om te worden uitgevoerd, maar de swapper moet het proces in het hoofdgeheugen plaatsen voordat de kernel het de beurt kan geven om te worden uitgevoerd. 6. Het proces slaapt en de swapper heeft het proces naar het secundaire geheugen gebracht om in het hoofdgeheugen ruimte te maken voor andere processen.
2 Figuur 4-7
3 4
Procestoestanden en transities in UNIX
1. Het proces is in uitvoering in user mode.
1. in uitvoering; 2. geblokkeerd; 3. wachtend, maar nog niet in uitvoering omdat de CPU zich op dat ogenblik met een ander proces bezighoudt.
geblokkeerd
4. De reden van het blokkeren valt weg omdat bijvoorbeeld de nodige invoer geleverd werd. Indien er geen andere processen met een hogere prioriteit staan te wachten, kan het nu van start gaan.
Zoals in bovenstaand schema is geschetst kan de levensduur van een proces worden verdeeld in een aantal toestanden die het proces beschrijven. De volgende lijst bevat de volledige verzameling procestoestanden.
Met dit voor ogen kunnen we 3 toestanden onderscheiden waarin een proces zich kan bevinden :
in uitvoering
3. Volgens de heersende prioriteitsregels is het proces aan de beurt om (terug) gestart te worden.
4.4.1.
Formeel is de context van een proces de vereniging van de context op userniveau, de registercontext en de context op systeemniveau.
1
2. Het O.S. beslist dat het proces lang genoeg gelopen heeft en dat het nu de beurt is aan een ander.
wachtend
7. Het proces komt vanuit kernel mode terug naar usermode, maar de kernel ontneemt het proces de beurt en voert een contextwisseling uit om een ander proces de beurt te geven. 8. Het proces is zojuist gecreëerd, maar staat niet klaar voor uitvoering en slaapt ook niet. Deze toestand is de begintoestand voor alle processen behalve proces 0. 9. Het proces heeft de systeemaanroep exit uitgevoerd en is in de zombietoestand. Het proces bestaat niet meer maar laat een record achter met een exitcode en enkele timingsgegevens, die het ouderproces kan ophalen. De zombietoestand is de eindtoestand van een proces.
Toestanden van een proces
∗
De gebruikte terminologie wil nog al eens variëren binnen concrete systemen, de basisgedachte is echter steeds dezelfde.
Operating systems
70
Operating systems
71
Afbeelding 4 - 8 geeft een volledig beeld van de toestandtransities. De weergegeven events zijn in zoverre kunstmatig dat processen ze niet altijd meemaken, maar ze illustreren verschillende transities tussen toestanden.
9
Interrupt, terugkeer van
proces in uitvoering komt. De toestand “onderbroken” is in feite dezelfde toestand als “klaar voor uitvoering, in geheugen”(3). De stippellijn geeft deze equivalentie aan. Ze zijn apart weergegeven om duidelijk te maken dat een dat in kernel mode loopt alleen de beurt kan ontnomen worden wanneer het op het punt staat om terug naar user mode te keren. Daarom kan de kernel zonodig ook vanuit de toestand “onderbroken” swappen. Uiteindelijk zal de scheduler het proces kiezen voor uitvoering en komt het weer in de toestand “lopend in user mode”(1) waarin het in user mode wordt uitgevoerd.
1
Wanneer een proces een systeemaanroep uitvoert komt het in “lopend in kernel mode”(2) terecht. Veronderstel dat een systeemaanroep I/O van disk nodig heeft en dat het proces moet wachten tot de I/O klaar is. Het komt dan in “slapend, in geheugen” (4) toestand, waarin het slaapt totdat het de melding krijgt dat de I/O klaar is. De hardware geeft een interrupt aan de CPU, de interrupthandler wekt vervolgens het proces, het komt in “klaar voor uitvoering in gebruik”(3).
Interrupt, systeemaanroep
2 onderbreke
4
3
Veronderstel dat het systeem veel processen in uitvoering heeft die niet tegelijk in het hoofdgeheugen passen dan kan de swapper het proces uitswappen om ruimte te maken. Het komt afhankelijk van de toestand waarin het zich bevond, terecht in de toestand “klaar voor uitvoering, geswapt” (5) of “slapend,geswapt” (6).
7
Wanneer een proces klaar is, roept het de systeemaanroep exit aan, wardoor het in de toestand “lopend in kernel mode” (2) en tenslotte in de toestand “zombie” (9).
8 6
5 4.5.
Threads
procestoestanden en transitiediagram De werking van een klassieke multitasking configuratie (één CPU, pseudoparallellisme) kunnen we ons voorstellen zoals afgebeeld in 4 - 9 De weg die de program counter, en dus de activiteit van de CPU, volgt kunnen we voorstellen als een draad (Eng. 'thread') die loopt door de adresruimte.
Figuur 4-8
Het proces komt het toestandssmodel in de toestand “gecreëerd” (8 ) binnen wanneer het ouderproces fork uitvoert en komt uiteindelijk in een toestand waarin het klaar is om te worden uitgevoerd (3 of 5 ). Ter wille van de eenvoud nemen we aan dat het proces in de toestand “klaar voor uitvoering, in het geheugen” (3) komt. De proces-scheduler zal het proces uiteindelijk aanwijzen om te worden uitgevoerd. Dan komt het proces in de toestand “lopend in kernel mode”, waarin het zijn deel van de systeemaanroep fork uitvoert.
1.
B
CPU
C
D Vier pseudo parallele processen
Als het proces met de systeemaanroep klaar is kan het in de toestand “lopend in user mode” (1) komen, waarin het in usermode wordt uitgevoerd. Na enige tijd kan de systeemklok een interrupt naar de processor sturen, waarna het proces weer in de toestand “lopend in kernel mode” terechtkomt. Wanneer de handler voor klokinterrupts klaar is met het afhandelen van de klokinterrupt, kan de kernel besluiten een ander proces de beurt te geven om te worden uitgevoerd, waardoor het eerste proces in de toestand “onderbroken” (7) terechtkomt en het andere Operating systems
Figuur 4-9 A
72
Operating systems
Fork
73
In een klassieke configuratie is het evident dat er per proces één program counter, en dus één thread, bestaat. Wat echter in een niet-klassieke situatie? Niet-klassiek in die zin dat er meerdere CPU's aanwezig zijn. Om je gedachten te richten, denk aan een krachtige computer waarmee grafische voorstellingen van voorwerpen in drie dimensies moeten kunnen gemanipuleerd worden. Een rotatie van het voorwerp betekent het uitvoeren van transformaties van de drie coördinaten. Uit snelheidsoverwegingen kunnen drie CPU's aanwezig zijn die elk parallel één coördinaat voor zijn rekening neemt. De code die daarbij moet uitgevoerd worden is echter drie keer dezelfde, enkel de data verschillen!
We zien dat het concept thread nu belangrijk gaat worden. In een klassieke situatie kunnen we zeggen dat het concept thread praktisch samenvalt met het concept process. In een niet-klassieke configuratie zoals in afbeelding 4 – 10 is er één proces aanwezig en drie threads, waarbij elk van die threads gebruik maakt van dezelfde adresruimte, maar elk een program counter en een set registers bezit.
Ze hebben met z'n drieën de code, data sectie en resources (geopende files, ..) gemeen, terwijl ze elk een eigen program counter, registerwaarden en stack bezitten (afb. 4 -12 ). Dat heeft onder andere als voordeel dat het verwisselen van threads minder extra werk vertegenwoordigt dan wanneer het om processen zou gaan.
CPU 1 CPU 2
CPU 3
A
Dat threads heden ten dage zo belangrijk zijn geworden heeft voor een groot gedeelte te maken met de populariteit van grafische user interfaces: de enorme afmetingen van de Figuur 4-11 grafische bibliotheken die 'meegesleurd' moeten 1 proces – 3 CPU’s worden voor zelfs het allerkleinste programmaatje maken een mechanisme noodzakelijk waarbij code slechts éénmaal in het geheugen aanwezig is en gebruikt kan worden door meerdere processen die dan de naam 'thread' krijgen.
CPU 1 CPU 2
In veel opzichten zijn threads miniprocessen. Elke thread wordt strikt sequentieel uitgevoerd en heeft zijn eigen programma teller en stack om te weten hoe ver hij is. Threads gebruiken de CPU gemeenschappelijk, net als processen: eerst loopt de ene thread, dan de andere ( timesharing ). Threads kunnen kind-threads maken en kunnen blokkeren in afwachting van systeemaanroepen, net als normale processen. Terwijl de ene thread geblokkeerd is, kan een andere thread van hetzelfde proces lopen, precies zoals andere processen op dezelfde machine kunnen lopen wanneer een bepaald proces geblokkeerd is. De analogie dat een thread zich tot een proces verhoudt zoals een proces zich tot een machine verhoudt, gaat in veel opzichten op.
CPU 3
A
We zullen op dit begrip nog terugkomen in het gedeelte over gespreide systemen.
B
Ook in systemen met één CPU hanteert men steeds meer het concept van threads en verdringt het zelfs de notie van proces van de positie van belangrijkste eenheid van uitvoering.
C
Figuur 4-12
D
Figuur 4-10
Drie processen met elk 1 thread
Stel dat drie taken moeten uitgevoerd worden. Met hetgeen we tot hiertoe gezien hebben, zouden dat drie processen vertegenwoordigen, elk met een eigen program counter, registerwaarden, stack, code, ... Maar wat indien het hier toevallig over dezelfde code gaat? Dat zou dan inhouden dat in het geheugen drie stukken identieke code aanwezig zijn. Ook hier biedt het concept van threads een oplossing. De drie vernoemde taken worden nu geen processen genoemd maar zogenaamde lightweight processes, een andere benaming voor threads. Operating systems
74
Programma teller
1 proces met drie threads
De verschillende threads in een proces zijn echter niet even onafhankelijk als verschillende processen. Alle threads hebben precies dezelfde adresruimte, hetgeen betekent dat ze dezelfde globale variabelen gebruiken. Aangezien elke thread toegang heeft tot elk virtueel adres, kan de ene thread de stack van een andere thread lezen, schrijven of zelfs helemaal wissen! Er is geen protectie van de ene thread voor de andere omdat dit ten eerste onmogelijk is en ten tweede niet nodig behoort te zijn. In tegenstelling tot verschillende processen die van verschillende gebruikers kunnen zijn en elkaar niet goed gezind behoeven te zijn, is een bepaald Operating systems
75
proces altijd in bezit van één eigenaar die, zo mag men aannemen, meerdere threads heeft gemaakt om ze te laten samenwerken, niet om ze met elkaar te laten vechten.
4.5.1.
actief is alleen processortijd als de VM, waarin deze toepassing wordt uitgevoerd een tijdschijf krijgt. Zodra de VM de tijdschijf verliest, komt de volgende WIN32 toepassing weer aan de beurt.
Multitasking in Windows 95
Zoals we hierboven beschreven fungeert het proces voor het bedrijfssysteem als een constructie om meerdere parallel verlopende toepassingen te kunnen controleren en van elkaar af te grenzen. Als de gebruiker meerdere toepassingen heeft gestart, zijn ook meerdere processen actief. Meestal gaat het om één proces per toepassing. Onder de processen zijn dan de threads ondergebracht. Dit zijn de uitvoeringseenheden die het eigenlijke verloop van de toepassing realiseren. Het is niet het proces dat bij het opstarten wordt uitgevoerd, maar een als “primary thread” aangeduide initiële thread, die de shell automatisch voortbrengt. Bij elk proces hoort minstens één thread, die zorg draagt voor de uitvoering. De eigenlijke multitasking van het systeem vindt plaats op threads-niveau. Dit wil zeggen dat meerdere threads gelijktijdig worden uitgevoerd.
Win32 toepassing
Win32 toepassing
Hoe sneller de omschakeling tussen de verschillende threads verloopt, des te eerder komt de thread weer aan de beurt en des te sterker wordt de indruk van parallelliteit. Maar er zijn grenzen aan het systeem, omdat er bij elke omschakeling ook een zekere administratieve rompslomp komt kijken. Onder Windows 95 zijn de tijdschijven vastgelegd op 20 milliseconden. Wat natuurlijk nog altijd overeenkomt met 50 thread-omschakelingen per seconden en met 400.000 uitvoerbare opdrachten met een 100 MHz snelle CPU.
Aangezien onder Windows 95 naast de nieuwe 32-bits toepassingen ook de oude 16bits toepassingen moeten blijven functionneren, moeten de twee zeer verschillende en eigenlijke incompatibele multitasking systemen worden ondersteund: het preëmptive van WIN32 en het coöperatieve van WIN16. Alle WIN16 toepassingen worden daarom in een virtuele machine uitgevoerd, die een normale WIN16 omgeving simuleert. Zo krijgt de WIN16 toepassing die op een bepaald moment Operating systems
76
Tijdschijf
Win32 toepassing
Als u meerdere threads met maar één processor parallel ten uitvoer wil brengen, moet u terugvallen op het tijdschijfprincipe. Bij deze procedure wordt de processor in korte afstanden tussen de uitvoering van verschillende threads heen en weer geschakeld.
Aangezien threads bij de tijdschijfprocedure zonder hun toedoen van de uitvoering worden afgehouden en willekeurig onderbroken, wordt hier gesproken van “pre-emptive multitasking”. Dit is precies het tegenovergestelde van de “coöperatieve multitasking” zoals onder Windows 3.1 gebruikelijk is en onder Windows 95 bij het uitvoeren van WIN16 toepassingen wordt gebruikt. Om de aandacht van de processor van een toepassing naar een andere toepassing te leiden is de Windows 3.1 – kern op de toepassingen aangewezen. Hij kan alleen omschakelen als een toepassing een oproep als GetMessage() of PeekMessage() doet en het bedrijfssysteem daarmee de controle over de processor krijgt. De kernel gebruikt deze mogelijkheid om door te gaan met het uitvoeren van een andere toepassing, die voordien ook één van beide functies heeft opgeroepen en daardoor geblokkeerd was geraakt.
Preëmptive multitasking
Coöperatieve
20 milliseconden
multitasking
Win16 toepassing oproep
Win16 API
Win16 toepassing
omschakeling
Figuur 4-13
parallelle verwerking van preëmptive en coöperatieve multitasking
4.6.
Implementatie van processen
Operating systems
77
Wanneer een proces onderbroken wordt, moet alle relevante informatie (program counter, stack, pointer, registers, ...) bijgehouden worden zodat later, bij het terug opstarten alles terug netjes op zijn plaats kan geplaatst worden. Bij de implementatie van zo'n systeem, vereist dit een aantal doordachte datastructuren. We zullen bij het bekijken van enkele concrete bedrijfssystemen hierop nog terugkomen. Om nu toch enigszins een idee te geven over de manieren van implementeren van het concept 'proces', volgt nu een gedeelte uit de code van de kernel van MINIX en meer bepaald het stuk waar de datastructuur voor een proces gedeclareerd wordt. IMPLEMENTATION MODULE Process; (* Title : Process.MOD LastEdit: 01/05/88 Auteur : Jan Beurghs Implementation of the process type. *) TYPE Register PSW
= =
MemoryMap Identif RealTime Ipc
= = = =
sp : pcpsw : PSW; flags : State: memoryMap : MemoryMap; spLimit : pID : Identif; userTime : RealTime; sysTime : RealTime; childTime : RealTime; childSTime : RealTime; alarm : RealTime; ipc : Ipc; nextReady : Proc; pending : PendSig END (* record *);
Operating systems
(* (* (* (* (* (* (* (* (* (* (* (* (* (* (* (* (* (*
Implementatie van processen in WIN2000
4.6.1.1. Data Structures Each Windows 2000 process is represented by an executive process (EPROCESS) block. Besides containing many attributes relating to a process, an EPROCESS block contains and points to a number of other related data structures. For example, each process has one or more threads represented by executive thread (ETHREAD) blocks. (Thread data structures are explained in the section "Thread Internals" later in this chapter.) The EPROCESS block and its related data structures exist in system space, with the exception of the process environment block (PEB), which exists in the process address space (because it contains information that is modified by user-mode code). In addition to the EPROCESS block, the Win32 subsystem process (csrss) maintains a parallel structure for each Windows 2000 process that executes a Win32 program. Also, the kernel-mode part of the Win32 subsystem (Win32k.sys) has a per-process data structure that is created the first time a thread calls a Win32 USER or GDI function that is implemented in kernel mode.
ARRAY Regs OF INTEGER; RECORD psw : INTEGER; codSeg : INTEGER; pc : INTEGER END (* record *); ARRAY Segs,Mem OF INTEGER; INTEGER; LONGINT; RECORD callerQ : Proc; sendLink : Proc; mess : Message; getFrom : PrNumber END (* record *);
Proc = POINTER TO RECORD nmbr : PrNumber; regs : Register;
4.6.1.
place of the process in the process table *) registers of the process *) depends on impl of stack *); stack pointer *) info to push on the stack by interrupt *) sending, receiving, ready,... *) memory map of the process *) depends on impl of stack *); lowest legal stack value *) process ID, passed in from MM *) user time in ticks *) system time in ticks *) cumulative user time of children *) cumulative system time of children *) time of next alarm in ticks, or 0 *) queueing mechanism for IPC *) next ready process *) pending signals : see MM. *)
Figuur 4-14
Figure 4- 14 is a simplified diagram of the process and thread data structures
78
Operating systems
79
Exception local procedure call (LPC) port
Interprocess communication channel to which the process manager sends a message when one of the process's threads causes an exception.
Debugging LPC port
Interprocess communication channel to which the process manager sends a message when one of the process's threads causes a debug event.
Access token (ACCESS_TOKEN)
Executive object describing the security profile of this process. Address of per-process handle table.
Handle table Device map Process environment block (PEB)
Win32 subsystem process block (W32PROCESS)
Address of object directory to resolve device name references in (supports multiple users). Image information (base address, version numbers, module list), process heap information, and thread-local storage utilization. (Note: The pointers to the process heaps start at the first byte after the PEB.) Process details needed by the kernel-mode component of the Win32 subsystem.
This table explains some of the fields in the preceding experiment in more detail and includes references to other places in the book where you can find more information about them. Table 6-1 Contents of the EPROCESS Block The kernel process (KPROCESS) block, which is part of the EPROCESS block, and the process environment block (PEB), which is pointed to by the EPROCESS block, contain additional details about the process object. The KPROCESS block (which is sometimes called the PCB, or process control block) is illustrated in Figure 4-16. It contains the basic information that the Windows 2000 kernel needs to schedule threads. (Page directories are covered in Chapter 7, and kernel thread blocks are described in more detail later in this chapter.)
Figuur 4-15 First let's focus on the process block. (We'll get to the thread block in the section "Thread Internals" later in the chapter.) Figure 4-15 shows the key fields in an EPROCESS block.
Element Kernel process (KPROCESS) block
Purpose Common dispatcher object header, pointer to the process page directory, list of kernel thread (KTHREAD) blocks belonging to the process, default base priority, quantum, affinity mask, and total kernel and user time for the threads in the process.
Process identification
Unique process ID, creating process ID, name of image being run, window station process is running on. Limits on nonpaged pool, paged pool, and page file usage plus current and peak process nonpaged and paged pool usage. (Note: Several processes can share this structure: all the system processes point to the single systemwide default quota block; all the processes in the interactive session share a single quota block Winlogon sets up.
Quota block
Virtual address descriptors (VADs)
Series of data structures that describes the status of the portions of the address space that exist in the process.
Working set information
Pointer to working set list (MMWSL structure); current, peak, minimum, and maximum working set size; last trim time; page fault count; memory priority; outswap flags; page fault history.
Virtual memory information
Current and peak virtual size, page file usage, hardware page table entry for process page directory.
Operating systems
80
Figuur 4-16
Operating systems
81
The PEB, which lives in the user process address space, contains information needed by the image loader, the heap manager, and other Win32 system DLLs that need to be writable from user mode. (The EPROCESS and KPROCESS blocks are accessible only from kernel mode.) The PEB is always mapped at address 0x7FFDF000. The basic structure of the PEB is illustrated in Figure 4-17.
Process: Thread Count
Returns the number of threads in the process.
Process: Handle Count
Returns the number of handles open in the process.
4.6.1.3. Relevant Functions For reference purposes, some of the Win32 functions that apply to processes are described in Table 6-4. For further information, consult the Win32 API documentation in the MSDN Library.
Function CreateProcess CreateProcessAsUser CreateProcessWithLogonW
OpenProcess
Returns a handle to the specified process object
ExitProcess
Ends a process and notifies all attached DLLs
TerminateProcess
Ends a process without notifying the DLLs
FlushInstructionCache
Empties the specified process's instruction cache
GetProcessTimes
Obtains a process's timing information, describing how much time the process has spent in user and kernel mode
GetExitCodeProcess
Returns the exit code for a process, indicating how and why the process shut down Returns a pointer to the command-line string passed to the current process Returns the ID of the current process
Figuur 4-17 GetCommandLine GetCurrentProcessId
4.6.1.2. Performance Counters
GetProcessVersion
Windows 2000 maintains a number of counters with which you can track the processes running on your system; you can retrieve these counters programmatically or view them with the Performance tool. Next table lists the performance counters relevant to processes (except for memory management and I/O-related counters, which are described in chapters 7 and 9, respectively).
GetEnvironmentStrings
Returns the major and minor versions of the Windows version on which the specified process expects to run Returns the contents of the STARTUPINFO structure specified during CreateProcess Returns the address of the environment block
GetEnvironmentVariable
Returns a specific environment variable
Get/SetProcessShutdownParameters
Defines the shutdown priority and number of retries for the current process Returns a count of User and GDI handles
GetStartupInfo
GetGuiResources Object: Counter Process: % Privileged Time
Function Describes the percentage of time that the threads in the process have run in kernel mode during a specified interval.
Process: % Processor Time
Describes the percentage of CPU time that the threads in the process have used during a specified interval. This count is the sum of % Privileged Time and % User Time.
Process: % User Time
Describes the percentage of time that the threads in the process have run in user mode during a specified interval.
Process: Elapsed Time
Describes the total elapsed time in seconds since this process was created. Returns the process ID. This ID applies only while the process exists because process IDs are reused. Returns the process ID of the creating process. This value isn't updated if the creating process exits.
Process: ID Process Process: Creating Process ID
Operating systems
82
Description Creates a new process and thread using the caller's security identification Creates a new process and thread with the specified alternate security token Creates a new process and thread with the specified alternate security token, allowing the user profile to be loaded
4.6.1.4. Relevant Tools A number of tools for viewing (and modifying) processes and process information are available. These tools are included within Windows 2000 itself and within the Windows 2000 Support Tools, Windows 2000 debugging tools, Windows 2000 resource kits, the Platform SDK, and the DDK. The trouble is, you can't get all the information you need with one single tool. However, most information is available from more than one tool, but the data is sometimes identified by different names (and sometimes assigned different values) in each of the tools. To help you determine which tool to use to get the basic process information you need, consult next table. This table isn't a comprehensive list of all the information available about a process—for example, you'll find out what tools you can use to gather memory Operating systems
83
management information in Chapter x —but if you need the basics, you'll find them here.
Net zoals er een EPROCESS block is, is er tevens een executive thread control block of ETHREAD block voor elke thread in een proces. Aangezien de threads bestaan binnen een proces, verwijst het EPROCESS block naar de lijst van ETHREAD blocks. De informatie die de Process Manager moet bijhouden over de threads wordt hier bewaard. Aangezien de thread wordt gebouwd bovenop een kernel thread object, is er ook een Kernel thread control block - KTHREAD block – die de informatie bevat die op Kernel niveau moet bijgehouden worden.
ETHREAD
NT EXECUTIVE
ETHREAD EPROCESS
ETHREAD
NT KERNEL KPROCESS
Figuur 4-18
Figuur 4-19
KTHREAD KTHREAD
4.6.1.5. Samenvatting
KTHREAD
De Executive Process and Thread Manager ( of gewoonweg de Process Manager ) vervult dezelde taken in WIN2K als elkeandere process manager vervult in een ander OS. Het is het gedeelte van het OS dat verantwoordelijk is voor : o o o o
Creatie en vernietiging van processen en threads. Het verschaffen van primitieven voor synchronisatie Controle op de status wijzigingen van processen en threads. Het bijhouden van de informatie die het OS weet over elk proces en thread.
De soliede pijlen stellen pointers voor, de stippelpijlen stellen code voor die de inhoud van de datastructuren manipuleert.
De Proces Manager implemeteer de abstractie van de processen die zal gebruikt worden door de subsysteem en applicatie lagen. Dit betekent dat de PM een aantal datastructuren bepaalt om de status van elke proces en elke thread bij te houden. De basis hiervan wordt een executive process control block of EPROCESS block genoemd. Dit EPROCESS block bevat als informatie: identificatie, resource lijsten en adres-ruimte beschrijvingen. Het EPROCESS block verwijst tevens naar de een Kernel level process control block ( PCB of KPROCESS block ) . Dit bevat de informatie over een proces vanuit het standpunt van de kernel. De Kernel manipuleert zijn deel van de EPROCESS block en de NT Executive is verantwoordelijk voor de overige velden.
Operating systems
84
Operating systems
85
4.6.1.6. Flow of CreateProcess So far in this chapter, you've seen the structures that are part of a process and the API functions with which you (and the operating system) can manipulate processes. You've also found out how you can use tools to view how processes interact with your system. But how did those processes come into being, and how do they exit once they've fulfilled their purpose? In the following sections, you'll discover how a Win32 process comes to life. A Win32 process is created when an application calls one of the process creation functions, such as CreateProcess, CreateProcessAsUser, or CreateProcessWithLogonW. Creating a Win32 process consists of several stages carried out in three parts of the operating system: the Win32 client-side library Kernel32.dll, the Windows 2000 executive, and the Win32 subsystem process (Csrss). Because of the multiple environment subsystem architecture of Windows 2000, creating a Windows 2000 executive process object (which other subsystems can use) is separated from the work involved in creating a Win32 process. So, although the following description of the flow of the Win32 CreateProcess function is complicated, keep in mind that part of the work is specific to the semantics added by the Win32 subsystem as opposed to the core work needed to create a Windows 2000 executive process object. The following list summarizes the main stages of creating a process with the Win32 CreateProcess function. The operations performed in each stage are described in detail in the subsequent sections.
o
Open the image file (.exe) to be executed inside the process.
o
Create the Windows 2000 executive process object.
o
Create the initial thread (stack, context, and Windows 2000 executive thread object).
o
Notify the Win32 subsystem of the new process so that it can set up for the new process and thread.
o
Start execution of the initial thread (unless the CREATE_SUSPENDED flag was specified).
o
In the context of the new process and thread, complete the initialization of the address space (such as load required DLLs) and begin execution of the program.
Figuur 4-20
4.6.1.7. Samenvatting. De WIN32 API functie CreateProcess wordt aangeroepen: De NTOSKRNL functie NtCreateProcess wordt aangeroepen om een proces te creëren. (. ) NTCreateProcess vervult de volgende taken : o Roep de Kernel aan en doe deze een Kernel process object aanmaken. o Creëer en initializeer een EPROCESS block o Creëer een adresruimte voor het proces Daarna wordt NTCreateThread wordt aangeroepen en vervult volgende taken: o o o o
Operating systems
86
Roep de Kernel aan en doe deze een Kernel thread object aanmaken. Creëer en initializeer een ETHREAD block Initialiseer ee thread voor uitvoering ( set de stack, voorzie een uitvoerbaar stack adres en zo voort ) Plaats de thread in een “scheduling queue”
Operating systems
87
5.
Interproces Communicatie (IPC) 5.1.
Probleemstelling
Enigszins vereenvoudigd, maar dat doet hier nu niets ter zake, werkt zo'n print spooler op de volgende manier:
Een van de moeilijkere problemen bij de constructie van een geschikt model voor het proces management, is de onderlinge communicatie tussen processen. Een van de belangrijkste bronnen van problemen is het feit dat we geen enkele veronderstelling kunnen maken aangaande het tijdstip waarop een proces gaat onderbroken worden.
o
Er is een tabel (of lijst) waarin de namen van de te drukken teksten komen te staan. Deze tabel is toegankelijk voor alle user processen (dus: competitie!).
o
Twee variabelen in en out zijn eveneens voor iedereen bereikbaar (competitie!) out: geeft aan welke tekst als volgende zal gedrukt worden. in: geeft de plaats aan waar iemand die iets wil afdrukken zijn tekst moet plaatsen.
o
Een proces, de printer daemon*, dat continu aanwezig is op de achtergrond, kijkt nu en dan of er iets te printen valt, doet dit indien nodig en maakt de plaats in de tabel leeg.
spooler directory
Om de problematiek duidelijk te maken bekijken we als voorbeeld de werking van een print spooler. Dit is een proces dat het afdrukken van teksten voor zijn rekening neemt in opdracht van gebruikersprocessen. Het zorgt er oa.voor dat
• •
Het uitprinten van een tekst door een gebruikersproces gebeurt dus door het volgende te doen:
(a) een user proces niet moet wachten tot het eigenlijke afdrukken voltooid is om verder te kunnen en (b) dat de output voor een printer van verschillende processen, per proces naar de printer gaat, en niet door elkaar geklutst wordt.
4
tekst.txt
5
brief.doc
6
cursus.pdf
IN = 7 o o o o
OUT = 4
Lees de variabele in. Schrijf de naam van je tekst in de tabel op plaats in. Vermeerder in met één. Schrijf in terug weg, ten behoeve van de volgende.
7 8
Vertaald naar een procedure Print, zou die er als volgt kunnen uitzien:
9
De communicatie tussen de processen die output voor de printer genereren en het printproces, bestaat er dus in dat er wordt meegedeeld wat er moet afgedrukt worden.
FROM Spool IMPORT in, tabel; PROCEDURE Print( filenaam: File ) BEGIN ... Lees(in); ZetInTabel(filenaam, in) in := in + 1; Schrijf( in ); ... END Print;
• • •
Op een bepaald ogenblik is de situatie zoals in afbeelding 5 - 1.: proces A
o
proces B Figuur 5-1 *
Een “daemon” ( fantoom ) is een programmaatje dat het grootste gedeelte van de tijd op de achtergrond “slaapt”. Op gezette tijden wordt het via een ingebouwde timer actief im te kijken of er geen werk te doen valt.
twee user processen en een print spooler
Operating systems
Er staan drie teksten X, Y en Z 'te wachten' tot de print spooler tijd heeft om ze naar de printer te sturen.
88
Operating systems
89
o
De eerste tekst die zal afgedrukt worden is text.txt. Dat valt echter onder de verantwoordelijkheid van de spooler en doet hier nu niet ter zake.
o o
Wanneer er een nieuwe tekst bijkomt, dan wordt die op plaats 7 gezet. Er zijn twee processen A en B die teksten produceren voor de printer. A en B zullen dus communiceren met de printer daemon.
5.2.
De oorsprong van deze problemen kan herleid worden tot het feit dat een proces dat iets gemeenschappelijks manipuleert, onderbroken wordt, alvorens hetgeen moest gedaan worden, beëindigd werd. Wat we nodig hebben, is een principe van uitsluiting: op een bepaald ogenblik wordt een proces door een ander proces uitgesloten van het gebruik van iets (een variabele, een file, ...). Wat dus moet gerealiseerd worden is: Dit principe noemt men meestal mutual exclusion of wederzijdse uitsluiting.
Het volgende scenario kan zich nu voordoen: o
A leest in om te kijken waar hij de titel van zijn tekst moet zetten (7) en onthoudt deze waarde (lokaal!).
o
Juist op dit ogenblik gebeurt er een klok-interrupt en het blijkt dat de scheduler heeft beslist dat de tijd van A om is. A wordt onderbroken : A wordt wachtend.
o
B leest in en vindt nog steeds 7 want A had nog geen tijd gehad om er een 8 van te maken.
o
B zet haar tekst op plaats 7 en maakt van in een 8.
o
B hoeft zich van het printen zelf niets meer aan te trekken en gaat verder met andere taken.
o
Na een tijdje is A terug aan de beurt en gaat verder waar hij gebleven was, d.w.z. met waarde 7 voor in.
o
A zet zijn tekst op plaats 7, maakt van in een 8 en gaat verder met andere activiteiten.
Gevolg o
Voor de printer daemon is er niets abnormaals aan de gang, dus hij werkt gewoon door.
o
De tekst van B staat niet meer genoteerd om uitgeprint te worden.
Kritische secties en mutual exclusion
Wanneer een proces een gemeenschappelijke resource gebruikt, zorg er dan voor dat geen enkel ander proces dezelfde resource kan gebruiken, zolang het eerste er niet mee klaar is.
Het vermijden van raceproblemen kan ook abstract worden geformuleerd: o Een deel van zijn tijd voert een proces interne berekeningen uit en andere dingen die niet tot raceproblemen leiden. o Soms zal en proces gemeenschappelijk geheugen of gemeenschappelijke files gebruiken of andere dingen doen die wél tot race kunnen leiden. Figuur 5-2 Wanneer we dus de code van processen bekijken, dan blijkt dat niet elk deel potentieel gevaarlijk is .”Gevaarlijk” in de zin dat het instructies bevat die betrekking hebben op gemeenschappelijke resources. Zo'n stuk dat wèl potentieel gevaarlijk is noemen we een kritische sectie.
niet-kritisch
niet-kritisch
kritisch kritisch
niet-kritisch In afbeelding 5-2 betekent dat, dat wanneer A onderbroken wordt in zijn kritische sectie, B nooit in de zijne mag komen.
Het lastige aan dit soort situaties is dat ze praktisch nooit problemen veroorzaken, maar dan opeens, ogenschijnlijk zonder enige aanleiding, gebeurt er iets dat niet normaal is. In het geval van een printer daemon is de schade nog vrij beperkt maar identieke situaties kunnen zich voordoen op elke plaats waar verschillende processen gemeenschappelijke zaken benaderen. Die 'gemeenschappelijke zaken' kunnen van zeer uiteenlopende aard zijn: intern geheugen, files, randapparaten, ...
niet-kritisch
proces A ( code )
proces B ( code )
Zulke situaties worden meestal race conditions genoemd. Waar we dus voor moeten zorgen, is dat processen niet tegelijkertijd in hun kritische secties zitten.
Operating systems
90
Operating systems
91
5.3.
Wederzijdse uitsluiting met busy waiting.
5.3.2. Reservatie van de gemeenschappelijke resources
In wat volgt zullen we enkele slechte en goede oplossingen bekijken. De slechte zijn geenszins overbodig: ze dienen om inzicht te verwerven in de vrij moeilijke problematiek. Als voorbeeld zullen we het probleem van de printspooler op een vereenvoudigde manier proberen op te lossen. De resources die in dit geval voor de race condition zorgen, zijn de variabele in en de tabel waarover sprake.
5.3.1.
Interrupts uitschakelen
We zouden als volgt kunnen redeneren: 1. Hoe komt het dat B in de kritische sectie komt, terwijl A nog in de zijne zit? Omdat A onderbroken werd tijdens de uitvoering van kritische code. 2. Waarom en hoe werd A onderbroken? Omdat de CPU lang genoeg aan A gewerkt heeft, en B nu aan de beurt is. Het onderbreken zelf gebeurt ten gevolge van een klok-interrupt. 3. Wat kan daaraan gedaan worden? We zetten het interrupt-mechanisme af, dan valt het middel weg om A te onderbreken
FROM Spool IMPORT in, tabel; PROCEDURE Print(filenaam: File) BEGIN (* niet-kritische sectie *) ... Zet_KlokInterrupt_Af; (* begin kritische sectie *) Lees(in); ZetInTabel(filenaam, in) in := in + 1; Schrijf(in); (* einde kritische sectie *) Zet_KlokInterrupt_Aan; ... END Print;
Het is gewoonweg ondenkbaar een gebruikersprogramma controle te geven over zoiets fundamenteels als het interrupt-mechanisme! Op deze wijze is de wederzijdse uitsluiting gerealiseerd. Deze werkwijze voldoet echter niet. Bedenk dat A en B behoren tot het niveau van de gebruikerssoftware. Stel dat één van de processen de interrupts uitzet en ze nooit meer aanzet. Dat zou het einde van het systeem zijn. In een systeem met meerdere CPU’s heeft het uitzetten van de interrupts alleen invloed op de CPU die de uitzetinstructie heeft uitgevoerd. De andere blijven lopen en kunnen nog steeds het gemeenschappelijk geheugen gebruiken.
Laten we de problematiek van een gans andere hoek bekijken: hoe bekom je voor zoiets als een kleedhokje of een toilet een gewenste situatie van mutual exclusion? In een gedisciplineerde samenleving zou een van buitenaf zichtbare aanduiding met "vrij" of "bezet" voldoende zijn. Getransponeerd naar de print spooler betekent dat een extra variabele toestand die twee waarden kan aannemen: "vrij" of "bezet". Het printen van een tekst gebeurt dan als volgt. 1. 2. 3. 4. 5. 6. 7.
Wacht tot toestand op "vrij" staat. Zet toestand op "bezet". Lees de variabele in. Schrijf de naam van je tekst in de tabel op plaats in. Vermeerder in met één. Schrijf in terug weg, ten behoeve van de volgende. Zet toestand op "vrij". PROCES A
FROM Spool IMPORT in, tabel, toestand; PROCEDURE Print(filenaam: File) BEGIN (* niet-kritische sectie *) ... REPEAT (* lege lus *) UNTIL toestand = vrij; toestand = bezet; (* begin kritische sectie *) Lees(in); ZetInTabel(filenaam, in) in := in + 1; Schrijf(in); (* einde kritische sectie *) toestand = vrij; ... END Print;
PROCES B
FROM Spool IMPORT in, tabel, toestand; PROCEDURE Print(filenaam: File) BEGIN (* niet-kritische sectie *) ... REPEAT (* lege lus *) UNTIL toestand = vrij; toestand = bezet; (* begin kritische sectie *) Lees(in); ZetInTabel(filenaam, in) in := in + 1; Schrijf(in); (* einde kritische sectie *) toestand = vrij; ... END Print;
Vermits A en B meestal niet samen aan hun kritische sectie beginnen, zal dit gedurende een tijdje probleemloos werken. Wanneer ze echter beide tegelijk toestand = vrij bemerken, zitten ze samen in hun kritische sectie en kunnen de problemen beginnen. Deze methode is dus niet bruikbaar. Zuiver principieel kunnen we ook zien dat dit geen oplossing biedt. De variabele toestand is namelijk gemeenschappelijk voor A en B, zodat we de kritische sectie gewoonweg vergroot hebben!
Operating systems
92
Operating systems
93
5.3.3.
het proces VerlaatSectie aan om aan te geven dat het klaar is en het andere proces aan zijn kritische sectie mag beginnen.
Strikt om beurten
De volgende oplossing werkt met een “beurt-aanduider”. Deze duidt aan wie de resources mag gebruiken. Een variabele “turn” houdt bij wie eraan de beurt is om aan zijn kritische sectie te beginnen. Proces A kijkt naar turn. Wanneer deze de waarde “Aturn” heeft kan proces A aan zijn kritische sectie beginnen. Wanneer proces A klaar is met zijn kritische sectie verandert het turn in “Bturn”. Proces B kan nu aan zijn kritische sectie beginnen. Een proces dat aan zijn kritische sectie wil beginnen, maar turn niet met de juiste waarde aantreft wacht in een lus, waarbij telkens gekeken wordt of de waarde van turn al veranderd is. Het voortduren testen van een variable tot deze een bepaalde waarde krijgt noemen we busy waiting. Dit moet in het algemeen worden vermeden omdat het nutteloos CPU tijd kost. PROCES A FROM Spool IMPORT in, tabel, turn; PROCEDURE Print(filenaam: File) BEGIN (* niet-kritische sectie *) ... REPEAT (* lege lus *) UNTIL turn = Aturn; (* begin kritische sectie *) Lees(in); ZetInTabel(filenaam, in) in := in + 1; Schrijf(in); turn := Bturn; (* einde kritische sectie *) ... END Print;
PROCES B FROM Spool IMPORT in, tabel, turn; PROCEDURE Print(filenaam: File) BEGIN (* niet-kritische sectie *) ... REPEAT (* lege lus *) UNTIL turn = Bturn; (* begin kritische sectie *) Lees(in); ZetInTabel(filenaam, in) in := in + 1; Schrijf(in); turn := Aturn; (* einde kritische sectie *) ... END Print;
Dit algoritme voorkomt inderdaad het raceprobleem, maar het is geen erg goede oplossing. Wanneer een van de processen veel langzamer is dan het andere moet het snellere toch steeds wachten tot het langzame de beurt overdraagt…
5.3.4.
De oplossing van Peterson
Laten we eens kijken hoe deze oplossing werkt. Aan het begin is geen van beide processen in zijn kritische sectie. Nu roept Proces 0 de procedure BeginSectie aan. Het geeft zijn belangstelling te kennen door zijn array-element TRUE te maken en geeft beurt de waarde 0. Omdat proces 1 niet geïnteresseerd is, is BeginSectie meteen klaar. Als Proces1 nu BeginSectie aanroept blijft het wachten in de lus tot geïnteresseerd[0] FALSE wordt, wat pas gebeurt wanneer proces 0 de procedure VerlaatSectie aanroept.
FROM Spool IMPORT in, tabel; VAR beurt : INTEGER; geïnteresseerd : ARRAY [ 0..1] OF BOOLEAN; PROCEDURE BeginSectie ( proces : INTEGER ) VAR ander : INTEGER BEGIN ander := 1 – proces; geïnteresseerd [ proces ] := TRUE; beurt := proces; WHILE ( beurt = proces ) AND ( geïnteresseerd [ ander ] = TRUE); (* Lege opdracht *) END BeginSectie; PROCEDURE VerlaatSectie ( proces : INTEGER ) BEGIN geïnteresseerd [ proces ] := FALSE; END VerlaatSectie;
PROCEDURE Print(filenaam: File ; proc : INTEGER) BEGIN (* niet-kritische sectie *) ... BeginSectie ( proc ); (* begin kritische sectie *) Lees(in); ZetInTabel(filenaam, in) in := in + 1; Schrijf(in); VerlaatSectie ( proc ); (* einde kritische sectie *) ... END Print;
Door het idee van de beurten te combineren met het idee van de slotvariabele ontdekte in 1981 G.L.Peterson een oplossing waarbij geen strikte afwisseling werd geëist. Deze bestaat uit twee procedures BeginSectie en VerlaatSectie. Alvorens de gemeenschappelijke variabelen te gebruiken ( dus alvorens aan zijn kritische sectie te beginnen ) roept elk proces BeginSectie aan met zijn eigen procesnummer als parameter. Na afloop van het gebruiken van de gemeenschappelijke variabelen roept
Operating systems
94
Operating systems
95
5.3.5.
5.4. Wederzijdse uitsluiting door blokkeren van processen.
Test and Set Lock ( TSL )
We zullen nu een oplossing bekijken waarvoor de hulp van de hardware nodig hebben. Veel computers hebben een instructie TEST AND SET LOCK. Voor de instructie TSL zullen we een gemeenschappelijke variabele vlag gebruiken, waarmee we de toegang tot het gemeenschappelijke geheugen zullen coördineren. De waarde van deze vlag wordt in een register weggeschreven. Wanneer vlag 0 is, mag elk proces er de waarde 1 aan geven en dan in het gemeenschappelijk geheugen lezen of schrijven. Wanneer het proces daarmee klaar is, geeft het vlag weer de waarde 0 met een gewone MOVE instructie.
0 of 1
wanneer een proces aan zijn kritische sectie wil beginnen, kijkt het of dat mag. Zoniet, wacht het in een lus tot het wél mag. register
vlag
Het uitvoeren van de lus is uiteraard verspilde CPU tijd! Laten we eens enkele operaties voor IPC bekijken waar processen blokkeren in plaats van CPU tijd te verspillen wanneer de toegang tot de kritische sectie verboden is. Figuur 5-3
5.4.1.
Hoe kunnen we deze instructie nu gebruiken om te voorkomen dat twee processen tegelijk aan hun kritische sectie beginnen? gemeenschappelijk geheugen
o
De eerste instructie kopieert de oude waarde van vlag naar een register en geeft vlag de waarde 1.
o
Daarna wordt de oude waarde met 0 vergeleken: - als de oude waarde 0 was, was het slot uit en kan het programma dus aan zijn kritische sectie beginnen. - als de ze ongelijk is aan nul, was het slot al aangezet, zodat het programma terug gaat naar het begin en opnieuw test.
o
Vroeg of laat wordt de vlagwaarde 0 - wanneer het proces dat momenteel in zijn kritische sectie zit klaar is zet het immers de vlag weer op 0 - en is de subroutine afgelopen mét het slot op aan.
Het lezen van vlag en het veranderen van de waarde ervan moeten gegarandeerd één ondeelbare handeling zijn. Dit wordt een atomic operation genoemd.
begin_sectie: tsl register,vlag cmp register, #0 jnz begin_sectie ret
| kopieer vlag naar register en maak vlag 1 | was vlag 0 ? | als vlag niet nul was, was het slot aan, dus herbegin | terug naar naar aanroeper; kritische sectie wordt begonnen.
verlaat_sectie mov vlag, #0 ret
| zet een 0 in de vlag | terug naar de aanroeper
Operating systems
De oplossing van Peterson en de oplossing met TSL zijn correct, maar hebben beide het nadeel dat er busy waiting wordt gebruikt. In feite komen deze oplossingen op het volgende neer:
Slaap en wek
Tot de simpelste behoort het tweetal SLAAP en WEK. SLAAP is een systeemaanroep die ervoor zorgt dat het aanroepende proces wordt geblokkeerd, tot een ander proces het wekt. De aanroep WEK heeft één parameter: het te wekken proces. producent
Als voorbeeld zullen we het producent / consument probleem bekijken. (Ook bekend als het probleem van de eindige buffer). Twee processen gebruiken gemeenschappelijk een buffer van vaste grootte. Eén daarvan, de producent, plaatst informatie in de buffer en het andere, de consument, haalt die informatie eruit.
Figuur 5-4
0
1
2
3
4
5
6
…
…
N
consument
Er ontstaat een probleem wanneer de producent een nieuw element wil plaatsen wanneer de buffer al vol is. De oplossing hiervoor is dat de producent gaat slapen en wordt gewekt wanneer de consument één of meer elementen verwijderd heeft. Andersom, als de consument een element uit de buffer wil halen en bemerkt dat deze leeg is, gaat hij slapen tot de producent iets in de buffer plaatst en de consument wekt. Deze methode lijkt eenvoudig maar leidt tot hetzelfde soort raceproblemen als we bij onze spoolerdirectory hebben gezien. 96
Operating systems
97
Om het aantal elementen bij te houden hebben we een variabele aantal nodig. Het maximale aantal elementen dat de buffer kan bevatten is N.
Ons raceprobleem kan optreden omdat de toegang tot aantal niet aan banden is gelegd. Het is mogelijk dat volgende situatie zich voordoet:
De producent moet:
De buffer is leeg De consument leest de variabele aantal en ziet dat deze op 0 staat. De scheduler beslist de uitvoering van de consument tijdelijk te stoppen. De Hij Hij De
o
eerst gaan kijken of aantal gelijk is aan N.
o
Ja: Æ de producent gaat slapen.
o
Neen: Æ hij plaatst een element in de buffer en verhoogt aantal.
o o o o
o
Indien aantal nu 1 is, was de buffer leeg : de consument was dus in slaap en moet gewekt worden.
o o
De consument gaat verder waar hij gebleven was en gaat slapen. Vroeg of laat maakt de producent de buffer vol en gaat ook slapen.
o
Beiden zijn dan voorgoed in slaap!
De consument moet: eerst gaan kijken of aantal gelijk is aan 0.
o
Ja: Æ de producent gaat slapen.
o
Neen: Æ hij haalt een element weg uit de buffer en verlaagt aantal.
o
Indien aantal nu 1 minder is dan N, was de buffer vol : de producent was dus in slaap en moet gewekt worden.
5.4.2.
PROCEDURE Producent ()
PROCEDURE Consument( )
VAR
VAR
WHILE ( TRUE ) DO produceer_element; IF (aantal = N) THEN slaap() plaats_element; aantal := aantal + 1; IF (aantal = 1 ) THEN wek (consument); END Producent;
Om dit probleem op te lossen stelde E.W. Dijkstra voor een integer variabele te gebruiken om het aantal weksignalen te tellen. In zijn voorstel voerde hij een nieuw type variabele in : de semafoor.
BEGIN
NEER • kijkt of de waarde van de semafoor groter is dan 0 o o
element : INTEGER
WHILE ( TRUE ) DO IF (aantal =0) THEN slaap() neem_element; aantal := aantal - 1; IF (aantal = N - 1 ) THEN wek (producent); consumer_element; END Consument;
Ja Æ De waarde wordt met één verlaagd; het proces gaat verder. Neen Æ Het proces gaat slapen.
Het inspecteren van de waarde, het veranderen van de waarde en het eventueel gaan slapen vormen samen één ondeelbare atomaire actie.
OP
•
kijkt of er processen “slapen” op deze semafoor. o Ja Æ Er wordt één proces wakker gemaakt. o Neen Æ De waarde van de semafoor wordt met één verhoogd.
Het inspecteren van de waarde, het veranderen van de waarde en het eventueel wekken van een proces vormen samen één ondeelbare atomaire actie.
Figuur 5-5
Operating systems
Semaforen
Een semafoor kon de waarde 0 hebben, die aangeeft dat er géén weksignalen in voorraad zijn, of een positieve waarde die aangeeft dat er één of meer weksignalen stonden te wachten. Op deze semaforen kunnen twee operaties uitgevoerd worden: NEER en OP.
aantal : INTEGER;
element : INTEGER
producent komt aan de beurt plaatst een element in de buffer. wekt de consument want de buffer was leeg. scheduler beslist de uitvoering van de producent tijdelijk te stoppen.
Ons probleem wordt veroorzaakt omdat er een weksignaal wordt gezonden naar een proces dat ( nog ) niet slaapt. Als het weksignaal niet verloren zou gaan, zou alles in orde zijn.
o
VAR
BEGIN
o o o
98
Operating systems
99
OP en NEER worden als system calls geïmplementeerd, waarbij het Operating System even alle interrupts uitzet terwijl het de semafoor inspecteert, van waarde verandert en zo nodig een proces laat slapen of wekt. Aangezien al deze acties slechts een paar instructies kosten, is het niet erg dat de interrupts uitstaan. Is er meer dan één CPU, kan elke semafoor worden beveiligd door een slotvariable, waarbij de TSL instructie wordt gebruikt om ervoor te zorgen dat slechts één CPU tegelijk de semafoor inspecteert. Oplossing van het producent / consument probleem met semaforen typedef int semafoor ; semafoor semafoor semafoor
5.4.3.
weduit = 1 ; leeg = N ; vol = 0 ;
void producent ()
Eventtellers
In een andere oplossing wordt gebruikt gemaakt van de speciale variable eventteller. Er zijn drie bewerkingen gedefinieerd die op een eventeller E kunnen worden uitgevoerd. Lees (E) Verhoog (E) Wacht_tot (E,w)
void consument ()
int element ;
int element ;
WHILE ( TRUE ) { produceer_element; neer (leeg); neer (weduit); plaats_element (element); op (weduit); op (vol); } }
WHILE ( TRUE ) { neer (vol); neer (weduit); neem_element (element); op (weduit); op (leeg); } }
lever de huidige waarde van E af verhoog E met 1 ( een atomaire actie ) wacht tot E de waarde w of hoger heeft
Let erop dat eventtellers alleen worden verhoogd, nooit verlaagd! define N 100 // aantal posities in de buffer typedef int eventteller ; eventteller in = 0; eventteller uit = 0;
void producent ()
Figuur 5-6 In deze oplossing worden drie semaforen gebruikt. vol leeg weduit
Met semaforen ziet IPC er gemakkelijk uit, vindt u niet ? Geen sprake van! Kijk in de psudocode maar eens goed naar de volgorde van de NEER opdrachten voordat er elementen in de buffer worden geplaatst of eruit worden gehaald. Stel dat de twee NEER opdrachten in de omgekeerde volgorde worden gezet… weduit wordt eerder verlaagd dan leeg in de plaats van later. Het gevolg is dat als de buffer helemaal vol is de producent blokkeert terwijl weduit nog steeds de waarde 0 heeft. De volgende keer dat de consument probeert toegang te krijgen tot de buffer vindt hij weduit op 0 en blokkeert. Resultaat is dan dat beide processen geblokkeerd zijn. Deze situatie noemen we deadlock.
om het aantal gevulde posities bij te houden om het aantal lege posities bij te houden zorgt ervoor dat producent en consument niet tegelijk toegang tot de buffer hebben.
vol is aanvankelijk 0, leeg is gelijk aan het aantal posities in de buffer en weduit is 1. In feite gebruiken we semaforen hier op twee verschillende manieren. De semafoor weduit wordt voor wederzijdse uitsluiting gebruikt en kan als waarde slechts 1 of 0 hebben. Dit worden binaire semaforen genoemd. De andere semaforen worden voor synchronisatie gebruikt. Vol en leeg zijn nodig om te garanderen dat bepaalde gebeurtenissen wel of niet optreden. In dit geval zorgt vol ervoor dat de producent geblokkeerd wordt wanneer de buffer vol is, leeg zorgt ervoor dat de consument geblokkeerd wordt wanneer de buffer leeg is.
void consument ()
int element , rangnr = 0;
int element, rangnr = 0 ;
WHILE ( TRUE ) { produceer_element; rangnummer := rangnr +1; wacht_tot ( uit, rangnr – N ); plaats_element ; verhoog (in); } }
WHILE ( TRUE ) { rangnr = rangnr + 1; wacht_tot (in, rangnr); neem_element (element); verhoog (uit); consumeer_element; } }
Figuur 5-7 In het producent / consument probleem wordt geen gebruik gemaakt van de operatie Lees, maar deze operatie is voor andere synchronisatie problemen wel nodig. Er worden twee tellers gebruikt. De teller in telt het cumulatieve aantal elementen dat de producent in de buffer heeft geplaatst sinds het programma gestart is. De
Operating systems
100
Operating systems
101
Spool
teller uit telt het cumulatieve aantal elementen dat de consument tot nu toe uit de buffer heeft verwijderd. Het is duidelijk dat in groter dan of gelijk aan uit moet zijn, maar dat het verschil niet groter mag zijn dan de grootte van de buffer. Wanneer de producent een nieuw element heeft geproduceerd kijkt hij met de systeemaanroep wacht_tot of er ruimte is in de buffer. Aanvankelijk is uit gelijk aan nul en rangnr – N negatief, zodat de producent niet geblokkeerd wordt. De logica van de consument is van dezelfde eenvoud: Alvorens te proberen het k-de element te verwijderen wacht hij gewoon tot in gelijk is geworden aan k ; dus tot de producent k aantal elementen heeft geproduceerd.
5.4.4.
Het doorgeven van berichten
Een probleem met semaforen en eventtellers is dat ze zijn ontworpen om het probleem van wederzijdse uitsluiting op te lossen op één of meer CPU’s die allemaal toegang hebben tot een gemeenschappelijk geheugen. Door de semaforen of eventtellers in het gemeenschappelijk geheugen te plaatsen en ze te beveiligen met TSL instructies kunnen we raceproblemen voorkomen. Wanneer we overstappen naar een gedistribueerd systeem bestaande uit verschillende CPU’s elk met zijn eigen geheugen en verbonden door een lokaal netwerk zijn deze bouwstenen niet meer bruikbaar. Een andere methode om interprocescommunicatie te verwezelijken is ook binnen gespreide systemen heel bruikbaar en is gebaseerd op het concept van messages. Processen kunnen berichten (messages) naar elkaar sturen. Een bericht bestaat o.a. uit de bestemmeling, de afzender en een inhoud. Het O.S. levert twee primitieven waarmee berichten kunnen uitgewisseld worden: Send o o o
(bestemmeling, inhoud): Een proces zendt een bericht naar een ander. Indien dat ander proces daar niet op zit te wachten, blokkeert de afzender. Wanneer de bestemmeling het bericht aanvaardt, wordt het zendende proces gedeblokkeerd.
Receive (afzender, inhoud): o Een proces aanvaardt een bericht, afkomstig van een afzender. o Indien er echter geen bericht verzonden werd, blokkeert het proces. o Het proces wordt gedeblokkeerd wanneer dat wel het geval wordt.
proces A
proces B
FROM Spool IMPORT in, tabel; PROCEDURE Print(filenaam: File) BEGIN (* niet-kritische sectie *) ... Send(Spool, bericht_van_A); (* begin kritische sectie *) Lees(in); ZetInTabel(filenaam, in) in := in + 1; Schrijf(in); (* einde kritische sectie *) Receive(Spool, antwoord); ... END Print;
FROM Spool IMPORT in, tabel; PROCEDURE Print(filenaam: File) BEGIN (* niet-kritische sectie *) ... Send(Spool, bericht_van_B); (* begin kritische sectie *) Lees(in); ZetInTabel(filenaam, in) in := in + 1; Schrijf(in); (* einde kritische sectie *) Receive(Spool, antwoord); ... END Print;
Figuur 5-8 Bekijken we nog eens ons voorbeeld met de print spooler:
Deze specifieke werking heet meestal message passing with rendez vous en is één van de manieren die gebruikt worden om op dit niveau het probleem van IPC te benaderen.
Operating systems
MODULE Spool; … LOOP DoeVanalles; … Receive (van iedereen,...) ; DoeWatMoetGebeuren; Send (naarAfzender,... ) ; ... END (* loop *) ....
102
•
Bij het opstarten van het systeem wordt de spooler geactiveerd. Hij zal blokkeren op de receive-opdracht; er is immers nog niemand die iets wil afdrukken. Vermits hij geblokkeerd is neemt hij geen processortijd in beslag.
•
A wil printen en voert bovenstaande procedure uit: een bericht gaat naar de spooler. Let wel: in deze context is de inhoud van het bericht zelfs van geen belang, het dient enkel om de spooler te deblokkeren. A zit nu in de kritische sectie en de spooler bereikt 'DoeWatMoetGebeuren'.
•
Bereikt de spooler als eerste zijn send-instructie, dan blokkeert hij tot wanneer A in zijn Print procedure de receive-instructie uitvoert. In het andere geval blokkeert A tot de spooler zijn send-instructie uitvoert.
Operating systems
103
•
Wat gebeurt er nu indien de Print procedure van A onderbroken wordt in de kritische sectie en B wil ook printen? B zal blokkeren op haar send-opdracht vermits de spooler nog niet aan een volgende cyclus van de loop-structuur begonnen is. B zal net zolang in deze toestand blijven tot A voorbij zijn receiveinstructie is, en dus voorbij zijn kritische sectie!
kritische sectie nzorgt ervoor dat slechts één proces tegelijk de datastructuur kan veranderen zodat raceproblemen worden voorkomen.
Probleem opgelost. Of toch niet? Wat indien de Send of de Receive procedure zelf op een lastig moment onderbroken worden? Indien dat het geval is, dan werd de problematiek uiteraard voor ons uit geschoven! De oplossing zit hem in het feit dat beide procedures deel uitmaken van de systeemsoftware. Deze systeemsoftware zal andere technieken zoals semaforen en TSL gebruiken om de procedures Send en Receive te implementeren.
Mailboxen
Er zijn met systemen gebaseerd op messaging veel varianten mogelijk. In de hierboven voorgestelde oplossing wordt er geen gebruik gemaakt van buffers. Een andere mogelijkheid zou er dus in kunnen bestaan een nieuwe datastructuur, een mailbox, te bedenken. Een mailbox is dan een plaats waar een bepaald aantal berichten kan worden gebufferd.
volle posities
volle posities
lege posities
lege posities
zendwachtrij
zendwachtrij
ontvangwachtrij
ontvangwachtrij
bericht
bericht
bericht
bericht
….
….
bericht
bericht
5.4.5. Het gebruik van semaforen om messaging te implementeren. Laten we eens bekijken hoe het doorgeven van berichten met semaforen kan worden geïmplementeerd. •
Bij elk proces hoort een semafoor die aanvankelijk 0 is en waarop het proces blokkeert wanneer een Send of Receive op voltooiing moet wachten
•
Een gemeenschappelijk gebruikt buffergebied zal worden gebruikt voor mailboxes, elk met een array van berichtposities. De posities in elke mailbox worden in een gelinkte lijst gekoppeld, zodat berichten worden afgeleverd in dezelfde volgorde waarin ze ontvangen zijn.
•
Elk mailbox heeft integer variabelen die aangeven hoeveel posities er vol zijn en hoeveel er leeg zijn.
•
Tenslotte bevat elke mailbox ook het begin van twee wachtrijen, één voor processen die niet in staat zijn naar de mailbox te zenden en één voor processen die niet in staat zijn van de mailbox te ontvangen. Deze wachtrijen hoeven alleen de procesnummers van de wachtende processen te leveren zodat er een OP kan worden uitgevoerd op de relevante semafoor.
•
De gehele gemeenschappelijke buffer wordt beveiligd door een binaire semafoor weduit om ervoor te zorgen dat slechts één proces tegelijk de gemeenschappelijke datastructuur kan inspecteren en veranderen.
Wanneer er een Send of Receive wordt uitgevoerd op een mailbox waarin minstens één lege of volle positie voorkomt, plaatst of verwijdert de operatie een bericht, werkt de tellers bij en eindigt normaal. Het gebruik van weduit aan het begin en einde van de Operating systems
104
S
S
…..
S
S
semaforen : één per proces
S
semafoor : weduit
Figuur 5-9
Wanneer er een Receive wordt uitgevoerd op een lege mailbox, plaatst het proces dat het bericht probeert te ontvangen zichzelf in de ontvangwachtrij en voert een OP uit op weduit, en onmiddellijk daarna een NEER op zijn eigen semafoor, waardoor het gaat slapen. Wanneer een Send operatie afgelopen is, wordt het bericht – als er ruimte is in de doelmailbox – daar geplaatst en controleert de zender of er in de ontvangende wachtrij van die mailbox processen staan te wachten. Zo Ja, dan wordt het eerste uit de wachtrij verwijderd en voert de zender een OP uit op zijn semafoor. Wanneer een Send niet kan worden afgemaakt omdat de mailbox vol is, plaatst de zender zichzelf eerst in de wachtrij van de doelmailbox, voert een OP uit op weduit en Operating systems
105
een NEER op zijn eigen semafoor. Wanneer een ontvanger later een bericht uit de volle mailbox verwijdert en merkt dat iemand in de wachtrij staat om naar die mailbox te zenden wordt de zender gewekt.
NEER (weduit)
Lege plaatsen in de mailbox ?
Neen
plaats bericht werk datastructuur bij
plaats zichzelf in zendwachtrij OP (weduit) NEER (eigen_semafoor)
Het leven van een filosoof bestaat afwisselend uit een periode waarin hij eet en een periode waarin hij denkt (dit is zelfs voor filosofen een abstractie maar hun andere activiteiten doen hier niet ter zake).
proces in ontvangwachtrij ?
Ja
Neen
o verwijder proces uit rij OP (weduit) OP (proc semafoor)
Het probleem van de dining philosophers
In 1965 stelde Dijkstra een synchronisatieprobleem dat hij het probleem van de etende filosofen (dining philosophers) noemde. Dijkstra loste dit probleem ook op. ledereen die sindsdien een andere synchronisatie-operatie bedacht en wilde aantonen hoe schitterend de nieuwe operatie was, gaf een elegante oplossing van dit probleem. Het probleem kan als volgt worden omschreven. Vijf filosofen zitten aan een ronde tafel. ledere filosoof heeft een bord voor zich staan met glibberige spaghetti. De spaghetti is zo glibberig dat een filosoof twee vorken nodig heeft om de spaghetti to eten. Tussen twee borden ligt steeds een vork. De tafel is weergegeven in figuur 5-11.
verloop van de Send instructie
Ja
5.4.6.
OP (weduit)
o o o o
Figuur 5-11
Wanneer een filosoof honger krijgt, probeert hij de vork aan zijn linker- en rechterhand te pakken. De vorken worden een voor een gepakt in willekeurige volgorde. Als de filosoof twee vorken te pakken heeft, eet hij enige tijd. Hierna worden de vorken weer op de tafel gelegd. Daarna denkt de filosoof weer een poosje.
De belangrijke vraag is of u in staat bent een programma te schrijven voor iedere filosoof. Het programma moet uitvoeren wat hierboven is beschreven en mag niet vastlopen. (Er is wel eens op gewezen dat de eis om twee vorken voor het eten beschikbaar te hebben nogal kunstmatig aandoet. Misschien moeten we dan overschakelen van Italiaans naar Chinees voedsel en in plaats van spaghetti nemen we rijst en in plaats van vorken praten we over eetstokjes.)
Figuur 5-10
Hieronder is de voor de hand liggende oplossing gegeven. De procedure take-fork wacht tot een gespecificeerde work (fork) beschikbaar is en claimt vervolgens deze vork. #define N 5 philosopher(i) int i; { while (TRUE) { think(); take_fork(i); take_fork((i+1) % N); eat(); put_fork(i); put_fork((i+1) % N); } }
Operating systems
106
Operating systems
/* aantal filosofen */ /* nummer van de filosoof 0 - 4 */ /* /* /* /* /* /*
de filosoof denkt */ pak de vork aan de linkerkant van het bord */ pak de vork aan de rechterkant van het bord ; heerlijke spaghetti */ leg de linkervork weer op tafel */ leg de rechtervork weer op tafel */
107
Ongelukkigerwijs is de voor de hand liggende oplossing fout. Stel dat alle filosofen hun linkervork tegelijkertijd vastpakken. Dan zal geen enkele filosoof erin slagen ook de rechtervork te pakken. In dat geval hebben we te maken met deadlock. We kunnen het programma als volgt aanpassen. Na het claimen van de linkervork kijkt het programma eerst of de rechtervork beschikbaar is. Als dat niet zo is, wordt de claim van de betreffende filosoof op de linkervork ongedaan gemaakt. De filosoof wacht enige tijd en herhaalt het hele proces. Dit voorstel leidt ook tot een foute oplossing. Met een beetje pech starten de filosofen dit algoritme tegelijkertijd. Ze pakken tegelijk de vork aan de linkerkant van het bord en merken dat de vork aan de rechterkant van het bord niet beschikbaar is en leggen de vastgepakte linkervork weer op tafel. Dan wachten ze een tijdje, pakken weer tegelijk de vork aan de linkerkant van het bord, enzovoort. Dit hele proces kan zo eeuwig doorgaan. Een situatie als deze, waarbij alle programma's voor onbepaalde tijd worden uitgevoerd maar in feite geen enkele vooruitgang boeken, wordt starvation (verhongering) genoemd. (De situatie wordt ook starvation genoemd als het probleem zich niet in een Italiaans of Chinees restaurant voordoet.) Men kan nu opperen ‘De filosofen merken dat de work aan de rechterkant van het bord al in gebruik is’. De kans dat alle processen in de deadlock-situatie volharden, kan als volgt worden verkleind. De filosofen proberen in plaats van na steeds dezelfde tijdsduur na een willekeurige tijdsduur de vork opnieuw te pakken. Natuurlijk is dit waar, maar er zijn toepassingen denkbaar waar men de voorkeur geeft aan een oplossing die onder alle omstandigheden tot een goed resultaat leidt en die niet kan vastlopen ten gevolge van een onwaarschijnlijke serie toevalsgetallen. (Denk bijvoorbeeld aan een besturing van de veiligheidsvoorzieningen in een kerncentrale.) Een verbetering van de vorige oplossing die geen deadlock of starvation veroorzaakt, is het beschermen van de vijf instructies na de aanroep van de procedure think met een binaire semafoor. Voordat de filosoof twee vorken probeert to pakken, voert het filosoofproces een DOWN uit op de binaire semafoor mutex. Nadat de vorken weer op de tafel zijn geplaatst, voert de filosoof een UP uit op mutex. Theoretisch is dit een afdoende oplossing. Vanuit een praktisch oogpunt is bezwaar te maken tegen deze oplossing. De performance is slecht, er kan namelijk telkens maar een filosoof eten. Omdat er vijf vorken beschikbaar zijn, willen we de mogelijkheid niet uitsluiten dat twee filosofen tegelijkertijd eten. De onderstaande oplossing zoals weergegeven is juist en bewerkstelligt een zo hoog mogelijke graad van parallellisme voor een willekeurig aantal filosofen. Er wordt gebruik gemaakt van een array state, waarin wordt bijgehouden of een filosoof denkt, eet of hongerig is (en twee vorken probeert to pakken te krijgen). Een filosoof kan alleen in de eet-toestand komen als de filosofen aan weerszijden niet aan het eten zijn. De filosofen aan weerszijden van filosoof i worden gedefinieerd door de macro's LEFT en RIGHT Met andere woorden als i de waarde 2 heeft, is de waarde van LEFT 1 en van RIGHT 3. Het programma maakt gebruik van een array van semaforen, waarbij een semafoor per filosoof wordt gebruikt. Processen die hongerige filosofen voorstellen, kunnen worden geblokkeerd als de benodigde vorken niet beschikbaar zijn. We beschikken nu over voldoende achtergrond om deze oplossing zonder verdere hulp te begrijpen.
Operating systems
108
#define N #define LEFT
5 (i-1)%N
#define RIGHT
(i+1)%N
#define THINKING #define HUNGRY #define EATING
0 1 2
typedef int semaphore; int state[N]; semaphore mutex = 1; semaphore s[N]; philosopher(i) int i; { while (TRUE) {
}
think(); take-forks(i); eat(); put forks(i);
/* /* /* /* /*
oneindige herhalingslus */ de filosoof denkt */ pak twee vorken of blokkeer */ heerlijk, spaghetti */ plaats beide vorken weer op de tafel */
}
/* nummer van de filosoof van 0 tot N-1 */ /* betreedt het kritieke gebied */ /* filosoof i is hongerig */ /* probeer twee vorken to pakken */ /* verlaat het kritieke gebied */ /* blokkeer als beide vorken niet kunnen worden verkregen */
}
put forks(i) int 1; { down(&mutex); state[i] = THINKING; test(LEFT); test(RIGHT); */ up(&mutex); } }
test(i) int i; {
/* semaforen zijn een speciaal soort integers */ /* een array waarin wordt bijgehouden wat de status is van iedere filosoof */ /* wederzijdse uitsluiting van de kritieke gebieden */ /* per filosoof een semafoor */
/* het nummer van de filosoof van 0 tot N-1 */
take forks(i) int 1; { down(&mutex); state[i] = HUNGRY; test(i); up(&mutex); down(&s[i]); }
/* aantal filosofen */ /* nummer van de filosoof aan de linkerkant van filosoof i */ /* nummer van de filosoof aan de rechterkant van filosoof i */ /* de filosoof denkt */ /* de filosoof probeert de vorken to pakken te krijgen */ /* de filosoof eet */
/* nummer van de filosoof van 0 tot N-1 */ /* /* /* /*
betreedt het kritieke gebied */ filosoof i is hongerig */ Stel vast of de filosoof aan de linkerzijde nu kan eten */ Stel vast of de filosoof aan de rechterzijde nu kan eten
/* verlaat het kritieke gebied */
/* nummer van de filosoof van 0 tot N-1 */ if (state[i] = HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING) { state[i] - EATING; up(&s[i]); }
}
Operating systems
109
6.
Process Scheduling 6.1.
6.2.1.
Probleemstelling
Vermits steeds verschillende processen wachtend zijn, moet er een mechanisme aanwezig zijn dat op elk ogenblik bepaalt welk van die processen actief is of het zal worden. Anders gezegd, dit mechanisme bepaalt voor de processor, met welk proces hij zich moet bezighouden. Het maken van deze beslissingen heet scheduling : werkindeling . De eisen waaraan een goed scheduling algoritme moet voldoen zijn dikwijls tegenstrijdig zodat een aanvaardbaar compromis moet worden gezocht: o
Elk proces moet op een zo 'eerlijk' mogelijke manier CPUtijd toegewezen krijgen.
o
De CPU moet zo efficiënt mogelijk benut worden.
o
Voor interactieve gebruikers moet de responstijd zo kort mogelijk gemaakt worden.
o
De tijd die batch gebruikers moeten wachten op output moet zo kort mogelijk zijn.
o
Het aantal afgewerkte taken (bvb. per uur) moet zo hoog mogelijk liggen.
Round Robin
Zoals reeds eerder is vermeld, wordt aan elk proces gedurende een bepaalde tijd (quantum of time slice) gewerkt. De scheduler beschikt over een lijst van wachtende processen waarvan hij telkens het eerste element kiest. Heeft dit proces zijn quantum opgebruikt, verhuist het naar het einde van de lijst. In afbeelding 6-1 (a) is een situatie geschetst waarin zes processen (A..F) actief zouden kunnen zijn: de scheduler kiest het eerste van de rij (A) en maakt dit actief terwijl de andere wachtend blijven. Nadat het quantum van A is 'opgebruikt' en indien A niet geblokkeerd is geraakt, ziet de situatie er uit zoals in afbeelding 6-1 (b).
Figuur 6-1
6.2.
Implementaties
Vermits er geen enkele veronderstelling kan gemaakt worden aangaande het tijdsverloop van een proces, is praktisch altijd het mechanisme ingebouwd waarbij na een bepaalde tijd (bvb. 100 ms) telkens een interrupt gegenereerd wordt. De scheduler beslist dan welk proces nu aan de beurt is. Het onderbroken proces vervoegt de rij van wachtenden. Vermits dit mechanisme vrij frequent in werking treedt, moet het nemen van een beslissing gebeuren volgens een eenvoudig algoritme waarvan de uitvoering niet veel tijd in beslag neemt.
Waar enige aandacht aan moet worden besteed, is de grootte van de time slice. Belangrijk is de verhouding tussen het quantum en de tijd nodig om van proces te wisselen. Nemen we aan dat een proceswissel bijvoorbeeld 5 ms in beslag neemt. Keuze 1: quantum is 20 ms. Gevolg: 20 % CPU-tijd wordt besteed aan andere activiteiten dan de eigenlijke processen. Keuze 2: quantum is 500 ms. Gevolg: minder dan 1 % tijd verloren, wat op het eerste zicht een spectaculaire verbetering is. Maar...stel dat er 10 interactieve gebruikers zijn. Dat betekent dat die alle 10 in de wachtrij kunnen staan (niemand geblokkeerd). Dat heeft voor gevolg dat de tiende gebruiker 5 s moet wachten op respons van het systeem! In de bekeken situatie zou een compromis van rond de 100 ms geschikt
Er bestaan een hele reeks verschillende scheduling algoritmen die dan ook nog gecombineerd kunnen gebruikt worden: hier worden slechts enkele veel voorkomende en eenvoudige algoritmen bekeken.
Operating systems
De implementatie van round robin ('haasje over') is elementair vermits ze slechts enkele bewerkingen op een gelinkte lijst inhoudt. Door zijn eenvoud is het een algoritme dat dikwijls gehanteerd wordt. Deze werkwijze wordt ook preëmptive scheduling genoemd: het actieve proces wordt onderbroken, alhoewel het nog niet beëindigd was.
110
Operating systems
111
zijn. Dit laatste is een voorbeeldje van iets dat we nog regelmatig zullen tegenkomen: het ontmoeten van tegenstrijdige eisen, waardoor de oplossing gezocht moet worden in een compromis.
6.2.2.
De scheduler behandelt eerst de lijst met prioriteit 4 volgens Round Robin. Slechts als de lijst leeg is, wordt aan die met prioriteit 3 begonnen. Allerlei varianten worden gebruikt: processen kunnen bijvoorbeeld verhuizen van wachtrij al naargelang de tijd dat ze staan te wachten.
Priority Scheduling
6.2.3.
Bij Round Robin gaan we er van uit dat alle processen in de wachtrij even belangrijk zijn: 'ieder wacht op zijn beurt'. In de praktijk is dit echter niet het geval. Zo heeft commercieel gezien een klant die meer betaalt dan een andere een hogere prioriteit. Zuiver technisch bekeken heeft een proces dat gegevens wegschrijft op een disk voorrang op alle user processen omdat de draaiende schijf moeilijk eventjes kan stilgezet worden. Deze gedachte ('processen hebben een bepaalde graad van prioriteit') heeft geleid tot het volgende algoritme:
Shortest Job First
Hetgeen we tot hiertoe bekeken valt onder de noemer 'CPU scheduling'. We zullen hier één voorbeeldje bekijken van ‘job scheduling’. Bij batch processen kan men heel dikwijls op voorhand de uitvoeringstijd van een proces bepalen. Denk daarbij bijvoorbeeld aan verrichtingen in het bankwezen: wanneer maandelijks voor elke klant een dossier moet behandeld worden, kan men redelijk goed bepalen hoelang het duurt om 500 dossiers af te handelen. Stel dat vijf jobs moeten uitgevoerd worden en we kennen voor elk de geschatte uitvoeringstijd:
o
elk proces start op met een vooraf bepaalde prioriteit;
o
het proces met de hoogste prioriteit wordt uitgekozen om actief te worden gedurende maximum een quantum;
o
om er voor te zorgen dat het proces met de hoogste prioriteit niet steeds actief is en de andere nooit, wordt de prioriteit van het actieve proces na elk quantum verlaagd.
Heel dikwijls wordt deze prioriteitsidee op een of andere manier gecombineerd met Round Robin: men kan werken met wachtrijen die een bepaalde prioriteit bezitten en binnen elke rij wordt Round Robin toegepast. Schematisch zou zo'n situatie er kunnen uitzien zoals in afbeelding 6-2.
8
4
4
2
2
Tijdseenheden
A
B
C
D
E
JOB
Worden ze in deze volgorde afgehandeld, dan krijgen we de volgende situatie: Tijd na dewelke elke job is afgehandeld: A B C D E
: : : : :
8 12 16 18 20
Dat geeft voor elke taak een gemiddelde van ongeveer 15. Behandelen we ze echter in de volgorde D E B C A, dan krijgen we: D E B C A
: : : : :
2 4 8 12 20
Wat een gemiddelde oplevert van ongeveer 9. Hoe moeten beide getallen nu geïnterpreteerd worden? In de eerste situatie is er gemiddeld om de 15 tijdseenheden -laat ons voor de eenvoud minuten nemen- een taak afgehandeld. In het tweede geval is dat gemiddeld om de 9 minuten. Nou en? Als alles na 20 minuten afgehandeld wordt maakt dat toch geen verschil! Probleem is dat er in de praktijk wel eens één en ander wil mislopen: het papier van de printer is op, een communicatielijn werkt niet naar behoren, ... Stel dat bijvoorbeeld na 7 minuten na het begin van de activiteiten een willekeurige panne optreedt. In de
Figuur 6-2
Operating systems
112
Operating systems
113
tweede situatie zijn op dat ogenblik reeds de jobs D en E voltooid. In de eerste situatie echter nog niets! Bij batch processing heeft men er dus baat bij de kortste jobs eerst te laten uitvoeren. Omdat de methode ‘kortste job’ altijd tot de kleinste gemiddelde tijd leidt, zou het mooi zijn als deze methode ook voor interactieve processen kon worden gebruikt. Tot op zekere hoogte is dat mogelijk. Interactieve processen volgen immers meestal het patroon van wachten op een opdracht, de opdracht uitvoeren, wachten op een opdracht, de opdracht uitvoeren; Als we het uitvoeren van elke opdracht als een afzonderlijke job beschouwen, zouden we de totale responstijd kunnen minimalizeren door de kortste eerst te draaien. Het enige probleem is hoe we kunnen bepalen welk van de uitvoerbare processen het kortst is. Een mogelijke aanpak is een schatting te maken op basis van het het vroegere gedrag en het proces te draaien waarvan de geschatte duur het kortst is. Stel dat de geschatte tijd per opdracht voor een bepaalde terminal T0 blijk te duren. Stel dat het de volgende maal T1 blijkt te duren. We kunnen onze schatting dan aanpassen door een gewogen som van deze twee tijden te nemen.: aT0 + ( 1 – a )T1 Met de keuzen van a kunnen we regelen of oudere tijden snel moeten worden vergeten of juist lang worden onthouden. Deze techniek, waarbij de volgende waarden in een reeks worden geschat door een gewogen gemiddelde te nemen van de nu gemeten waarde en de oude schatting, wordt veroudering genoemd. Deze techniek is toepasbaar in veel situaties waarin een voorspelling moet worden gebaseerd op eerdere waarden.
6.2.4.
Scheduling op twee niveaus
Tot hiertoe is stilzwijgend aangenomen dat alle wachtende processen in het intern geheugen zitten. In de praktijk zal dit niet steeds kunnen en zullen een aantal tijdelijk op disk moeten gezet worden. Dat heeft echter drastische gevolgen voor het scheduling algoritme: een process switch tussen processen in het interne geheugen gaat immers grootte-orden sneller dan een switch met een proces op disk. Om deze situatie baas te kunnen, wordt gewerkt met een scheduler op twee niveaus. o
o
Een algoritme dat enkel rekening houdt met de processen in het interne geheugen (round robin, ...). Een ander algoritme dat zich enkel bezighoudt met te bepalen welke processen lang genoeg in het interne geheugen gezeten hebben en naar disk geswapt moeten worden.
6.2.5.
Tot nu toe hebben we stilzwijgend aangenomen dat alle processen van verschillende gebruikers zijn en dus met elkaar in concurrentie staan wat het gebruik van de CPU betreft. Vaak is dat ook zo, maar soms is er een proces dat veel kinderen heeft die onder het beheer van dit proces lopen. Het is heel goed mogelijk dat het hoofdproces een uitstekend idee heeft welk van zijn kindprocessen het belangrijkst is, en welk het minst belangrijk. Helaas aanvaard geen van de hierboven beschreven schedulers invoer van gebruikerprocessen voor beslisingen. Daarom kan de scheduler zelden de beste keuze maken. De oplossing voor dit probleem is het scheduling-mechanisme te scheiden van het scheduling-beleid. Dit betekent dat het scheduling-algoritme op één of ander manier wordt geparametreerd en dat deze parameters door de gebruikersprocessen kunnen worden ingevuld. Op deze manier kan het ouderproces precies regelen hoe de scheduling van de kindprocessen moet worden uitgevoerd, ook al voert het de scheduling zélf niet uit. Het mechanisme is in de kernel geplaatst, maar het beleid wordt door een gebruikersproces gemaakt..
6.3.
Hoe lang is het geleden dat het proces in- of uitgeswapt werd? Hoeveel CPU-tijd heeft het recentelijk gehad? Is het een groot proces? Welke prioriteit heeft het?
Operating systems
114
De MINIX-scheduler
6.3.1.
Beschrijving
De voorbeelden van schedulers die we nu bekeken hebben, zijn slechts exponenten van een zeer grote klasse van algoritmen en combinaties van algoritmen. Het was dan ook slechts de bedoeling een algemeen beeld te verschaffen van een vrij belangrijk onderdeel van een bedrijfssysteem. Om dit abstracte beeld toch enige concrete vorm te geven, volgt hierna de implementatie van een vrij eenvoudige scheduler zoals die gebruikt wordt in het operating system MINIX. MINIX gebruikt een multilevel scheduling mechanisme dat geïmplementeerd is met behulp van een abstracte variabele, voorgesteld in afbeelding 6-3 o
De drie niveaus komen overeen met de bovenste drie lagen van MINIX.
o
Per niveau is er een wachtrij van startklare processen.
o
Die wachtrijen zijn geïmplementeerd als gelinkte lijsten. Om gemakkelijk elementen te kunnen toevoegen en verwijderen wordt per niveau zowel het begin als het einde van elke rij bijgehouden in de tabellen rdyHead en rdyTail.
o
Telkens een geblokkeerd proces 'ontwaakt', wordt het aan het einde van zijn wachtrij toegevoegd.
o
Als de uitvoering van een proces onderbroken of beëindigd wordt, verdwijnt het uit zijn wachtrij.
Bij de criteria die gehanteerd worden door het algoritme dat bepaalt of een proces geswapt gaat worden of niet, zullen we zaken tegenkomen als: 9 9 9 9
Beleid versus mechanisme
Operating systems
115
(* A process is removed from the waiting queues *) PROCEDURE Preempt ( VAR pr : Proc ); (* A user process is replaced from the head to the tail of the queue when it has run for 100 msec. *)
Op deze abstracte variabele zijn vijf bewerkingen gedefinieerd :
PROCEDURE IsReadyTask() : BOOLEAN; (* Is there a task in the task waiting queue ? *)
IsReadyTask: De functieprocedure kijkt na of er startklare I/O tasks aanwezig zijn. PickProcess: Deze procedure kiest het startklare proces met de hoogste prioriteit. Daartoe wordt de wachtrij met de hoogste prioriteit gezocht, op voorwaarde dat die niet leeg is. In die rij wordt het proces gekozen dat aangeduid wordt door rdyHead. MakeReady: Een proces wordt achteraan de geschikte wachtrij geplaatst.
END Schedul. IMPLEMENTATION MODULE Schedul; (* Title : Schedul.MOD LastEdit : 06/05/88 Auteur : Jan Beurghs Implementation of the scheduling mechanism. *)
MakeUnReady: Een proces in uitvoering dat beëindigd is of geblokkeerd geraakt, wordt uit zijn wachtrij gehaald.
FROM Process IMPORT
Preempt: Een gebruikersproces wordt na 100 ms onderbroken en achteraan de wachtrij gezet.
FROM Table
(* typ *) ProcessKind, (* pro *) PKind, NextReady, SetNextReady;
FROM AsmLevel IMPORT (* pro *) Lock, Restore; IMPORT
* con *) NONE, (* pro *) CurProc, SetCurProc, SetPrevious, IsUser, SetBill;
TYPE PriorityArray = ARRAY ProcessKind OF Proc;
Figuur 6-3
VAR rdyHead, rdyTail : PriorityArray;
6.3.2.
Code van de MINIX scheduler
(*=========================================================*) (* IsReadyProcess *) (*=========================================================*)
DEFINITION MODULE Schedul; (* Title : Schedul.DEF LastEdit : 06/05/88 Auteur : Jan Beurghs Definition module for the scheduling mechanism. *)
PROCEDURE IsReadyProcess() : BOOLEAN; (* Are there runnable processes ? *) BEGIN RETURN ( rdyHead[TaskQ] # NIL ) OR ( rdyHead[ServerQ] # NIL ) OR ( rdyHead[UserQ] # NIL )
FROM Process IMPORT (* typ *) Proc; EXPORT QUALIFIED (* pro *) PickProcess, MakeReady, MakeUnReady, IsReadyTask, Preempt;
END IsReadyProcess; (*=========================================================*) (* IsReadyTask *) (*=========================================================*)
PROCEDURE PickProcess ( VAR pr : Proc ); (* Chooses the process with the highest priority that is runnable *)
PROCEDURE IsReadyTask() : BOOLEAN; (* Are there runnable tasks ? *)
PROCEDURE MakeReady ( VAR pr : Proc ); (* A runnable process is added to the waiting queues *)
BEGIN RETURN rdyHead[TaskQ] # NIL END IsReadyTask;
PROCEDURE MakeUnReady ( VAR pr : Proc ); Operating systems
116
Operating systems
117
(*=========================================================*) (* PickProcess *) (*=========================================================*) PROCEDURE PickProcess ( VAR pr : Proc ); (* Chooses the process with the highest priority that is runnable. *) VAR
*)
SetPrevious ( CurProc() ); IF rdyHead[queue] # NIL THEN
(* someone is runnable *) (* the head of the queue will be the current process *) SetCurProc ( ProcNumber ( rdyHead[queue] ) ); IF IsUser ( CurProc() ) THEN SetBill ( CurProc() ) (* user pays for CPU time *) END (* if *) ELSE (* no one is runnable *) SetCurProc( NONE ); SetBill ( NONE ) END (* if *); END PickProcess; (*=========================================================*) (* MakeReady *) (*=========================================================*)
(* disable interrupts (* determinate queue (* add to empty queue
queue was empty
*)
remove head of queue *)
Search body of queue. A process even if it is not running can be made unready by being sent a signal that kills it. *) WHILE ( NextReady ( temp ) # rp ) AND ( NextReady ( temp ) # NIL ) DO temp := NextReady ( temp ) (* proceed the search *) END (* while *); IF NextReady ( temp ) = NIL (* process not in queue *) THEN RETURN ELSE (* process found *) IF NextReady ( temp ) = NIL (* process is at tail *) THEN rdyTail[queue] := temp (* change tail *) ELSE (* remove process *) SetNextReady ( temp , NextReady ( temp ) ) END (* if *); END (* if *); END (* if *); Restore (* restore interrupts to previous state *)
(*=========================================================*) (* Preempt *) (*=========================================================*) *)
PROCEDURE Preempt; (* The current (user) process has run too long. (* It is moved from the head to the tail of the queue.
*) *)
VAR
(* add to tail of nonempty queue *) END (* if *); rdyTail[queue] := rp; SetNextReady ( rdyTail[queue] , NIL );(* new entry has no successor *) Restore (* restore interrupts to previous state *) END MakeReady;
(*=========================================================*) (* MakeUnReady *) (*=========================================================*)
Operating systems
disable interrupts *) determinate queue *)
END MakeUnReady;
PROCEDURE MakeReady ( VAR pr : Proc ); (* A runnable process is added to the waiting queues *) VAR queue : ProcessKind; BEGIN Lock; queue := KindOfProcess ( pr ); IF rdyHead[queue] = NIL THEN rdyHead[queue] := pr ELSE SetNextReady ( rdyTail[queue] , pr )
VAR queue : ProcessKind; temp : Proc; BEGIN Lock; (* queue := KindOfProcess ( pr ); (* temp := rdyHead[queue]; IF rdyHead[queue] = NIL THEN RETURN (* END (* if *); IF temp = rp THEN (* rdyHead[queue] := NextReady ( temp ) ELSE (*
queue : ProcesKind;
BEGIN IF NextReady ( rdyHead[TaskQ] ) # NIL (* determinate queue THEN queue := TaskQ ELSIF NextReady ( rdyHead[ServerQ] ) # NIL THEN queue := ServerQ ELSE queue := UserQ END (* if *);
PROCEDURE MakeUnReady ( VAR pr : Proc ); (* A process is removed from the waiting queues *)
118
*) *)
pr : Proc;
BEGIN Lock; (* disable interrupts *) IF NextReady ( rdyHead[UserQ] ) # NIL THEN (* there are other runnable pr.*) setNextReady ( rdyTail[UserQ], rdyHead[UserQ] ); rdyTail[UserQ] := NextReady ( rdyTail[UserQ] ); SetNextReady ( rdyTail[UserQ], NIL ); rdyHead[UserQ] := NextReady ( rdyHead[UserQ] ) END (* if *); PickProcess ( pr ); (* change the current process *) Restore (* restore interrupts to previous state
Operating systems
*)
119
END Preempt; (*=========================================================*) BEGIN (* Initialisation *) FOR i := TaskQ TO UserQ DO rdyHead[i] := NIL; rdyTail[i] := NIL END (* for *) END Schedul.
6.4.
Thread Scheduling in Windows 2000
This section describes the Windows 2000 scheduling policies and algorithms. The first subsection provides a condensed description of how scheduling works on Windows 2000 and a definition of key terms. Then Windows 2000 priority levels are described from both the Win32 API and the Windows 2000 kernel points of view. After a review of the relevant Win32 functions and Windows 2000 utilities and tools that relate to scheduling, the detailed data structures and algorithms that comprise the Windows 2000 scheduling system are presented.
6.4.1.
Overview of Windows 2000 Scheduling
Windows 2000 implements a priority-driven, preemptive scheduling system— the highest-priority runnable (ready) thread always runs, with the caveat that the thread chosen to run might be limited by the processors on which the thread is allowed to run, a phenomenon called processor affinity. By default, threads can run on any available processor, but you can alter processor affinity by using one of the Win32 scheduling functions.
o
A thread becomes ready to execute—for example, a thread has been newly created or has just been released from the wait state.
o
A thread leaves the running state because its time quantum ends, it terminates, or it enters a wait state.
o
A thread's priority changes, either because of a system service call or because Windows 2000 itself changes the priority value.
o
The processor affinity of a running thread changes.
At each of these junctions, Windows 2000 must determine which thread should run next. When Windows 2000 selects a new thread to run, it performs a context switch to it. A context switch is the procedure of saving the volatile machine state associated with a running thread, loading another thread's volatile state, and starting the new thread's execution. As already noted, Windows 2000 schedules at the thread granularity. This approach makes sense when you consider that processes don't run but only provide resources and a context in which their threads run. Because scheduling decisions are made strictly on a thread basis, no consideration is given to what process the thread belongs to. For example, if process A has 10 runnable threads and process B has 2 runnable threads, and all 12 threads are at the same priority, each thread would receive one-twelfth of the CPU time—Windows 2000 wouldn't give 50 percent of the CPU to process A and 50 percent to process B. To understand the thread-scheduling algorithms, you must first understand the priority levels that Windows 2000 uses.
6.4.2. When a thread is selected to run, it runs for an amount of time called a quantum. A quantum is the length of time a thread is allowed to run before Windows 2000 interrupts the thread to find out whether another thread at the same priority level or higher is waiting to run or whether the thread's priority needs to be reduced. Quantum values can vary from thread to thread (and differ between Windows 2000 Professional and Windows 2000 Server). (Quantums are described in more detail elsewhere in this chapter.) A thread might not get to complete its quantum, however. Because Windows 2000 implements a preemptive scheduler, if another thread with a higher priority becomes ready to run, the currently running thread might be preempted before finishing its time slice. In fact, a thread can be selected to run next and be preempted before even beginning its quantum!
Priority Levels
As illustrated in Figure 6-4, internally, Windows 2000 uses 32 priority levels, ranging from 0 through 31. These values divide up as follows:
o o o
Sixteen real-time levels (16-31) Fifteen variable levels (1-15) One system level (0), reserved for the zero page thread
The Windows 2000 scheduling code is implemented in the kernel. There's no single "scheduler" module or routine, however—the code is spread throughout the kernel in which scheduling-related events occur. The routines that perform these duties are collectively called the kernel's dispatcher. Thread dispatching occurs at DPC/dispatch level and is triggered by any of the following events: Operating systems
120
Operating systems
121
Figuur 6-5
Figuur 6-4
Thread priority levels are assigned from two different perspectives: those of the Win32 API and those of the Windows 2000 kernel. The Win32 API first organizes processes by the priority class to which they are assigned at creation (Real-time, High, Above Normal, Normal, Below Normal, and Idle) and then by the relative priority of the individual threads within those processes (Time-critical, Highest, Above-normal, Normal, Below-normal, Lowest, and Idle). In the Win32 API, each thread has a priority based on a combination of its process priority class and its relative thread priority. The mapping from Win32 priority to internal Windows 2000 numeric priority is shown in Figure 6-5. The priorities shown in Figure 6-5 are thread base priorities. Threads start out inheriting the process base priority, which can be changed with Task Manager (as described in the section "Relevant Tools") or with the Win32 SetPriorityClass function. Normally, the process base priority (and hence the starting-thread base priority) will default to the value at the middle of each process priority range (24, 13, 10, 8, 6, or 4). However, some Windows 2000 system processes (such as the Session Manager, service controller, and local security authentication server) have a base process priority slightly higher than the default for the Normal class (8). This higher default value ensures that the threads in these processes will all start at a higher priority than the default value of 8. A system process uses internal Windows 2000 functions to set its process base priority to a numeric value other than its default starting Win32 base priority.
Operating systems
122
Operating systems
123
Be aware that many important Windows 2000 kernel-mode system threads run in the real-time priority range, so if your process spends excessive time running in this range, it might be blocking critical system functions in the memory manager, cache manager, local and network file systems, and even other device drivers. It won't block hardware interrupts because they have a higher priority than any thread, but it might block system threads from running.
Win32 versus Kernel Priorities Win32 Process Classes Real Time
High
15 15 14 13 12 11
15 12 11 10 9 8
15 10 9 8 7 6
15 8
15 6
Lowest
31 26 25 24 23 22
7 6 5 4
5 4 3 2
Idle
16
1
1
1
1
1
TimeTime-critical Highest Win32 AboveAbove-normal Thread Normal Priorities BelowBelow-Normal
Above Below Normal Normal Normal Idle
There is one behavioral difference for threads in the real-time range (mentioned in the section "Preemption"): their thread quantum is reset if they are preempted. -------------------------------------------------------------------------------Although Windows 2000 has a set of priorities called real-time, they are not realtime in the common definition of the term in that Windows 2000 doesn't provide true real-time operating system facilities, such as guaranteed interrupt latency or a way for threads to obtain a guaranteed execution time.
6.4.5.
Figuur 6-6
Whereas a process has only a single priority value (base priority), each thread has two priority values: current and base. The current priority for threads in the dynamic range (1 through 15) might be, and often is, higher than the base priority. Windows 2000 never adjusts the priority of threads in the real-time range (16 through 31), so they always have the same base and current priority.
6.4.3.
Interrupt Levels vs. Priority Levels
As illustrated in Figure 6-7, all threads run at IRQL 0 or 1. User-mode threads run at IRQL 0; only kernel-mode APCs execute at IRQL 1, since they interrupt the execution of a thread. Also, threads running in kernel mode can raise IRQL. Because of this, no user-mode thread, regardless of its priority, blocks hardware interrupts (although high-priority real-time threads can block the execution of important system threads).
Relevant Tools
You can view (and change) the base-process priority class with Task Manager, Pview, or Pviewer. You can view the numeric base-process priority value with the Performance tool or Pstat. You can view thread priorities with the Performance tool, Pview, Pviewer, and Pstat. There is no general utility to change relative thread priority levels, however.
Figuur 6-7
The only way to specify a starting priority class for a process is with the start command in the Windows 2000 command prompt.
6.4.4.
Real-Time Priorities
You can raise or lower thread priorities within the dynamic range in any application; however, you must have the increase scheduling priority privilege to enter the realtime range. (If you attempt to move a process into the Real-time priority class and don't have the privilege, the operation doesn't fail—the High class is used.) Operating systems
124
Thread-scheduling decisions are made at DPC/dispatch level. Thus, while the kernel is deciding which thread should run next, no thread can be running and possibly changing scheduling-related information (such as priorities).
Operating systems
125
6.4.6.
Thread States
Before you can comprehend the thread-scheduling algorithms and data structures, you need to understand the various execution states that a thread can be in. Figure 6-8 illustrates the state transitions for a Windows 2000 thread. More details on what happens at each transition are included later in this section.
system, for example) can wait on the thread's behalf, or an environment subsystem can direct the thread to suspend itself. When the thread's wait ends, depending on the priority, the thread either begins running immediately or is moved back to the ready state. Transition A thread enters the transition state if it is ready for execution but its kernel stack is paged out of memory. For example, the thread's kernel stack might be paged out of memory. Once its kernel stack is brought back into memory, the thread enters the ready state. Terminated When a thread finishes executing, it enters the terminated state. Once terminated, a thread object might or might not be deleted. (The object manager sets policy regarding when to delete the object.) If the executive has a pointer to the thread object, it can reinitialize the thread object and use it again. Initialized Used internally while a thread is being created.
6.4.7.
Quantum
As mentioned earlier in the chapter, a quantum is the amount of time a thread gets to run before Windows 2000 checks whether another thread at the same priority should get to run. If a thread completes its quantum and there are no other threads at its priority, Windows 2000 reschedules the thread to run for another quantum. Figuur 6-8
Each thread has a quantum value that represents how long the thread can run until its quantum expires. This value isn't a time length but rather an integer value, which we'll call quantum units.
The thread states are as follows: 6.4.7.1. Quantum Accounting Ready When looking for a thread to execute, the dispatcher considers only the pool of threads in the ready state. These threads are simply waiting to execute. Standby A thread in the standby state has been selected to run next on a particular processor. When the correct conditions exist, the dispatcher performs a context switch to this thread. Only one thread can be in the standby state for each processor on the system.
By default, threads start with a quantum value of 6 on Windows 2000 Professional and 36 on Windows 2000 Server. (We'll explain how you can change these values later.) The rationale for the longer default value on Windows 2000 Server is to minimize context switching. By having a longer quantum, server applications that wake up as the result of a client request have a better chance of completing the request and going back into a wait state before their quantum ends.
Running Once the dispatcher performs a context switch to a thread, the thread enters the running state and executes. The thread's execution continues until the kernel preempts it to run a higher priority thread, its quantum ends, it terminates, or it voluntarily enters the wait state.
Each time the clock interrupts, the clock-interrupt routine deducts a fixed value (3) from the thread quantum. If there is no remaining thread quantum, the quantum end processing is triggered and another thread might be selected to run. On Windows 2000 Professional, because 3 is deducted each time the clock interrupt fires, by default a thread runs for 2 clock intervals; on Windows 2000 Server, by default a thread runs for 12 clock intervals.
Waiting A thread can enter the wait state in several ways: a thread can voluntarily wait on an object to synchronize its execution, the operating system (the I/O
Even if the system were at DPC/dispatch level or above (for example, if a DPC or an interrupt service routine was executing) when the clock interrupt occurred, the
Operating systems
126
Operating systems
127
current thread would still have its quantum decremented, even if it hadn't been running for a full clock interval. If this was not done and device interrupts or DPCs occurred right before the clock interval timer interrupts, threads might not ever get their quantum reduced. The length of the clock interval varies according to the hardware platform. The frequency of the clock interrupts is up to the HAL, not the kernel. For example, the clock interval for most x86 uniprocessors is 10 milliseconds, and for most x86 multiprocessors, 15 milliseconds.
Figuur 6-9
Determining the Clock Interval Frequency The Win32 GetSystemTimeAdjustment function returns the clock interval. To determine the clock interval, run the Clockres program from www.sysinternals.com (\Sysint\Clockres on the companion CD). Here's the output from a uniprocessor x86 system:
Short vs. Long 1 specifies long, and 2 specifies short. 0 or 3 indicates that the default will be used (short for Windows 2000 Professional, long for Windows 2000 Server).
C:\>clockres
Variable vs. Fixed 1 means to vary the quantum for the foreground process, and 2 means that quantum values don't change for foreground processes. 0 or 3 means that the default will be used (variable for Windows 2000 Professional, fixed for Windows 2000 Server).
ClockRes - View the system clock resolution By Mark Russinovich SysInternals - www.sysinternals.com
Foreground Quantum Boost This field, which must have a value of 0, 1, or 2 (3 is invalid and treated as 2) is an index into a three-entry quantum table used to obtain the quantum for the threads in the foreground process. The quantum for threads in background processes is taken from the first entry in this quantum table. The field's value is stored in the kernel variable PsPrioritySeparation.
The system clock interval is 10 ms
The reason quantum is expressed in terms of a multiple of 3 quantum units per clock tick rather than as single units is to allow for partial quantum decay on wait completion. When a thread that has a base priority less than 14 executes a wait function (such as WaitForSingleObject or WaitForMultipleObjects), its quantum is reduced by 1 quantum unit. (Threads running at priority 14 or higher have their quantums reset after a wait.) This partial decay addresses the case in which a thread enters a wait state before the clock interval timer fires. If this adjustment were not made, it would be possible for threads never to have their quantums reduced. For example, if a thread ran, entered a wait state, ran again, and entered another wait state but was never the currently running thread when the clock interval timer fired, it would never have its quantum charged for the time it was running. 6.4.7.2. Controlling the Quantum The registry value HKLM\SYSTEM\CurrentControlSet\Control\PriorityControl\Win32PrioritySeparation
allows you to specify the relative length of thread quantums (short or long) and whether or not threads in the foreground process should have their quantums boosted (and if so, the amount of the boost). This value consists of 6 bits divided into the three 2-bit fields shown in Figure 6-9.
Operating systems
128
The foreground process is the process that owns the thread that owns the window that's in focus. When the foreground window changes to one owned by a thread in a process higher than the Idle priority class, the Win32 subsystem changes the quantum values for all the threads in that process.. This table shows the possible settings for PspForegroundQuantum.
Variable Fixed
6 18
Short 12 18
18 18
12 36
Long 24 36
36 36
The reason that Windows 2000 boosts the quantum of foreground threads and not the priority is best illustrated with the following example, which shows the potential problems resulting from an approach based on foreground priority boosting. Suppose you start a long-running spreadsheet recalculation and then switch to a CPUintensive application (such as a graphics-intensive game). The spreadsheet process running in the background will get little CPU time if the game process, which is in the foreground, has its priority boosted. Increasing the quantum of the game process doesn't prevent the spreadsheet calculation from running but instead favors the game process. If you do want to run an interactive application at a higher priority than all other interactive processes, you can always change the priority class to Above Normal or High using Task Manager (or start the application from the command prompt with the command start /abovenormal or start /high).
Operating systems
129
6.4.8.
Scheduling Scenarios
Windows 2000 bases the question of "Who gets the CPU?" on thread priority; but how does this approach work in practice? The following sections illustrate just how priority-driven preemptive multitasking works on the thread level. Note that there are differences in the way Windows 2000 handles scheduling decisions on a multiprocessor system vs. on a uniprocessor system. 6.4.8.1. Voluntary Switch First a thread might voluntarily relinquish use of the processor by entering a wait state on some object (such as an event, a mutex, a semaphore, an I/O completion port, a process, a thread, a window message, and so on) by calling one of the many Win32 wait functions (such as WaitForSingleObject or WaitForMultipleObjects). Voluntary switching is roughly equivalent to a thread ordering an item that isn't ready to go at a fast-food counter. Rather than hold up the queue of the other diners, the thread will step aside and let the next thread place its order (execute its routine) while the first thread's hamburger is being prepared. When the hamburger is ready, the first thread goes to the end of the ready queue of the priority level. However, as you'll see later in the chapter, most wait operations result in a temporary priority boost so that the thread can pick up its hamburger right away and start eating. Figure 6-10 illustrates a thread entering a wait state and Windows 2000 selecting a new thread to run.
enters a wait state—in fact, as explained earlier, when the wait is satisfied, the thread's quantum value is decremented by 1 quantum unit, equivalent to one-third of a clock interval (except for threads running at priority 14 or higher—they have their quantum reset after a wait). 6.4.8.2. Preemption In this scheduling scenario, a lower-priority thread is preempted when a higherpriority thread becomes ready to run. This situation might occur for a couple of reasons. o
A higher-priority thread's wait completes. (The event that the other thread was waiting on has occurred.)
o
A thread priority is increased or decreased.
In either of these cases, Windows 2000 must determine whether the currently running thread should still continue to run or whether it should be preempted to allow a higher-priority thread to run. Threads running in user mode can preempt threads running in kernel mode— the mode in which the thread is running doesn't matter. The thread priority is the determining factor. When a thread is preempted, it is put at the head of the ready queue for the priority it was running at. Threads running in the real-time priority range have their quantum reset to a full time slice while threads running in the dynamic priority range finish their quantum when they get to run again. Figure 6-11 illustrates this situation.
Figuur 6-11
Figuur 6-10
In Figure 6-10, the top block (thread) is voluntarily relinquishing the processor so that the next thread in the ready queue can run (as represented by the halo it has when in the Running column). Although it might appear from this figure that the relinquishing thread's priority is being reduced, it's not—it's just being moved to the wait queue of the objects the thread is waiting on. What about any remaining quantum for the thread? The quantum value isn't reset when a thread Operating systems
130
In Figure 6-11, a thread with priority 18 emerges from a wait state and repossesses the CPU, causing the thread that had been running (at priority 16) to be bumped to the head of the ready queue. Notice that the bumped thread isn't going to the end of the queue but to the beginning; when the preempting thread has finished running, the bumped thread can complete its quantum. In this example, the threads are in the real-time range; as explained in the note in the section "Priority Boosts," no dynamic priority boosts are allowed for threads in the real-time range. Operating systems
131
If voluntary switching is roughly equivalent to a thread letting another thread place its lunch order while the first thread waits for its meal, preemption is roughly equivalent to a thread being bumped from its place in line because the president of the United States has just walked in and ordered a hamburger. The preempted thread doesn't get bumped to the back of the line but is simply moved aside while the president gets his lunch. As soon as the president leaves, the first thread can resume ordering its meal.
thread is removed from the process thread list and the associated data structures are deallocated and released.
6.4.9.
Context Switching
A thread's context and the procedure for context switching vary depending on the processor's architecture. A typical context switch requires saving and reloading the following data:
6.4.8.3. Quantum End When the running thread exhausts its CPU quantum, Windows 2000 must determine whether the thread's priority should be decremented and then whether another thread should be scheduled on the processor.
o
Program counter
o
Processor status register
If the thread priority is reduced, Windows 2000 looks for a more appropriate thread to schedule. (For example, a more appropriate thread would be a thread in a ready queue with a higher priority than the new priority for the currently running thread.) If the thread priority isn't reduced and there are other threads in the ready queue at the same priority level, Windows 2000 selects the next thread in the ready queue at that same priority level and moves the previously running thread to the tail of that queue (giving it a new quantum value and changing its state from running to ready). This case is illustrated in Figure 6-20. If no other thread of the same priority is ready to run, the thread gets to run for another quantum.
o
Other register contents
o
User and kernel stack pointers
o
A pointer to the address space in which the thread runs (the process's page table directory)
6.4.10.
Priority Boosts
In five cases, Windows 2000 can boost (increase) the current priority value of threads: On completion of I/O operations
o
After waiting on executive events or semaphores
o
After threads in the foreground process complete a wait operation
o
When GUI threads wake up because of windowing activity
o
When a thread that's ready to run hasn't been running for some time (CPU starvation)
The intent of these adjustments is to improve overall system throughput and responsiveness as well as resolve potentially unfair scheduling scenarios. Like any scheduling algorithms, however, these adjustments aren't perfect, and they might not benefit all applications.
Figuur 6-12
6.4.8.4. Termination When a thread finishes running (either because it returned from its main routine, called ExitThread, or was killed with TerminateThread), it moves from the running state to the terminated state. If there are no handles open on the thread object, the Operating systems
o
132
Windows 2000 never boosts the priority of threads in the real-time range (16 through 31). Therefore, scheduling is always predictable with respect to other threads in the real-time range. Windows 2000 assumes that if you're using the real-time thread priorities, you know what you're doing.
Operating systems
133
6.4.10.1. Priority Boosting After I/O Completion Windows 2000 gives temporary priority boosts upon completion of certain I/O operations so that threads that were waiting on an I/O will have more of a chance to run right away and process whatever was being waited on. Recall that 1 quantum unit is deducted from the thread's remaining quantum when it wakes up so that I/O bound threads aren't unfairly favored. The actual value for the boost is up to the device driver. It is the device driver that specifies the boost when it completes an I/O request on its call to the kernel function IoCompleteRequest. Table 6-19 Recommended Boost Values Device Disk, CD-ROM, parallel, video Network, mailslot, named pipe, serial Keyboard, mouse Sound
Boost 1 2 6 8
When a thread that was waiting on an executive event or a semaphore object has its wait satisfied (because of a call to SetEvent, PulseEvent, or ReleaseSemaphore), it receives a boost of 1. (See the value for EVENT_INCREMENT and SEMAPHORE_INCREMENT in the DDK header files.) Threads that wait for events and semaphores warrant a boost for the same reason that threads that wait on I/O operations do—threads that block on events are requesting CPU cycles less frequently than CPU-bound threads. This adjustment helps balance the scales. This boost operates the same as the boost that occurs after I/O completion as described in the previous section: the boost is always applied to the base priority (not the current priority), the priority will never be boosted over 15, and the thread gets to run at the elevated priority for its remaining quantum (as described earlier, quantums are reduced by 1 when threads exit a wait) before decaying one priority level at a time until it reaches its original base priority. 6.4.10.3. Priority Boosts for Foreground Threads After Waits
The boost is always applied to a thread's base priority, not its current priority. As illustrated in Figure 6-13, after the boost is applied, the thread gets to run for one quantum at the elevated priority level. After the thread has completed its quantum, it decays one priority level and then runs another quantum. This cycle continues until the thread's priority level has decayed back to its base priority. A thread with a higher priority can still preempt the boosted thread, but the interrupted thread gets to finish its time slice at the boosted priority level before it decays to the next lower priority.
Whenever a thread in the foreground process completes a wait operation on a kernel object, the kernel function KiUnwaitThread boosts its current (not base) priority by the current value of PsPrioritySeparation. (The windowing system is responsible for determining which process is considered to be in the foreground.) The reason for this boost is to improve the responsiveness of interactive applications—by giving the foreground application a small boost when it completes a wait, it has a better chance of running right away, especially when other processes at the same base priority might be running in the background. Unlike other types of boosting, this boost applies to both Windows 2000 Professional and Windows 2000 Server, and you can't disable this boost, even if you've disabled priority boosting using the Win32 SetThreadPriorityBoost function. 6.4.10.4. Priority Boosts for CPU Starvation
Figuur 6-12
As noted earlier, these boosts apply only to threads in the dynamic priority range (0 through 15). No matter how large the boost is, the thread will never be boosted beyond level 15 into the real-time priority range. In other words, a priority 14 thread that receives a boost of 5 will go up to priority 15. A priority 15 thread that receives a boost will remain at priority 15. 6.4.10.2. Boosts After Waiting for Events and Semaphores
Operating systems
134
Imagine the following situation: you've got a priority 7 thread that's running, preventing a priority 4 thread from ever receiving CPU time; however, a priority 11 thread is waiting on some resource that the priority 4 thread has locked. But because the priority 7 thread in the middle is eating up all the CPU time, the priority 4 thread will never run long enough to finish whatever it's doing and release the resource blocking the priority 11 thread. What does Windows 2000 do to address this situation? Once per second, the balance set manager (a system thread that exists primarily to perform memory management functions and is described in more detail in Chapter 7) scans the ready queues for any threads that have been in the ready state (that is, haven't run) for longer than 300 clock ticks (approximately 3 to 4 seconds, depending on the clock interval). If it finds such a thread, the balance set manager boosts the thread's priority to 15 and gives it double the normal quantum. Once the 2 quantums are up, the thread's priority decays immediately to its original base priority. If the thread wasn't finished and a higher priority thread is ready to run, the decayed thread will return to the ready queue, where it again becomes eligible for another boost if it remains there for another 300 clock ticks.
Operating systems
135
The balance set manager doesn't actually scan all ready threads every time it runs. To minimize the CPU time it uses, it scans only 16 ready threads; if there are more threads at that priority level, it remembers where it left off and picks up again on the next pass. Also, it will boost only 10 threads per pass—if it finds 10 threads meriting this particular boost (which would indicate an unusually busy system), it stops the scan at that point and picks up again on the next pass.
7.
Invoer en uitvoer
Een belangrijke taak van een O.S. is het besturen van de randapparaten (Input/Output devices). Dit besturen houdt o.a. in:
Will this algorithm always solve the priority inversion issue? No—it's not perfect by any means. But over time, CPU-starved threads should get enough CPU time to finish whatever processing they were doing and reenter a wait state.
o o o o
7.1.
zeggen wat ze moeten doen; behandelen van interrupts die van de devices komen; het behandelen van fouten: ingrijpen wanneer er iets misloopt; zorgen voor een eenvoudige interface voor al die ingewikkelde toestellen: abstracties!
I/O Hardware
We gaan ons niet bezighouden met allerlei details over de constructie en werking van randapparatuur: wel zullen we een aantal principes bekijken die nuttig zijn om de besturing van de I/O devices door een O.S. te begrijpen.
7.1.1.
Blok en karakter devices
Grof gesproken kunnen randapparaten ingedeeld worden in twee soorten: blok en karakter devices. Bij blok devices gebeurt de communicatie met het O.S. op de volgende manier: o de gegevens worden in blokken gelezen of weggeschreven; o zulke blokken kunnen onafhankelijk van elkaar benaderd worden omdat ze beschikken over een adres. Vb. disk drives. Bij karakter devices is er sprake van een continue stroom van gegevens. Tot deze soort behoren bijna alle andere apparaten dan de drives: printers, toetsenbord, display, muis, ... Deze indeling is echter niet sluitend: wat te denken van een tape drive? Voor de bestudering van de meest algemene principes i.v.m. I/O is deze indeling echter bruikbaar.
7.1.2.
Device controllers
De meeste randapparaten bestaan uit twee grote stukken: 1. 2.
Operating systems
136
een mechanisch gedeelte een elektronisch gedeelte.
Operating systems
137
Het elektronisch gedeelte bestuurt het eigenlijke apparaat (de 'mechanica') en wordt meestal de device controller (to control = besturen) of adapter genoemd. Het is deze component die communiceert met het operating system (afbeelding 6-1).
Voorbeeld 2: monitor (CRT) Het O.S. geeft aan de controller van de CRT door welke tekens moeten afgedrukt worden en op welke plaats. De controller zorgt er dan voor dat de elektronenstraal op de correcte manier gestuurd wordt om dat te verwezenlijken.
Device
Device controller
Operating System
Het O.S. communiceert dus enkel met de device controllers.
Die communicatie gebeurt via daartoe bestemde registers en interrupts. Bij sommige computers behoren deze registers tot de normale adresruimte. Die manier van werken noemt men memory-mapped I/O. Bij de IBM PC gebruikt men een aparte adresruimte voor de communicatie met de controllers: de poorten. Hieronder staan voor die computer enkele veel gebruikte poortadressen en interruptnummers.
Figuur 7-1 Aspecten van deze modulaire opbouw: o o
o
De complexiteit van het mechanisch gedeelte blijft verborgen. De controller levert een door het O.S. eenvoudig te bedienen interface. Indien voor een bepaald type computer de controllers gestandaardiseerd zijn, kunnen de eigenlijke apparaten door andere fabrikanten vervaardigd worden (drives, monitors, printers, ...). Nieuwe typen van randapparaten kunnen onafhankelijk van computers en bedrijfssystemen waarmee ze zullen moeten communiceren ontwikkeld worden. Een disk met een grotere capaciteit vereist geen nieuwe versie van je bedrijfssysteem!
Hoe de communicatie tussen device en controller verloopt, is verborgen voor het O.S., dat is een zaak tussen de fabrikanten van de devices en de controllers.
Voorbeeld 1: disk drive (dus een blok device) Onderstel de grootte van de blokken = 512 bytes (= 4096 bits). Hetgeen bij het lezen afkomstig is van de eigenlijke lees/schrijfkop is echter een stroom bits die uit de volgende drie stukken bestaat: 1. 2. 3.
I/O controller klok toetsenbord tweede seriële poort harde schijf printer monochr. monitor kleurenmonitor floppy disk eerste seriële poort
I/O adres 040 - 043 060 - 063 2F8 - 2FF 320 - 32F 378 - 37F 380 - 3BF 3D0 - 3DF 3F0 - 3F7 3F8 - 3FF
o
Met elke controller is een interrupt verbonden: het is via deze weg dat de aandacht van de CPU gevraagd wordt.
o
Alle uitwisseling van informatie tussen de CPU en de controller gebeurt via een soort 'brievenbus': ze bevindt zich op de aangeduide adressen.
De meeste computers hebben een single bus architectuur. Schematisch is dat voorgesteld in afbeelding 7-2.
informatie aangaande de plaats op de schijf (track, sector); de 4096 bits eigenlijke gegevens; een aantal bits om aan foutdetectie te kunnen doen. CPU
Een taak van de controller is o.a. deze stroom in een buffer op te slaan, foutdetectie uit te voeren en dan pas aan het O.S. te melden dat de gevraagde data gereed liggen om opgehaald te worden. De controller heeft van de stroom bits een blok gemaakt. Een andere taak van de controller is o.a. de besturing van de stappenmotor die de leeskop bestuurt: dit wordt nooit door het O.S. gedaan! Operating systems
Interrupt 8 9 11 13 15 14 12
138
Geheugen
Disk
Printer
Disk controller
Printer controller
Andere controller
Figuur 7-2
Operating systems
139
Disk
Wanneer we nu een IBM PC in deze context bekijken, kunnen we een van de oorzaken van zijn enorme populariteit opmerken: op de systeembus kunnen heel eenvoudig bijkomende controllers aangebracht worden. Er moet enkel voor gezorgd worden dat ze op de gepaste manier met het O.S. kunnen communiceren.
Disk Controller
Geheugen CPU
Plaats blok
adres
buffer
teller
7.1.3.
Direct Memory Access ( DMA ) Systeembus
Het principe van DMA kan best uitgelegd worden door te starten vanuit een situatie waarbij er van DMA geen sprake is: we bekijken daarvoor een Ieesoperatie op een disk. Een disk is een blok device, wat o.a. betekent dat steeds een transfer van een volledig blok gegevens moet gebeuren. De leesoperatie van een blok ziet er als volgt uit:
1.
o
Het O.S. geeft aan de disk controller de informatie: 9 Het disk adres van het blok 9 Een aanduiding dat het een leesoperatie betreft.
o
Terwijl het O.S. zich met iets anders gaat bezighouden, voert de controller de opdracht uit. Het blok wordt bit per bit overgebracht van de disk naar een buffer in de controller. Daar wordt het blok op fouten nagekeken, desgevallend wordt de Ieesoperatie nogmaals uitgevoerd, en wanneer het blok foutloos gekopieerd is van disk naar controller, genereert de controller een interrupt om te melden dan wat hem betreft de opdracht voltooid is.
o
o
Figuur 7-3
De inhoud van de buffer moet nu nog, overgebracht worden naar de gewenste plaats in het interne geheugen. Deze transfer gebeurt byte per byte of woord per woord, al naargelang de architectuur en behelst dus de uitvoering van het kopieerprogrammaatje door de CPU. Om de processor te ontlasten, en dus om tijd te winnen, bevatten veel controllers een eigen ‘gespecialiseerde’ processor die enkel en alleen dient om de datatransfer tussen controller en intern geheugen te verzorgen. Vermits het intern geheugen normaal gezien exclusief beheerd wordt door de CPU, wordt deze toestand Direct Memory Access genoemd:
Het O.S. geeft aan de disk controller de informatie: o het disk adres van het blok o een aanduiding, dat het een leesoperatie betreft o het geheugenadres waar de blok uiteindelijk moet terechtkomen o het aantal bytes dat moet getransfereerd worden.
De laatste twee getallen worden daartoe op de controller in de twee daarvoor voorziene registers geplaatst: adres en teller. 2. Terwijl het O.S. zich met iets anders gaat bezighouden, voert de controller de opdracht uit. Het blok wordt bit per bit overgebracht van de disk naar een buffer in de controller. Daar wordt het blok op fouten nagekeken, desgevallend wordt de leesoperatie nogmaals uitgevoerd, en wanneer bet blok foutloos gekopieerd is van disk naar controller, begint de DMA -processor van de controller zelfstandig, de buffer naar het geheugen te kopiëren. 3. Pas daarna genereert de controller een interrupt. Die dient nu om het O.S. er van te verwittigen dat de transfer voltooid is. Vermits de controller rechtstreeks toegang heeft tot het geheugen, kunnen we ons afvragen waarom de buffer nog nodig is! Anders gezegd: waarom kan de controller bijvoorbeeld de foutcontrole niet rechtstreeks in het geheugen uitvoeren? De reden hiervoor is dat de datatransfer gebeurt via de systeembus en dat die ook door andere devices gebruikt wordt. Indien de controller de gegevens rechtstreeks van disk naar geheugen zou sturen, zou dat betekenen dat op het ogenblik dat de te lezen sector onder de leeskop passeert, de bus vrij moet zijn en dat is iets wat nooit op voorhand bepaald kan worden!
De controller heeft rechtstreekse toegang tot het interne geheugen.
De schematische voorstelling van een DMA situatie vind je in afbeelding 7 – 3. De leesoperatie van een blok ziet er met behulp van DMA dan als volgt uit :
Deze werkwijze heeft enkele belangrijke implicaties wat betreft de performantie van de I/0. De meeste eenvoudige controllers kunnen niet tegelijkertijd van disk lezen en kopiëren naar geheugen. Bijgevolg moeten bij het lezen van een sector twee operaties na elkaar uitgevoerd worden: o
Operating systems
140
van disk in de buffer lezen
Operating systems
141
o
van de buffer naar geheugen overbrengen.
Beide bewerkingen vergen een zekere tijd!
4 3 2 1
Bekijken we nu de situatie waarbij een volledige track van een disk moet gelezen worden: 1. De eerste sector wordt van disk gelezen. 2. Die data worden dan vanuit de buffer naar bet geheugen gekopieerd. Ondertussen draait de disk echter verder! Om de volgende sector te kunnen lezen moet de controller dus een volledige omwenteling wachten. Vermits dit moet gebeuren voor elke achtereenvolgende sector, zal de performantie zeker niet schitterend zijn. Dit probleem kan opgelost worden door de sectoren die logisch op elkaar volgen fysisch niet achter mekaar te plaatsen maar bijvoorbeeld door telkens een sector over te slaan, dit om de controller de tijd te geven zijn DMA transfer uit te voeren. Die fysische ordening van de sectoren op een disk noemt men de interleaving.
7.2.
I/O Software
Een belangrijke taak van een O.S. is het verbergen van de details van de in- en uitvoerapparaten, anders gezegd: een interface voorzien tussen de I/O devices zelf en alles en iedereen die ze gebruikt. Die interface is de I/O-software. Hij kan geconstrueerd worden volgens een gelaagd model: de onderste lagen verbergen de specifieke eigenschappen van de hardware en de bovenste verzorgen de uiteindelijke vorm waarin de I/O devices aan de gebruiker ervan gepresenteerd worden. Het streefdoel is het uiteindelijk gebruik zo eenvoudig mogelijk te laten verlopen.
Dat 'eenvoudig gebruik' is gekoppeld aan een aantal sleutelbegrippen in verband met I/O-software: o Onafhankelijkheid van de gebruikte apparaten (device independency). Voorbeeld: een gebruikersprogramma dat bestanden naar een floppy disk wegschrijft, moet dat zonder enige aanpassing naar een harde schijf kunnen doen.
Laag Gebruikerssoftware Device – independent O.S. software Device drivers ( tasks ) Interrupt handlers
Een gelaagd model zoals voorgesteld in deze afbeelding is geschikt om de vorige doelstellingen te verwezenlijken. De taken van de verschillende lagen (abstractieniveaus) kunnen heel summier als volgt omschreven worden: Laag 1: de interrupt handlers Op dit niveau gebeurt de allerelementairste communicatie tussen computer en randapparaten: interrupts worden behandeld en omgevormd tot abstracties die op een hoger niveau gemakkelijker handelbaar zijn. Een voorbeeld hiervan zijn we tegengekomen bij de bespreking van het message mechanisme bij de interproces communicatie. Laag 2: de device drivers Hier vinden we processen die zich elk bezighouden met de communicatie en besturing van een apart randapparaat of een groep aanverwante devices. Zo zal er bijvoorbeeld een proces te vinden zijn dat zich bezighoudt met de besturing van de floppy drives, een voor de printer, een voor het beeldscherm,... Soms noemt men deze processen ook tasks. Laag 3: device-onafhankelijke software Hier gebeuren alle werkzaamheden die gemeenschappelijk zijn aan alle I/O devices zoals naamgeving, protectie en foutmelding alsook het verzorgen van een uniforme interface voor alle device drivers. Laag 4: software op gebruikersniveau Een kleiner gedeelte van de I/O wordt verzorgd door bibliotheken, gelinkt met gebruikersprogramma's. In MODULA-2 zijn dit bvb. de procedures die te vinden zijn in modules als InOut, IO, en FIO.
I/O Request
I/O Reply User process
o Uniforme benamingen: de naam van het apparaat mag niet afhangen van het apparaat zelf maar moet bestaan uit een nummer of een eenvoudige string van karakters.
Device – independent software Device drivers
o Fouten moeten zo dicht mogelijk bij de hardware gecorrigeerd worden. Slechts in noodgevallen mogen ze doordringen tot bij de gebruiker.
Figuur 7-4
o Sommige devices kunnen door meerdere gebruikers tegelijkertijd gebruikt worden (disk drives) en andere niet (printers). Het is aan het O.S. om dit onderscheid voor de gebruikers te verbergen.
Operating systems
142
Interrupts handlers Hardware
Operating systems
143
7.2.1.
Device drivers
De structuur van alle device drivers is dezelfde vermits ze alle een gelijkaardige werking hebben: wachten op een bericht waarin staat welke taak ze moeten uitvoeren. Als zo'n bericht is gearriveerd, wordt het werk uitgevoerd en begint het wachten opnieuw.
o
Er moet voor gezorgd worden dat de interrupt vector aangepast wordt: er komt immers een nieuwe interrupt bij die door het O.S. moet kunnen behandeld worden.
o
Er moet voor gezorgd worden dat bij het opstarten van het systeem de device driver, horend bij het nieuwe apparaat, geactiveerd wordt.
Dat laatste was vroeger niet zo evident vermits een device driver deel uitmaakt van het bedrijfssysteem. Een stuk code toevoegen aan een programma, of dat nu een O.S. is of niet - dat doet er niet toe, impliceert hercompileren en opnieuw linken! Zo iets is uiteraard niet weggelegd voor een modale gebruiker van een PC-systeem die een nieuw randapparaat en driver wil installeren. De meeste hedendaagse bedrijfssystemen zijn daarom zo ontworpen dat de mogelijkheid voorzien is om device drivers als aparte modules aan te spreken, zonder gans de boel te moeten hercompileren. Deze niet evidente eigenschap heeft in het verleden zonder twijfel bijgedragen tot de enorme populariteit van MS-DOS. De situatie met betrekking tot dit systeem zou als volgt kunnen samengevat worden:
LOOP blokkeer tot een bericht arriveert; CASE type van bericht OF READ : doe de lees-operatie | WRITE : doe de schrijf-operatie | OTHER : doe een andere operatie END (* case *); zend een antwoordbericht naar de afzender END (*loop *)
o
Je bezit een eenvoudige en goedkope computer met een open bus architectuur.
o
Dat ding werkt onder besturing van systeemprogrammatuur die toelaat het O.S. (MS-DOS) gewoonweg te negeren en vanuit user software rechtstreeks de hardware aan te spreken.
o
Device drivers kunnen samenwerken met dat O.S. zonder dat ze in de code er van moeten opgenomen zijn.
Let wel: steeds wanneer een device driver niets te doen heeft, is hij geblokkeerd! Hun werking moet je dus zo bekijken: o
Bij het opstarten van het O.S. wordt alle device drivers geïnitialiseerd (de 'andere operaties').
o
De drivers bestaan alle uit een oneindige lus: de eerste instructie van die lus bestaat erin te wachten (blokkeren) tot een proces een bericht stuurt. Dit bericht zorgt er voor dat de driver gedeblokkeerd geraakt en dat hij zijn taak, vermeld in het toegezonden bericht, kan uitvoeren.
o
Na de uitvoering van de opgedragen opdracht stuurt de driver een antwoordbericht naar het opdrachtgevende proces (dat ondertussen zelf geblokkeerd werd!). Dat bericht heeft twee functies: 1. Het resultaat van de I/O handeling meedelen aan de opdrachtgever. 2. De opdrachtgever deblokkeren.
o
De driver begint aan een nieuwe cyclus van de oneindige lus: blokkeren tot een nieuwe opdracht arriveert.
Niets staat je dus principieel in de weg om als knutselaar een besturing te realiseren van, zeg maar, je verwarmingsinstallatie, je treintafel of wat dan ook. Dit gegeven, gecombineerd met een vrije markt economie, heeft ontegensprekelijk voor een niet te verwaarlozen gedeelte het informaticalandschap gecreëerd zoals we dat nu kennen.
Bekijken we nu met deze wetenschap even opnieuw wat er principieel moet gebeuren bij het toevoegen van een nieuw randapparaat aan een configuratie: o
De device controller wordt aangesloten op de systeembus.
Operating systems
144
Operating systems
145
7.3.
I/O System in Windows 2000
7.3.1.
Design Goals
The design goals for the Windows 2000 I/O system include the following: o
Make I/O processing fast on both single and multiprocessor systems.
o
Protect shareable resources by using the standard Windows 2000 security mechanisms.
o
Meet the requirements for I/O services dictated by the Microsoft Win32, OS/2, and POSIX subsystems.
o
Provide services to make device driver development as easy as possible and allow drivers to be written in a high-level language.
o
Allow device drivers to be added or removed from the system dynamically, based on user direction or automatic configuration as the result of the addition or removal of a hardware device from the system.
o
Allow for the addition of drivers that transparently modify the behavior of other drivers or devices, without requiring any changes to the driver whose behavior or device is modified.
o
Provide support for multiple installable file systems, including FAT, the CDROM file system (CDFS), the Universal Disk Format (UDF) file system, and the Windows 2000 file system (NTFS). (See Chapter 12 for more specific information on file system types and architecture.)
o
Allow the system and individual hardware devices to enter and leave lowpower states to prolong battery life and conserve energy.
7.3.2.
The PnP manager works closely with the I/O manager and a type of device driver called a bus driver to guide the allocation of hardware resources as well as to detect and respond to the arrival and removal of hardware devices. The PnP manager and bus drivers are responsible for loading a device's driver when the device is detected. When a device is added to a system that doesn't have an appropriate device driver, the executive Plug and Play component calls on the device installation services of a user-mode PnP manager. The power manager also works closely with the I/O manager to guide the system, as well as individual device drivers, through power-state transitions.
I/O System Components
The Windows 2000 I/O system consists of several executive components as well as device drivers, which are shown in Figure 9-1. o
The I/O manager connects applications and system components to virtual, logical, and physical devices, and defines the infrastructure that supports device drivers.
o
A device driver typically provides an I/O interface for a particular type of device. Device drivers receive commands routed to them by the I/O manager that are directed at devices they manage, and they inform the I/O manager when those commands complete. Device drivers often use the I/O manager to forward I/O commands to other device drivers that share in the implementation of a device's interface or control.
Operating systems
Figuur 7-5
146
Windows Management Instrumentation (WMI) support routines, called the Windows Driver Model (WDM) WMI provider. The registry serves as a database that stores a description of basic hardware devices attached to the system as well as driver initialization and configuration settings. INF files, which are designated by the .inf extension, are driver installation files. INF files are the link between a particular hardware device and the driver that assumes primary control of the device. They are made up of scriptlike instructions describing the device they correspond to, the source and target locations of driver files, required driver-installation registry modifications, and driver dependency information. Digital signatures that Windows 2000 uses to verify that a driver file has
Operating systems
147
passed testing by the Microsoft Windows Hardware Quality Lab (WHQL) are stored in .cat files. The hardware abstraction layer (HAL) insulates drivers from the specifics of the processor and interrupt controller by providing APIs that hide differences between platforms. In essence, the HAL is the bus driver for all the devices on the computer's motherboard that aren't controlled by other drivers. Most I/O operations don't involve all the components just described. A typical I/O request starts with an application executing an I/O-related function (for example, reading data from a device) that is processed by the I/O manager, one or more device drivers, and the HAL. In Windows 2000, threads perform I/O on virtual files. The operating system abstracts all I/O requests as operations on a virtual file, hiding the fact that the target of an I/O operation might not be a file-structured device. This abstraction generalizes an application's interface to devices. A virtual file refers to any source or destination for I/O that is treated as if it were a file (such as files, directories, pipes, and mailslots). All data that is read or written is regarded as a simple stream of bytes directed to these virtual files. User-mode applications (whether Win32, POSIX, or OS/2) call documented functions, which in turn call internal I/O system functions to read from a file, write to a file, and perform other operations. The I/O manager dynamically directs these virtual file requests to the appropriate device driver.
7.3.3.
•
Legacy drivers are device drivers written for Microsoft Windows NT but that run unchanged on Windows 2000. They are differentiated from other Windows 2000 drivers in that they don't support power management or work with the Windows 2000 PnP manager. If the driver controls a hardware device, that driver might limit the power management and Plug and Play capabilities of the system.
•
Win32 subsystem display drivers translate device-independent graphics requests into device-specific requests. The device-specific requests are then paired with a kernel-mode video miniport driver to complete video display support. A display driver is responsible for implementing drawing operations, either by writing directly to the frame buffer or by communicating with the graphics accelerator chip on the controller. The miniport driver is responsible for global changes to the state of the display controller, such as mode setting (screen resolution, refresh rate, pixel depth, and so on) as well as cursor (pointer) positioning and loading the color lookup table.
•
WDM drivers are device drivers that adhere to the Windows Driver Model (WDM). WDM includes support for Windows 2000 power management, Plug and Play, and WMI. WDM is implemented on Windows 2000, Windows 98, and Windows Millennium Edition, so WDM drivers are source-compatible between these operating systems and in many cases are also binary compatible. There are three types of WDM drivers:
o
Bus drivers manage a logical or physical bus. Example buses include PCMCIA, PCI, USB, IEEE 1394, and ISA. A bus driver is responsible for detecting and informing the PnP manager of devices attached to the bus it controls as well as managing the power setting of the bus.
o
Function drivers manage a particular type of device. Bus drivers present devices to function drivers via the PnP manager. The function driver is the driver that exports the operational interface of the device to the operating system. In general, it's the driver with the most knowledge about the operation of the device.
o
Filter drivers logically layer above or below function drivers, augmenting or changing the behavior of a device or another driver. For example, a keyboard capture utility could be implemented with a keyboard filter driver that layers above the keyboard function driver.
Device Drivers in Windows 2000
7.3.3.1. Types of Device Drivers Windows 2000 supports a wide range of different device driver types and programming environments. Even within a type of device driver, programming environments can differ, depending on the specific type of device for which a driver is intended. In this chapter, the focus is on kernel-mode device drivers. There are many different types of kernel-mode drivers, which can be divided into the following broad categories: •
•
File system drivers accept I/O requests to files and satisfy the requests by issuing their own, more explicit requests to mass storage or network device drivers. Windows 2000 drivers are device drivers that integrate with the Windows 2000 power manager and PnP manager, when required. They include drivers for mass storage devices, protocol stacks, and network adapters.
Operating systems
148
In WDM, no one driver is responsible for controlling all aspects of a particular device. The bus driver is responsible for detecting bus membership changes (device addition or removal), assisting the PnP manager in enumerating the devices on the bus, accessing bus-specific configuration registers, and in some cases, controlling power to devices on the bus. The function driver is generally the only driver that accesses the device's hardware.
Operating systems
149
In addition to the above device driver types, Windows 2000 also supports several types of user-mode drivers: •
Virtual device drivers (VDDs) are used to emulate 16-bit MS-DOS applications. They trap what an MS-DOS application thinks are references to I/O ports and translates them into native Win32 I/O functions, which are then passed to the actual device driver. Because Windows 2000 is a fully protected operating system, user-mode MS-DOS applications can't access hardware directly and thus must go through a real kernel-mode device driver.
•
Win32 subsystem printer drivers translate device-independent graphics requests to printer-specific commands. These commands are then typically forwarded to a kernel-mode port driver such as the parallel port driver (Parport.sys) or the universal serial bus (USB) printer port driver (Usbprint.sys).
Figuur 7-6
Support for an individual piece of hardware is often divided among several drivers, each providing a part of the functionality required to make the device work properly. In addition to WDM bus drivers, function drivers, and filter drivers, hardware support might be split between the following components: •
Class drivers implement the I/O processing for a particular class of devices, such as disk, tape, or CD-ROM.
•
Port drivers implement the processing of an I/O request specific to a type of I/O port, such as SCSI, and are also implemented as kernel-mode libraries of functions rather than actual device drivers.
•
Miniport drivers map a generic I/O request to a type of port into an adapter type, such as a specific SCSI adapter. Miniport drivers are actual device drivers that import the functions supplied by a port driver.
An example will help demonstrate how these device drivers work. o A file system driver accepts a request to write data to a certain location within a particular file. o It translates the request into a request to write a certain number of bytes to the disk at a particular "logical" location. o It then passes this request (via the I/O manager) to a simple disk driver. o The disk driver, in turn, translates the request into a physical location (cylinder/track/sector) on the disk and manipulates the disk heads to write the data.
This figure illustrates the division of labor between two layered drivers. The I/O manager receives a write request that is relative to the beginning of a particular file. The I/O manager passes the request to the file system driver, which translates the write operation from a file-relative operation to a starting location (a sector boundary on the disk) and a number of bytes to read. The file system driver calls the I/O manager to pass the request to the disk driver, which translates the request to a physical disk location and transfers the data. Because all drivers—both device drivers and file system drivers—present the same framework to the operating system, another driver can easily be inserted into the hierarchy without altering the existing drivers or the I/O system. For example, several disks can be made to seem like a very large single disk by adding a driver. Such a driver exists in Windows 2000 to provide fault tolerant disk support. (Whereas the driver is present on all versions of Windows 2000, fault tolerant disk support is available only on Windows 2000 Server versions.) This logical, volume manager driver is located between the file system and the disk drivers, as shown in Figure 7-7.
This layering is illustrated in Figure 7-6 .
Operating systems
150
Operating systems
151
o
An initialization routine The I/O manager executes a driver's initialization routine, which is typically named DriverEntry, when it loads the driver into the operating system. The routine fills in system data structures to register the rest of the driver's routines with the I/O manager and performs any global driver initialization that's necessary.
o
An add-device routine A driver that supports Plug and Play implements an add-device routine. The PnP manager sends a driver notification via this routine whenever a device for which the driver is responsible is detected. In this routine, a driver typically allocates a device object (described later in this chapter) to represent the device.
o
A set of dispatch routines Dispatch routines are the main functions that a device driver provides. Some examples are open, close, read, and write and any other capabilities the device, file system, or network supports. When called on to perform an I/O operation, the I/O manager generates an IRP and calls a driver through one of the driver's dispatch routines.
o
A start I/O routine The driver can use a start I/O routine to initiate a data transfer to or from a device. This routine is defined only in drivers that rely on the I/O manager for IRP serialization. The I/O manager serializes IRPs for a driver by ensuring that the driver processes only one IRP at a time. Most drivers process multiple IRPs concurrently, but serialization makes sense for some drivers, such as a keyboard driver.
o
An interrupt service routine (ISR) When a device interrupts, the kernel's interrupt dispatcher transfers control to this routine. In the Windows 2000 I/O model, ISRs run at device interrupt request level (DIRQL), so they perform as little work as possible to avoid blocking lower-level interrupts unnecessarily. (See Chapter 3 for more information on IRQLs.) An ISR queues a deferred procedure call (DPC), which runs at a lower IRQL (DPC/dispatch level), to execute the remainder of interrupt processing. (Only drivers for interruptdriven devices have ISRs; a file system driver, for example, doesn't have one.)
o
An interrupt-servicing DPC routine A DPC routine performs most of the work involved in handling a device interrupt after the ISR executes. The DPC routine executes at a lower IRQL DPC/dispatch level than that of the ISR, which runs at device level, to avoid blocking other interrupts unnecessarily. A DPC routine initiates I/O completion and starts the next queued I/O operation on a device.
o
Although the following routines aren't shown in Figure 7-7, they're found in many types of device drivers:
o
One or more I/O completion routines A layered driver might have I/O completion routines that will notify it when a lower-level driver finishes
Figuur 7-7
7.3.3.2. Structure of a Driver The I/O system drives the execution of device drivers. Device drivers consist of a set of routines that are called to process the various stages of an I/O request. Figure 7-8 illustrates the key driver-function routines, which are described below.
Figuur 7-8
Operating systems
152
Operating systems
153
processing an IRP. For example, the I/O manager calls a file system driver's I/O completion routine after a device driver finishes transferring data to or from a file. The completion routine notifies the file system driver about the operation's success, failure, or cancellation, and it allows the file system driver to perform cleanup operations. o
o
A cancel I/O routine If an I/O operation can be canceled, a driver can define one or more cancel I/O routines. When the driver receives an IRP for an I/O request that can be canceled, it assigns a cancel routine to the IRP. If a thread that issues an I/O request exits before the request is completed or cancels the operation (with the CancelIo Win32 function, for example), the I/O manager executes the IRP's cancel routine if one is assigned to it. A cancel routine is responsible for performing whatever steps are necessary to release any resources acquired during the processing that has already taken place for the IRP as well as completing the IRP with a canceled status. An unload routine An unload routine releases any system resources a driver is using so that the I/O manager can remove them from memory. Any resources acquired in the initialization routine are usually released in the unload routine. A driver can be loaded and unloaded while the system is running.
o
A system shutdown notification routine This routine allows driver cleanup on system shutdown.
o
Error-logging routines When unexpected errors occur (for example, when a disk block goes bad), a driver's error-logging routines note the occurrence and notify the I/O manager. The I/O manager writes this information to an error log file.
8.
Geheugenbeheer
Geheugen is een belangrijk onderdeel van een computersysteem. Een gedeelte van het operating system zal zich moeten bezighouden met het beheer van het aanwezige geheugen. De voornaamste taken van dit beheer bestaan uit: o o o o
Bijhouden welke delen van het geheugen vrij zijn en welke niet. Toekennen van geheugen aan processen. Vrijgeven van geheugen als een proces verwijderd wordt. Swapping: op disk zetten van stukken geheugen wanneer er niet genoeg plaats is om alle processen in het interne geheugen te zetten.
Ruwweg kunnen we memory management systemen indelen in twee grote groepen, en dit naargelang de relatieve afmetingen van het intern geheugen: o
Het interne geheugen is groot genoeg om alle opgestarte processen te bevatten.
o
Het vorige is niet het geval, zodat tijdens de uitvoering van processen regelmatig geheugen moet getransfereerd worden tussen het inwendige en een uitwendig geheugen (meestal een harde schijf). Dit transfereren van geheugen noemt men swapping.
8.1.
Systemen zonder swapping
8.1.1.
Monoprogramming zonder swapping
De allereenvoudigste manier om het geheugen te beheren is de volgende: in het interne geheugen (dat we in dit hoofdstuk zullen afkorten tot mm, 'main memory') zit slechts één proces en dat kan heel het mm benutten. Een user proces wordt in het geheugen geladen en het is dit proces dat vanaf dat ogenblik heel de computer beheert. Dit houdt o.a. in dat dit user proces alle taken van een O.S. moet waarnemen! Het moet dus al zeker drivers bevatten voor elke I/O device die gebruikt wordt. Deze volgens huidige normen ontoelaatbare manier van werken wordt dan ook nergens meer gebruikt, ook niet in de meest simpele huiscomputers. Tegenwoordig wordt in zeer eenvoudige microcomputers het volgende systeem toegepast: het geheugen wordt opgesplitst in 2 stukken: één bevat het O.S. en het andere kan één user proces bevatten. Deze manier van werken kan op verschillende manieren gerealiseerd worden: ze zijn weergegeven in afbeelding 8-1. De IBM PC gebruikt het derde model. Het programma in ROM heet daar BIOS (Basic Input Output System).
Operating systems
154
Operating systems
155
O.S in ROM
Device drivers In ROM
User Program
User Program
User Program
O.S in RAM
(afbeelding 8-2(b)). o
Bij een variante hiervan wordt de rij afgezocht naar het eerste proces dat de vrijgekomen partitie het beste opvult. Dit benadeelt dan echter weer de kleinere processen.
O.S in RAM
Figuur 8-1
8.1.2.
Multiprogramming
Wanneer meerdere processen zich tegelijkertijd in het geheugen bevinden, moet dit opgedeeld worden: die delen noemt men partities. We bekijken eerst een systeem van memory management waarbij die partities in de tijd vaste afmetingen hebben.
Figuur 8-2
Twee soorten van problemen moeten daarbij opgelost worden: o o
De toekenning van geheugenruimte aan processen. Relocatie en protectie (zie verder); 8.1.2.2. Relocatie en protectie.
8.1.2.1. De toekenning van geheugenruimte aan processen. Verschillende algoritmen kunnen gehanteerd worden om de partities op te vullen met processen die moeten worden uitgevoerd.
Een belangrijk iets waarmee rekening moet gehouden worden bij multiprogramming, is het feit dat een proces moet kunnen uitgevoerd worden, onafhankelijk van zijn plaats in het geheugen (relocatieprobleem).
Een bedenking die hierbij een rol speelt, is het feit dat wanneer een proces zich in een partitie bevindt, de rest van de partitie onbruikbaar is voor iemand anders.
Bekijken we wat er gebeurt bij het uitvoeren van een statisch gelinkt programma. Bij het maken van zo een een programma wordt de uiteindelijke, uitvoerbare code geproduceerd door de linker. Stel dat de eerste instructie een oproep bevat naar een procedure die zich bevindt op adres 75 t.o.v. de eerste instructie. Wanneer dat programma zich bevindt in partitie 1 (afbeelding 7-2), zal het resultaat een sprong zijn naar adres 75: middenin het O.S.! Hetgeen zou moeten gebeuren, is een sprong naar adres 100K + 75. Als het proces loopt in partitie 2, moet het een sprong worden naar adres 200K + 75. Dit probleem geldt niet alleen voor proceduresprongen maar voor alle adresverwijzingen!
Enkele algoritmen : o
o
De uit te voeren processen worden gesorteerd in verschillende wachtrijen zodat elk proces zich bevindt voor de kleinst mogelijke partitie waar het in kan (afbeelding 8-2(a)). Een nadeel is dat het heel goed mogelijk is, zoals in de afbeelding op te merken valt, dat voor een grote partitie niemand staat te wachten, en voor een kleine een heleboel processen staan aan te schuiven.
Een tweede op te lossen probleem bij multiprogramming is protectie, de bescherming van de verschillende partities: het is vrij lastig wanneer een proces ineens data begint weg te schrijven in een andere partitie dan de zijne.
De processen zitten in één wachtrij: telkens een partitie vrij komt, wordt het eerste proces dat er in kan uit de rij genomen en in het geheugen geladen
Operating systems
156
Operating systems
157
Voor de relocatie en protectie bestaan verschillende oplossingen; we bekijken er hier twee. Een eerste oplossing bestaat erin tijdens het inladen van het programma, in de code alle adressen te wijzigen. In het voorbeeld zouden dus alle adresverwijzingen van een proces dat in partitie 1 geladen wordt, vermeerderd worden met 100K. Werken op deze manier houdt dan wel in dat de linker, samen met de code, een lijst produceert met alle te veranderen adressen. Onnodig te zeggen dat dit vrij veel overhead met zich meebrengt. Deze manier van werken lost bovendien het protectieprobleem niet op. Een andere benadering is de volgende (afbeelding 8-3): o
De hardware is uitgerust met twee speciale registers: het zogenaamde base en limit register.
o
Bij de uitvoering van een programma bevat het base register het beginadres van de partitie waarin het proces zich bevindt, en het limit register de lengte ervan. De code van het proces wordt ongewijzigd ingeladen.
o o
Alvorens een instructie wordt uitgevoerd, wordt bij het adres het base adres bijgeteld; dan pas wordt ze uitgevoerd. In ons voorbeeld wordt de sprong naar adres 75 omgevormd tot een sprong naar 100K + 75 alvorens de instructie de CPU bereikt. De code zelf wordt echter niet veranderd. Hiermee is de relocatie verwezenlijkt.
o
Elke adresverwijzing wordt gecontroleerd met behulp van het limit register om te zien of er geen referentie gebeurt buiten de partitie zodat eveneens het probleem van protectie is opgelost.
8.2.
Swapping
8.2.1.
Multiprogramming met variabele partities
In een batch systeem is de voorgaande oplossing doorgaans voldoende. In een interactieve omgeving echter, zijn er dikwijls meer processen aanwezig dan dat er in het interne geheugen kunnen. Swapping is dan noodzakelijk. Op dat ogenblik wordt er bij het werken met vaste partities te veel intern geheugen verspild door processen die hun partitie 'slecht' opvullen. Een werkwijze met variabele partities dringt zich dan ook op. Daarbij wordt de geheugenruimte per proces toegekend, en dit naargelang de noden van het proces. Afbeelding 8-4 schetst een aantal opeenvolgende situaties.
Figuur 8-4
o o o o o o o
A zit in mm B komt erbij C ook A is beëindigd of wordt uitgeswapt D wordt ingeladen B is beëindigd of wordt uitgeswapt E wordt ingeladen.
De flexibiliteit die alzo verkregen wordt, komt het efficiënt gebruik van het geheugen ten goede, maar aan de andere kant is het duidelijk dat het algoritme dat heel de boel verzorgt, ingewikkelder wordt.
Figuur 8-3
Voor we enkele van die algoritmen bekijken, eerst nog twee bedenkingen:
Operating systems
158
Operating systems
159
o
Het is onvermijdelijk dat er gaten ontstaan in mm. Die kunnen opgevuld worden door op bepaalde tijdstippen alle aanwezige processen naar beneden op te schuiven. Dit is echter een tijdrovende bezigheid die enkel lonend is op krachtige systemen.
o
Processen kunnen meer geheugen nodig hebben naarmate ze uitgevoerd worden. Het is daarom een goed idee om steeds een extra stuk geheugen toe te kennen.
1. 2. 3. 4.
P/H: gaat het om een proces of een gat (hole)? beginadres van het segment lengte van het segment pointer naar de volgende knoop
Vertrekkend van deze basisidee zijn een massa werkwijzen mogelijk. Bekijken we die waarbij de lijst gesorteerd is volgens startadres (zoals in afbeelding 8-6). Bij het verwijderen van een proces (beëindigd of uitgeswapt) is het aanpassen van de lijst zeer eenvoudig.
8.2.1.1. Memory management met behulp van bit maps Werking: o o o o
Het geheugen wordt ingedeeld in zogenaamde allocation units Er wordt een bitmap bijgehouden (1 bit per unit): 0 als unit vrij is 1 als unit bezet is Figuur 8-6
Figuur 8-5
Voor het toekennen van geheugenruimte aan een proces (een nieuw of een ingeswapt) kunnen weer een hele reeks algoritmen gehanteerd worden. We bekijken er hier een drietal. First Fit De lijst wordt afgelopen tot een gat gevonden wordt dat groot genoeg is om het proces te bevatten. Dit gat wordt verdeeld in twee stukken: een voor het proces en de overschot blijft een hole. First Fit is een snel algoritme omdat er zo kort mogelijk gezocht wordt.
Enkele bedenkingen: o
De keuze van de grootte van de units is belangrijk: klein: grote bitmap groot: veel geheugen verspild
o
De implementatie is eenvoudig, de werking echter redelijk traag. Wanneer bijvoorbeeld een proces moet ingeladen worden dat 7 units nodig heeft, moet in de bitmap gezocht worden naar een plaats met 7 achtereenvolgende nullen. Dit is tijdrovend. 8.2.1.2. Memory management met gelinkte lijsten
Het geheugen is ook hier onderverdeeld in allocation units, alleen gebruiken we een andere datastructuur om het geheel voor te stellen, namelijk gelinkte lijsten. Elk element van de lijst heeft 4 velden:
Operating systems
160
Next Fit Idem, alleen wordt bijgehouden waar ergens in de lijst we ons bevonden. Het zoeken kan dan vanaf daar beginnen voor een proces dat minstens even groot is. Best Fit De hele lijst wordt afgezocht naar een gat waarin het proces het best past. In plaats van een groot gat ( dat later misschien nodig is ) te splitsen, probeert het algoritme een gat te vinden dat net groot genoeg is. Best Fitis langzamer dan First Fit omdat telkens de hele lijst moet doorzocht worden. Verrassend genoeg leidt Best Fit ook tot meer verspild geheugen dan First of Next Fit, omdat het geheugen vol geraakt met kleine, onbruikbare gaten. First Fit produceert gemiddeld grotere gaten. In het voorbeeld van afbeelding 8-6 zal een proces met grootte 2 met First Fit terechtkomen op 5 en met best Fit op 18. Operating systems
161
Enkele voorkomende varianten : o
2 aparte lijsten: een voor processen, een voor gaten. Op die manier besteedt elk van de algoritmen al zijn energie aan het inspecteren van gaten en niet van processen. De prijs die voor deze versnelling betaald moet worden is de grotere complexiteit én de vetraging bij de deallocatie, want een vrijgegegeven segment moet uit de lijst processen worden verwijderd en ingevoegd worden in de lijst van gaten.
o
de lijst(en) zijn gesorteerd volgens grootte van de segmenten. Dit werkt uiteraard beter bij Best Fit.
o
Worst Fit : Hier wordt steeds voor het grootste gat gekozen, het overblijvende deel kan zo groot genoeg blijven om nog bruikbaar te zijn. Simulaties hebben aangetoond dat ook Worst Fit geen verstandig systeem is.
o
Quick Fit : aparte lijsten worden bijgehouden voor grootten die vaak worden aangevraagd. Dit algoritme kan bijvoorbeeld een tabel met n elementen hebben, waarbij het eerste element een pointer is naar een kop van een lijst gaten van 4K; het tweede element is dan een pointer naar een lijst gaten van 8K, het derde een pointer naar een lijst gaten van 12K enz... Met Quick Fit kan buitengewoon snel een gat van gewenste grootte gevonden worden. Het nadeel is hetzelfde als dat van alle systemen die gaten op grootte sorteren: het opzoeken van buren wanneer een proces klaar is of uitgeswapt wordt kan lang duren. Als er niet wordt samengevoegd wordt het geheugen snel gefragmenteerd in een groot aantal kleine, onbruikbare gaten.
8.2.1.3. Memory management met het buddy-systeem. We hebben in de vorige paragraaf gezien dat de allocatie zeer snel werd als we alle gaten in een of meer lijsten gesorteerd bewaarden op gatgrootte, maar dat de deallocatie langer duurde omdat alle gatenlijsten moesten doorzocht worden om de buren van het desbetreffende segment te vinden. Het buddy-systeem is een algoritme voor geheugenbeheer dat van het feit dat computers met binaire getallen adresseren gebruik maakt om het samenvoegen van naast elkaar liggende gaten te versnellen wanneer een proces eindigt of wordt uitgeswapt.
Figuur 8-7
Operating systems
162
Dit algoritme werkt als volgt. De geheugenmanager houdt een lijst bij met vrije blokken van 1, 2, 4, 8, 16 enz. Bytes, tot de grootte van het hele geheugen. Bij een geheugen van 1 MB zijn bijvoorbeeld 21 van deze lijnen nodig, lopend van 1 byte tot 1 MB. Aanvankelijk is het gehele geheugen vrij en bevat de lijst van 1 MB één element dat één gat van 1 MB representeert. De andere lijsten zijn leeg. Laat ons nu eens kijken wat er gebeurt wanneer er een proces van 70 K wordt ingeswapt. Omdat de gatenlijsten alleen bestemd zijn voor machten van 2 is er een blok van 128 K nodig, omdat dit de kleinste macht van 2 is die groot genoeg is.Er is geen blok van 128 K beschikbaar en ook geen van 256 of 512 K. Daarom wordt het blok van 1 MB gesplist in twee blokken van 512 K – twee zogenaamde buddies – het ene op geheugenadres 0, het andere op geheugenadres 512. Eén daarvan, welk zich op adres 0 bevindt, wordt gespitst in twee buddy blokken, één op 0, het andere op 256 K. Het onderste wordt weer gesplitst in twee blokken van 128 K. Het blok op adres 0 wordt aan het proces toegewezen. Later wordt er een proces van 35 K ingeswapt.. We ronden 35 K omhoog af naar de dichtstbijzijnde macht van twee en zien dat er geen blokken van 64 K beschikbaar zijn. We splitsen het blok van 128 K in twee buddies van 64 K, één op 128 K en één op 192 K. Het blok op 128 wordt toegewezen aan het proces. Bij de derde aanvraag gaat het om 80 K. Laat ons nu eens kijken wat er gebeurt wanneer er een blok weer wordt vrijgegeven. Stel dat blok A, van 128 K ( waarvan er maar 70 K gebruikt wordt ) op dit moment wordt vrijgemaakt. Het wordt gewoon in de lijst met blokken van 128 K geplaatst. Nu is er een blok van 60 K nodig, dus wordt er gekeken of er een voldoende groot blok beschikbaar is. Het blok van 64 K op adres 192 K is voldoende, dus dat wordt toegewezen. Nu wordt blok B teruggegeven. Op dit moment hebben we een blok van 128 K op adres 0 en een blok van 64 K op adres 128 K vrij. Samenvoegen is nog niet mogelijk. Zelfs als het blok van 128 K op adres 0 zou gesplitst zijn in een blok van 64 K op adres 0 dat in gebruik was en een vrij blok op 64 K, zou er niet worden samengevoegd. Wanneer blok D wordt teruggegeven, kunnen we het blok van 256 K op adres 0 herstellen. En wanneer ten slotte blok C wordt teruggegeven, krijgen we weer de oorspronkelijke systeemconfiguratie van één gat van 1 MB. Buddy systemen hebben een voordeel ten opzichte van algoritmes die blokken op grootte sorteren maar niet noodzakelijk een veelvoud van die blokgrootte zijn. Het voordeel is dat de geheugenmanager, wanneer er een blok van 2K wordt vrijgegeven alleen in de lijst van 2K gaten hoeft te zoeken om vast te stellen of samenvoegen mogelijk is. Bij andere systemen moeten alle gatenlijsten worden doorzocht. Het resultaat is dat het buddy-systeem snel is. Helaas is deze methode ook buitengewoon inefficiënt qua gebruiksgraad van het geheugen. Dat komt uiteraard omdat alle aanvragen moeten worden afgerond naar een macht van twee. Aan een proces van 35 K moet 64 K worden toegewezen. Deze vorm van overhead noemen we interne fragmentatie.
Operating systems
163
8.3.
memory management unit (MMU). Het is dus de MMU die ervoor zorgt dat de adressen op de juiste manier 'aangepast' worden.
Virtueel geheugen
Een probleem apart vormen processen die te groot zijn om in hun totaliteit in het mm te geraken. Een veelgebruikte techniek hierbij was het werken met zogenaamde overlays: het grote programma bestaat uit verschillende aparte stukken en ‘at runtime’ worden de nodige overlays ingeladen. Dat swappen gebeurt automatisch maar het ontwerpen van de verschillende overlays en ervoor zorgen dat het swappen automatisch gebeurt, is de verantwoordelijkheid van de programmeur. Dit wordt tegenwoordig ook geautomatiseerd en wel met behulp van een systeem dat virtueel geheugen wordt genoemd.
CPU Memory
Paginatie
Bekijken we hoe een geheugenreferentie gebeurt in een systeem zonder virtueel geheugen: er moet bijvoorbeeld gelezen worden wat zich bevindt op geheugenplaats 1000. Dit adres wordt op de adresbus gezet en hetgeen zich in de fysische geheugenlocatie 1000 bevindt, wordt op de databus gezet. Niets staat echter in de weg om op de volgende manier te werken: o
Het programma wil uitlezen wat staat op adres 1000. Die 1000 is echter zuiver vanuit het standpunt van het programma! (= virtueel geheugen).
o
Waar de data echt staan (= fysisch geheugen) heeft uiteindelijk geen belang! Als het O.S. weet dat wanneer het programma een referentie doet naar adres 1000, het die gegevens kan vinden op bvb. 14436, en wel omdat het ze daar zelf gezet heeft, is er geen enkel probleem.
o
Bus
We zullen zo'n mapping bekijken aan de hand van een concreet voorbeeld om daarmee de werking van een MMU te onderzoeken.
Die totale virtuele ruimte is aanwezig op disk. Tijdens de uitvoering worden slechts die stukken van de virtuele adresruimte die het programma op een bepaald ogenblik benut, van disk in het fysisch geheugen geladen. Er is dus een mapping systeem nodig van de virtuele in de fysische adresruimte. Naargelang de aard van de stukken die geswapt worden onderscheidt men 2 systemen: paging en segmentatie.
8.3.1.
Het O.S. zet op de adresbus de waarde 14436 en de door het programma gevraagde gegevens worden op de databus geplaatst.
Wat we dus nodig hebben, is een mechanisme dat een mapping doet van de virtuele op de fysische adresruimte. Het stukje hardware dat deze mapping verzorgt is de
In afbeelding 8-9 zien we het geheugen van een 16-bit computer: met die 16 bit kan hij adressen genereren van 0 tot 64K. Hij beschikt intern echter slechts over 32K. Een programma van 64K is op disk aanwezig. De virtuele adresruimte is onderverdeeld in eenheden die we pagina's noemen. Het fysisch geheugen is verdeeld in even grote stukken: page frames. De grootte heeft voor de principiële werking niet zo'n groot belang: we nemen hier 4K zodat er 16 virtuele pagina's zijn en 8 page frames. De transfer tussen disk en mm gebeurt steeds met volledige pagina's. Het basisprincipe is, dat slechts die pagina's (virtueel) die op elk ogenblik nodig zijn voor de uitoefening van het programma, zich in mm bevinden. Het is zo'n situatie die te zien is in afbeelding 7-8: pagina 0 (0-4K) bevindt zich in page frame 2 (8-12K), pagina 1 in frame 1 (4-8K), enzovoort. De tabel waarin deze informatie wordt bijgehouden, bevindt zich in de MMU. De pagina's, aangeduid met een 'X', zitten op dit ogenblik niet in mm.
164
Figuur 8-9
Bekijken we nu wat gebeurt wanneer het programma bijvoorbeeld adres 20 wil benaderen: o
Operating systems
Disk Controller
MMU
De basisidee kan als volgt worden beschreven: Een programma is ontworpen voor een geheugenruimte die groter is dan het fysisch beschikbare geheugen. De ruimte waarin het programma theoretisch loopt heet de virtuele adresruimte.
Figuur 8-8
Schematisch is zo'n situatie voorgesteld in afbeelding 8-8.
Adres 20 komt toe in de MMU.
Operating systems
165
o o
De MMU ziet dat adres 20 valt in pagina 0 (0 tot 4095) en dat die pagina zich bevindt in frame 2 (8192 tot 12387). De MMU verandert het adres 20 in 8192 + 20 = 9012 en zet dat adres op de bus: dat is OK want op die plaats staat het gevraagde!
Dit procédé werkt fijn, maar lost het oorspronkelijke probleem nog niet op: het programma van 64K dat in 32K moet geraken! Bekijken we daarvoor wat gebeurt bij een geheugenreferentie naar een pagina die niet in mm zit: 32780 bijvoorbeeld. 32780 zit 12 byte ver in pagina 8, die begint op 32768. o o o o
Adres 32780 komt toe in de MMU. De MMU ziet dat 32780 valt in pagina 8 en dat pagina 8 niet in mm zit. De MMU deelt dit mee aan het O.S. dat op dat ogenblik de besturing overneemt. Zo'n situatie noemt men een page fault. De memory manager van het O.S. kiest een page frame, en vervangt die door de pagina in kwestie (8). De tabel binnen de MMU wordt aangepast aan de nieuwe toestand en vanaf dan kan die zijn werk terug hervatten zoals in het eerste voorbeeld werd beschreven.
Laten we nu een blik werpen binnen de MMU om te zien hoe deze werkt en waarom we een paginagrootte hebben gekozen die een macht van twee is. In afbeelding 7 – 10 zien we een voorbeeld van een virtueel adres, namelijk 8196 ( 0010 0000 0000 0100 ) , dat wordt afgebeeld met behulp van de MMU Map uit afbeelding 7 – 9 . Het binnenkomende virtuele adres van 16 bits wordt gesplitst in een paginanummer van vier bits en een offset van twaalf bits. Met vier bits voor het paginanummer kunnen we 16 pagina’s representeren en met de twaalf bits voor de offset kunnen we alle 4096 bytes binnen een pagina adresseren. Het paginanummer wordt gebruikt als index in de paginatabel, waardoor het nummer wordt opgeleverd van het paginaframe dat met die virtuele pagina correspondeert. Als de bit Aanwezig / Afwezig 0 is, treedt er een trap naar het operating system op. Als de bit 1 is wordt het paginaframenummer dat in de paginatabel wordt aangetroffen gekopieerd naar de hoogste drie bits Operating systems
van het uitvoeringsregister, samen met de offset van twaalf bit, die ongewijzigd uit het binnenkomende virtuele adres wordt gekopieerd. Samen vormen ze een fysiek adres van 15 bits. Het uitvoeringsregister wordt dan als fysieke geheugenadres op de geheugenbus gezet.
8.3.2.
Paginatabellen
In theorie verloopt het afbeelden van virtuele adressen naar fysieke adressen op de volgende manier. Het virtuele adres wordt gesplitst in een virtueel paginanummer (de hoogste bits) en een offset (de laagste bits). Het virtuele paginanummer wordt gebruikt als index in de paginatabel om het element voor die virtuele pagina te vinden. In het element in de paginatabel wordt het nummer van het paginaframe (als dat er is) gevonden. Het paginaframe-nummer wordt aan de hoge kant van de offset geplakt, waar het in de plaats komt van het virtuele paginanummer; het geheel vormt dan een fysiek adres dat naar het geheugen kan worden gestuurd. Het doel van de paginatabel is virtuele pagina's af te beelden op paginaframes. Wiskundig gesproken is de paginatabel een functie met het virtuele paginanummer als argument en het fysieke framenummer als resultaat. Met het resultaat van deze functie kan het veld voor de virtuele pagina in een virtueel adres worden vervangen door een veld voor een paginaframe, waardoor we een fysiek geheugenadres krijgen. Ondanks het feit dat deze beschrijving zo eenvoudig is, moeten we met twee factoren rekening houden: o o
De paginatabel kan zeer groot worden. De afbeelding moet snel zijn.
Het eerste punt volgt uit het feit dat tegenwoordige computers virtuele adressen van minstens 32 bits gebruiken. Met bijvoorbeeld een paginagrootte van 4 K heeft een adresruimte met 32 bits 1 miljoen pagina's - om van het aantal pagina’s bij adressen van 64 bits maar niet te spreken. Als er een miljoen pagina's zijn, moet de paginatabel een miljoen elementen omvatten. En denk eraan dat elk proces zijn eigen paginatabel nodig heeft. Het tweede punt is een gevolg van het feit dat do afbeelding van virtueel naar fysiek voor elk gebruik van het geheugen moet plaatsvinden. De meeste instructies hebben een instructiewoord en vaak ook nog een geheugenoperand. Daarom zijn er per instructie 1, 2 of soms nog meer verwijzingen naar de paginatabel nodig. Als een instructie bijvoorbeeld 10 nsec duurt, zoals dat bij veel werkstations het geval is, mag het raadplegen van de paginatabel maar een paar nanoseconden duren. De eis van grote, snelle pagina-mapping is een belangrijk punt waarmee bij het ontwerp van computers rekening moet worden gehouden. Hoewel dit probleem het ernstigst is bij dure machines, is het ook bij goedkope van belang, waar de prijs en de verhouding tussen prijs en prestaties essentieel zijn. In deze en de volgende paragrafen zullen we het ontwerp van paginatabellen in detail bekijken en een aantal hardwareoplossingen zien die in echte computers zijn toegepast.
Figuur 8-10
166
Operating systems
167
Het eenvoudigste ontwerp (althans conceptueel) is het gebruik van één paginatabel bestaande uit een array van snelle hardwareregisters, met één element voor elke virtuele pagina, geïndexeerd met het virtuele paginanummer. Wanneer een proces wordt opgestart, laadt het Operating System de registers met de paginatabel van het proces, die wordt overgenomen uit een exemplaar dat in het hoofdgeheugen staat. Tijdens de uitvoering van het proces zijn er voor de paginatabel geen verwijzingen naar het geheugen meer nodig. De voordelen van deze methode zijn de eenvoud en het feit dat er geen verwijzingen naar het geheugen nodig zijn tijdens het afbeelden. Een nadeel is dat deze methode duur kan zijn (als de paginatabel groot is). Ook het laden van de paginatabel bij elke wisseling van context kan de snelheid nadelig beïnvloeden. Het andere uiterste is dat de paginatabel in zijn geheel in het hoofdgeheugen kan staan. Voor de hardware is dan alleen één register nodig dat naar het begin van de paginatabel wijst. Bij dit ontwerp kan de geheugenmap bij een wisseling van context worden veranderd door één register opnieuw te laden. Dit heeft natuurlijk het nadeel dat er een of meer verwijzingen naar het geheugen nodig zijn om tijdens de uitvoering van elke instructie elementen van de paginatabel te lezen. Om deze reden wordt deze aanpak zelden in zijn zuiverste vorm gebruikt.
8.3.3.
Paginatabellen met meerdere niveaus
Om het probleem op te lossen dat er voortdurend geweldige paginatabellen in het geheugen moeten staan, wordt op veel computers een paginatabel met meerdere
Figuur 8-11
niveaus gebruikt. In figuur 8-11 is een eenvoudig voorbeeld te zien. In afbeelding 8-11(a) hebben we een virtueel adres van 32 bits dat wordt gesplitst in een veld PT1 van 10 bits, een veld PT2 van 10 bits en een veld Offset van 12 bits. Aangezien offsets 12 bits lang zijn, zijn de pagina's 4 K groot en zijn er in totaal 220 pagina's. Het geheim van de methode met paginatabellen met meerdere niveaus is dat niet alle paginatabellen tegelijk in het geheugen hoeven te staan. Met name de paginatabellen die niet nodig zijn, moeten niet in het geheugen staan. Stel dat een proces 12 M nodig heeft - de onderste 4 M geheugen voor de programmatekst, de volgende 4 M voor data en de bovenste 4 M voor de stack. Tussen de bovenkant van de data en de onderkant van de stack is een groot gat dat niet gebruikt wordt. De adresruimte omvat in dat geval meer dan een miljoen pagina’s, maar er zijn maar vier paginatabellen nodig : de tabel van niveau 1 en de drie tabellen van niveau 2 voor 0 tot 4M , 4 M tot 8 M en de bovenste 4 M . De bits Aanwezig / Afwezig in 1021 elementen van de paginatabel van niveau 1 zijn 0 gemaakt, waardoor een paginafout optreedt als ze ooit worden gebruikt. Als dit gebeurt merkt het Operating System dat het proces probeert toegang te krijgen tot geheugen waar het niet mag komen en reageert daar op een passende manier op, bijvoorbeeld door een signaal naar het proces te sturen of het proces af te breken.. Het systeem met paginatabellen op twee niveaus in afbeelding kan worden uitgebreid tot drie, vier of meer niveaus. Meer niveaus geven meer flexibiliteit, maar het is de vraag of het gebruik van meer dan drie niveaus de extra complexiteit waard is.
8.3.4.
Het vervangen van pagina's bij een page fault
In de vorige paragraaf werd bij de afhandeling van een page fault vlotjes genegeerd hoe de te verwijderen page frame gekozen werd door de memory manager. Dat de kwaliteit van het algoritme dat die keuze maakt, bepalend is voor de performantie van het systeem, is vrij evident. Stel dat steeds pagina's gekozen worden die heel frequent gebruikt worden: door hun heen en weer geswap zouden ze de uitvoering van het programma hopeloos vertragen! De kunst bestaat er dus in telkens een pagina te verwijderen, waarvan de kans groot is dat ze niet direct daarna terug moet binnengeladen worden. Vermits het O.S. echter nooit kan voorspellen wat er in de toekomst gaat gebeuren, is dit niet zo'n heel simpele zaak. We zullen enkel veelgebruikte oplossingen voor dit probleem schetsen. 8.3.4.1. Not-Recently-Used algoritme (NRU) Bij deze werkwijze worden met elke pagina twee bits geassocieerd:
Operating systems
168
Operating systems
169
o
R-bit (Referenced bit): wordt op 1 gezet wanneer de pagina benaderd werd (lezen of schrijven).
o
M-bit (Modified bit): wordt op 1 gezet na een schrijfoperatie.
8.3.5.
Dit onderscheid (lezen/schrijven) wordt gemaakt omdat een page frame waarin enkel gelezen werd, bij vervanging, niet terug op disk moet weggeschreven worden. Regelmatig (bijvoorbeeld om de 20 ms) wordt het R-bit van alle pagina's terug op 0 gezet. Op die manier verkrijgt de memory manager een ruw beeld van welke pagina's recentelijk werden benaderd en welke niet. Op basis van die twee bits ontstaan vier klassen van pagina's: klasse klasse klasse klasse
0: 1: 2: 3:
Bij paginering bekijkt men de virtuele adresruimte als één aaneengesloten adresseerbaar geheel waarin de gebruiker (programmeur) zowel instructies als data kwijt kan. Anders gezegd: in de ogen van de gebruiker zijn er geen 'aparte delen' voor entiteiten die functioneel zeer verschillend zijn. De techniek van segmentering brengt daarin verandering in die zin dat de gebruiker tegen het geheugen kan aankijken als tegen een verzameling van gescheiden stukken geheugen (segmenten) die elk hun eigen functie hebben. Bekijken we even wat er aan informatie geproduceerd wordt bij de constructie van een programma (de lijst is niet exhaustief): o o o o o
niet benaderd, niet gewijzigd niet benaderd, wel gewijzigd wel benaderd, niet gewijzigd wel benaderd, wel gewijzigd
Bij een page fault wordt uit de laagste, niet lege klasse, een willekeurige pagina gekozen. Dit vrij eenvoudige algoritme werkt in veel gevallen bevredigend. 8.3.4.2. First-In, First-Out algoritme (FIFO) Een zeer eenvoudig te implementeren algoritme is gebaseerd op een klassieke FIFOredenering: de langst aanwezige vliegt er het eerst uit. Een nadeel hierbij is, dat er op dat ogenblik geen rekening gehouden wordt met de belangrijkheid van de pagina's in kwestie ('belangrijkheid' hier in termen van veel of weinig gebruikt). Dit euvel kan voorkomen worden door met behulp van de eerder genoemde R- en M-bits: behoort de eerste kandidaat tot klasse 0, dan gaat hij eruit, anders bekijken we de volgende in de rij. Zijn er geen van klasse 0, dan gebeurt hetzelfde voor klasse 1 enzovoort.
Segmentering
De code van een hoofdprogramma. De code van de procedures. Een tabel met datastructuren, variabelen, constanten. Een stack. Een parse tree met de syntactische analyse van het programma.
Al die stukken zitten achter mekaar in het geheugen en dit heeft zelfs niets te maken met het feit of dat geheugen virtueel is of niet (afb. 8-12(a)). Voor de programmeur zou het handig zijn moest hij kunnen werken met een beeld van (in dit geval) vijf aparte adresruimten, genummerd van 0 t.e.m. 4 (afb. 8-12(b)). Het 'apart' zijn van die adresruimten zou onder andere inhouden dat ze onafhankelijk van elkaar zouden kunnen krimpen en groeien. Dat is duidelijk niet mogelijk in één lineaire ruimte.
Figuur 8-12
8.3.4.3. Least-Recently-Used algoritme (LRU) Een van de beste algoritmen op dit gebied is gebaseerd op de volgende redenering: o
Het blijkt dat pagina's die in de laatste instructies veel gebruikt werden, dat in de eerstvolgende instructies ook zullen zijn.
o
Omgekeerd, pagina's die al een tijdje ongebruikt zijn, zullen in de eerstvolgende instructies waarschijnlijk ook niet benaderd worden.
De te volgen strategie is dan ook: bij een page fault verdwijnt de pagina die het langst niet gebruikt werd. Dat houdt natuurlijk in dat de datastructuur waarin die informatie bijgehouden wordt, aangepast moet worden bij elke geheugenreferentie. Vermits dat, qua snelheid geen eenvoudige zaak is, wordt LRU dikwijls ineens hardwarematig geïmplementeerd. De adressering gebeurt dan met behulp van twee getallen: het segmentnummer en de offset in het betreffende segment. Vermits de uiteindelijke (fysische) Operating systems
170
Operating systems
171
adresruimte lineair is, moet er uiteraard een mechanisme aanwezig zijn dat de ene adresseringswijze omzet in de andere. Dat mechanisme wordt hardwarematig gerealiseerd door middel van een segment table en is voorgesteld in afbeelding 8-13.
Tot slot bekijken we nog enkele voordelen van deze werkwijze. o
Werken met dynamische datastructuren Wanneer het geheugengedeelte, gereserveerd voor data, een vooraf bepaalde afmeting bezit, kunnen problemen ontstaan wanneer een programma at runtime geheugen gaat bijvragen. Door de data in een logisch apart segment te plaatsen, is dit probleem van de baan: het segment zelf kan groeien naargelang de behoeften van het programma.
o
Linken wordt eenvoudiger Stel dat elke procedure in een apart segment geplaatst wordt. Dat heeft voor gevolg dat elke procedure aangeroepen wordt op adres (n,0) waarbij n het nummer van het betreffende segment is. Wordt nu een procedure A die zich bevindt op adres (a,0) veranderd en opnieuw gecompileerd, dan staat ze bij het opnieuw gebruiken nog steeds op adres (a,0), en wat belangrijker is: alle andere procedures staan nog steeds op hun zelfde oude adres!
o
Het gemeenschappelijk gebruik van geheugen wordt eenvoudiger Bekijken we processen onder een grafisch georiënteerde windowomgeving. Elk proces maakt gebruik van monsterachtig grote grafische bibliotheken die 'normaal' gezien in de kode van elk proces afzonderlijk zouden moeten zitten. Dat zou uiteraard een enorme geheugenverspilling inhouden. Door nu die gemeenschappelijke code in aparte segmenten te stoppen hoeft ze slechts éénmaal in het geheugen worden opgenomen; ze kan daar op eenvoudige wijze dynamisch gelinkt worden met elk proces vermits het adres bekend is.
Figuur 8-13
In afbeelding 7-14 wordt deze werking verder verduidelijkt aan de hand van een concreet voorbeeld. .
Figuur 8-14
Operating systems
172
Operating systems
173
9.
Bestandsystemen 9.1.
We zullen eerst een aantal aspecten van bestandssystemen bekijken zoals ze zich aan de gebruikers voordoen. Daarna komen elementen aan bod in verband met de implementatie van file systems. Vervolgens bekijken we enkele aspecten aangaande de veiligheid van files, en tenslotte volgen enkele voorbeelden van concrete systemen.
Bestanden
Elke computertoepassing moet informatie opslaan en ophalen. Terwijl een proces loopt kan het een beperkte hoeveelheid informatie binnen zijn adresruimte opslaan. De manier waarop gegevens (programma's, teksten, tekeningen, ...) door een computersysteem bijgehouden worden, en vooral hoe ze door gebruikers benaderd kunnen worden, is in grote mate bepalend voor de bruikbaarheid van het systeem. Een ideale situatie zou er ongeveer als volgt uitzien: o o o o
Een nieuwe gebruiker start een shell-proces op en krijgt daarmee een enorm grote virtuele adresruimte toegewezen. Alle processen die de gebruiker via zijn shell opstart, gebruiken die zelfde ruimte. De gebruiker stopt die ruimte vol met voor hem/haar logische entiteiten: editors, tekeningen, facturen, … Die entiteiten worden naar believen ondergebracht in een of andere hiërarchische structuur, vastgelegd volgens de noden van de gebruiker.
In de loop van de geschiedenis zijn pogingen ondernomen om zo'n memory-mapped file system te realiseren. MULTICS is daar ongetwijfeld de bekendste onder. Aan deze manier van werken zijn echter ernstige technische en principiële moeilijkheden verbonden: o o o
o
Zo'n enorme virtuele adresruimte vereist te grote adressen. Niet alle computers ondersteunen virtueel geheugen. Wanneer een proces vastloopt, is normaal gezien de daarbij horende adresruimte verloren. Indien dat dan de geheugenruimte is van de gebruiker, wordt dat een lastige zaak. In veel toepassingen worden gegevens gebruikt door meerdere processen tegelijkertijd.
. Meestal werkt men dan ook volgens een fundamenteel andere denkwijze: bedrijfssystemen laten toe aan gebruikers om benoemde objecten te creëren en te manipuleren. Die objecten, files of bestanden genoemd, kunnen allerhande soorten informatie bevatten (programma's, tekst,tekeningen, ...) maar wat er essentieel aan is: files behoren NIET tot de adresruimte van een proces. Bedrijfssystemen leveren aan gebruikers een aantal operatoren (system calls) die toelaten om die speciale dingen te manipuleren: aanmaken, vernietigen, lezen, beschrijven, ... We kunnen drie essentiële eisen aan de opslag van informatie voor de lange termijn stellen : o o o
Het moet mogelijk zijn een zeer grote hoeveelheid informatie op te slaan. De informatie moet blijven bestaan wanneer het proces dat er gebruik van maakt afgelopen is. Het moet mogelijk zijn dat meerdere processen tegelijk toegang hebben tot de informatie.
Operating systems
174
9.1.1.
Naamgeving
Een file is een abstractie van een hoeveelheid gegevens: de interne structuur, de wijze van opslag, het medium van opslag e.d. zijn van geen belang voor het proces dat de file creëert. Hetgeen wel belang heeft, is de wijze waarop de file later terug kan benaderd worden. Daarbij speelt naamgeving een zeer grote rol: het is de gemakkelijkste manier om naar een bestand te verwijzen. Alle operating systems kennen dan ook een of ander mechanisme van naamgeving voor hun bestanden. Enkele aspecten waarin die mechanismen kunnen verschillen, zijn de volgende: o o o o
De lengte van de toegelaten namen. De toegelaten tekens. Het onderscheid tussen hoofd- en kleine letters. Het gebruik van extensies.
file.bak file.bas file.c file.mod file.pas file.ftn file.dat file.doc file.hlp file.obj file.txt file.lib file.exe
backup file BASIC bronkode C bronkode MODULA-2 bronkode Pascal bronkode FORTRAN bronkode file met gegevens documentatie tekst voor een help-commando object kode tekstbestand (algemeen) bibliotheek van obj-files uitvoerbare kode (MS-DOS) Figuur 9-1
Sommige extensies worden opgelegd door het operating system, andere door de gebruikte software, en nog andere kan de gebruiker volkomen vrij kiezen. Zo moet onder MS-DOS bijvoorbeeld, een batchfile de extensie 'bat' hebben en binaire uitvoerbare kode 'com' of 'exe'. De meeste C-compilers vragen dan weer een extensie 'c' en Pascal-compilers 'pas', zonder dat dit een eis is van het operating system. Bij de naamgeving van teksten bij een tekstverwerker zal de gebruiker de extensies tot op zekere hoogte zelf mogen kiezen.
Operating systems
175
9.1.2.
Bestandsstructuren
In afbeelding 9-2 zijn drie voorbeelden van bestandsstructuren weergegeven. Let wel: dit is de wijze waarop bedrijfssystemen de bestanden organiseren en heeft niets te zien met de manier waarop gebruikerssoftware die files presenteren naar gebruikers toe! In systemen als UNIX, MINIX en MS-DOS vanaf versie 2.00 bestaan files uit een sequentie van bytes. CP/M en MS-DOS 1.00 behandelen files als een sequentie van records van constante lengte. Op grote systemen wordt dikwijls een geordende boomwstructuur gehanteerd: Indexed Sequential Access Method (ISAM).
voor die vlucht zonder eerst de records voor duizenden andere vluchten te hoeven lezen. Er zijn twee methoden om aan te geven waar met lezen moet worden begonnen. Bij de eerste methode geeft elke leeshandeling de positie in de file aan waar met lezen moet worden begonnen. Bij de tweede methode wordt er een speciale operatie SEEK gebruikt om de actuele positie in te stellen. Daarna kan de file sequentieel worden gelezen vanaf de positie die nu de huidige positie is. In sommige oudere operating systems voor mainframes worden files als sequentieel of random access ingedeeld op het moment dat ze worden gemaakt. Dan kan het operating system verschillende opslagtechnieken voor de twee soorten gebruiken.In tegenwoordige operating systems wordt dit onderscheid niet gemaakt. Alle files zijn automatisch random access files.
9.1.4.
Attributen
We zagen reeds dat een file bestaat uit haar inhoud, gekoppeld aan een naam. Alle operating systems associëren nog extra informatie aan de file. Die informatie noemt men de attributen. In afbeelding 9-3 staan een reeks mogelijke attributen die kunnen gekoppeld worden aan een file.
Figuur 9-2
.
9.1.3.
Toegang tot files
Oude operating systems hadden maar één vorm van toegang tot een file : sequentiële toegang. In deze systemen kon een proces alle bytes of records in een file lezen in de volgorde waarin ze in de file stonden, te beginnen bij het begin. Het was niet mogelijk elementen over te slaan of ze in een andere volgorde te lezen. Sequentiële files kunnen echter opnieuw worden gestart ( rewind ) zodat ze zo vaak kunnen worden gelezen als nodig is. Sequentiële files zijn handig, wanneer er magneetband in plaats van een schijf als opslagmedium wordt gebruikt. Toen schijven in zwang raakten voor het opslaan van files, werd het mogelijk de protectiebytes of records in willekeurige volgorde uit een file te lezen of records op sleutel in plaats van positie op te zoeken. Files waarvan de bytes of records in elke gewenste volgorde kunnen worden gelezen, worden random access files genoemd.
Protectie Sleutel Creator Eigenaar Read-only flag Hidden flag System flag Archive flag ASCII/binaire flag Directory Record lengte Key positie Key lengte Tijd van creatie Tijd van laatste toegang Tijd van laatste verandering Huidige afmeting
Figuur 9-3
Random access files zijn voor veel toepassingen essentieel, bijvoorbeeld in databasesystemen. Als er iemand opbelt die een plaats voor een bepaalde vlucht wil boeken, moet het reserveringsprogramma toegang kunnen krijgen tot het record
Operating systems
176
wie mag wat doen met de file sleutelwoord dat toegang geeft wie maakte de file van wie is ze is ze beschrijfbaar of niet is ze zichtbaar of niet is het een systeembestand of niet gebeurde er een backup of niet is het een ASCII-file of niet is het een directory of niet aantal bytes in een record plaats van de sleutel in de record lengte van de sleutel wanneer werd ze aangemaakt wanneer werd ze laatst benaderd wanneer werd ze laatst veranderd aantal bytes in de file
Operating systems
177
Niet al deze attributen zijn in een enkel O.S. te vinden. Een MS-DOS bestand bijvoorbeeld besit een naam, extensie, afmeting, datum, tijd, read-only, archive, system en hidden.
9.1.5.
Bewerkingen op bestanden
Bestanden kunnen benaderd worden via system calls. Hieronder staan een reeks veel voorkomende system calls in verband met files. CREATE.
De file is gecreëerd, zonder data. Een aantal attributen worden gezet. DELETE. Wanneer de file overbodig is geworden, wordt ze vernietigd om ruimte vrij te maken op het uitwendig geheugen. OPEN. Vooraleer een file te gebruiken, moet het proces ze openen. De bedoeling is de attributen te verzamelen en de adressen op disk in het geheugen te krijgen zodat ze later vlug benaderd kan worden. CLOSE. Wanneer alle bewerkingen uitgevoerd zijn, wordt het bestand afgesloten om intern geheugen vrij te maken. READ. Gegevens worden uit de file gehaald. Meestal moet de gebruiker zeggen hoeveel bytes nodig zijn en in welke buffer ze moeten geplaatst worden. WRITE. Data worden in de file geschreven, meestal op de huidige positie. APPEND. Een handige variant op het vorige: data worden aan het einde van het bestand bijgeschreven. SEEK. Aanduiden op welke plaats de volgende lees- of schrijfoperatie moet gebeuren. GET ATTRIBUTES Opvragen van de attributen. SET ATTRIBUTES Aanpassen van de attributen. RENAME. Naam veranderen van de file.
9.2.
Directories
9.2.1.
. Figuur 9-4
(a) en (b) tonen elk een directory-file met vier entries: informatie aangaande vier files (file.a, ..., file.d). Bij (a) zit alle informatie over de files in de directory. Bij (b) bevat de directory enkel de naam en een verwijzing naar een andere structuur waar dan alle gegevens over het bestand te vinden zijn. MS-DOS gebruikt (a), UNIX (b). Door nu in de attributen van een bepaalde file aan te duiden dat het hier gaat om een speciaal soort bestand, namelijk een directory-file, schept men de mogelijkheid om hiërarchische structuren van directories op te bouwen. De meeste hedendaagse systemen laten toe om volledig naar eigen goeddunken en noden een eigen hiërarchische structuur te ontwerpen, al dan niet gekoppeld aan een of ander protectiemechanisme dat toelaat bepaalde stukken af te schermen voor andere gebruikers of groepen van gebruikers. Zo'n structuur, of boom wordt dikwijls weergegeven zoals in afbeelding 9-5.
Hiërarchische directory systemen
Een belangrijke taak van het file system, is het aanbieden van een handige manier om bestanden te groeperen, en dit om voor gebruikers de toegang te vergemakkelijken. Centraal daarbij staat het concept directory. Meestal zijn dat files zoals alle andere, alleen bevatten ze een speciale soort informatie, namelijk gegevens over andere files (naam, attributen). Twee veel gebruikte structuren staan in afbeelding 9-4. Figuur 9-5
Operating systems
178
Operating systems
179
9.2.2.
Wat rest is dus: het bestand in stukken hakken (zogenaamde blokken) en die stukken op disk zetten, daar waar plaats is en dit alles los van de logische volgorde binnen het bestand. Het is dan wel noodzakelijk een 'boekhouding' bij te houden zodat het O.S. achteraf nog weet waar welke blokken te vinden zijn.
Padnamen
Twee manieren worden meestal gehanteerd om een bestand binnen zo'n boomstructuur aan te duiden: Elk bestand heeft een absolute padnaam: daarin zitten de namen van alle knooppunten die, vertrekkend vanuit de top van de boomstructuur (root) moeten doorlopen worden om bij de file te 'geraken'. Voorbeeld: /usr/2info/os/filesysteem Men kan ook gebruik maken van een relatieve padnaam. Daarvoor moet eerst het idee van een actieve directory geïntroduceerd worden: een gebruiker kan een bepaalde directory aanduiden en alle padnamen van daaruit laten vertrekken. Voorbeeld: De actieve directory is /usr/2info. De naam os/filesysteem slaat op hetzelfde bestand als hierboven.
Vooraleer iets te zeggen over die 'boekhouding', moet eerst nog de grootte van die blokken bepaald worden. Het ligt voor de hand daarvoor eenheden te kiezen die rechtstreeks aanwezig zijn op disk (track, cilinder, sector) of veelvouden daarvan om de eenvoudige reden dat bijvoorbeeld een halve sector toch nooit gelezen kan worden: de disk drive is nu eenmaal zo geconstrueerd. Statistisch onderzoek heeft uitgewezen dat in UNIX-omgevingen de gemiddelde lengte van files ongeveer 1K bedraagt. Zouden we nu als bloklengte een cilinder (Vb. 32K) nemen, dan zou dat betekenen dat gemiddeld 31/32 disk ruimte ongebruikt is! Afmetingen van 512 byte, 1K en 2K zijn dan ook veel voorkomend.
9.3.2.
De 'boekhouding' van bestanden
We weten nu reeds dat:
9.3.
o
Implementatie van file systems
o
In vorige paragraaf kwamen hoofdzakelijk elementen aan bod waarmee gebruikers rechtstreeks geconfronteerd worden (directory structuur, naamgeving, ...). Nu zullen we enkele elementen bekijken die onder de verantwoordelijkheid vallen van ontwerpers van file systemen. Niettegenstaande het niet de bedoeling is voldoende funderingen te leggen om zelf bedrijfssystemen te schrijven, geeft het bekijken van enkele van die onderwerpen wel een hoop inzichten.
9.3.1.
Bijhouden op disk
Vermits files meestal op disks bewaard worden, is er een sterke koppeling tussen de interne structuur van bestanden en de organisatie van de geheugenruimte op disk, en dit niettegenstaande beide problematieken door verschillende soorten mensen worden benaderd. Het eerste is de zorg van ontwerpers van bedrijfssystemen terwijl het tweede de verantwoordelijkheid is van constructeurs van disk drives. Een basisprobleem dat moet opgelost worden is eigenlijk het volgende: een file, bestaande uit n bytes moet op disk bijgehouden worden. Hoe moet dat gebeuren? De meest voor de hand liggende manier is uiteraard die ‘n’ bytes achtereen op disk weg te schrijven. Een volgend bestand wordt dan geschreven waar het vorige stopte. Voor bestanden die niet van lengte veranderen is dit ontegensprekelijk de meest efficiënte manier. Spijtig genoeg hebben files heel dikwijls de eigenschap, niettegenstaande een gezegende ouderdom, steeds te blijven groeien. Dat zou betekenen dat bij groei, een bestand gekopieerd moet worden naar een lege plaats die groot genoeg is om ze in haar nieuwe vorm te bevatten. Dat kopiëren is echter een vrij tijdrovende bezigheid zodat een andere organisatie zich opdringt.
Operating systems
180
Files liefst bijgehouden worden door ze in blokken te snijden en die blokken afzonderlijk te stockeren. De afmeting van die blokken best rond 1K ligt.
Wat we nu moeten bekijken is de manier waarop het file systeem kan bijhouden waar elk blok van een bepaald bestand staat. In de ogen van het file systeem bestaat een disk uit uit een genummerde reeks blokken die kunnen gelezen en beschreven worden. Een typische vraag van het file systeem aan de I/O-software is dus van de stijl: 'Geef de inhoud van blok x.' De realisatie van die opdracht gebeurt op een lager abstractieniveau, namelijk door de I/O-software. Het file systeem weet niet waar op disk (cilinder, sector) blok x te vinden is: dat weet dan weer de I/O-software. We zullen nu drie manieren bekijken waarop het file systeem die reeks blokken kan benaderen. In de gebruikte voorbeelden zullen we steeds blokken van 1K gebruiken.
9.3.3.
Gelinkte lijsten op disk
Een bestand bestaat op disk uit een gelinkte lijst van blokken (afb. 9-6). De 1024 bytes van elk blok worden verdeeld in: o 1022 data bytes o 2 bytes voor de pointer naar het volgende blok.
Operating systems
181
Figuur 9-7 Figuur 9-6
De directory-inhoud van elke file bevat het nummer van het eerste blok. Dit is de enige informatie die het file systeem nodig heeft om het bestand te lezen. Deze manier van werken is vrij eenvoudig te implementeren maar heeft enkele grote nadelen: o Het aantal data bytes is geen macht van twee en in een computer is dat altijd een lastig iets. o Om een bepaald blok te lezen moeten alle voorgaande gelezen worden en dat vertraagt de werking hopeloos.
9.3.4.
Gelinkte lijsten in het interne geheugen
De twee bezwaren die kleven aan het vorige, kunnen vermeden worden door de relevante informatie, namelijk de pointers, niet samen met de gegevens op disk te zetten maar in het interne geheugen. Deze organisatie vinden we terug in MSDOS en is reeds een felle verbetering ten opzichte van het vorige. De tabel met de pointers heet File Allocation Table (FAT). Die tabel is uiteraard op disk te vinden maar wordt in eerste plaats in het intern geheugen gemanipuleerd. De situatie uit de vorige paragraaf is, nu met behulp van een FAT, te zien in afbeelding 9-7. Zo'n FAT werkt vlot maar is niet zaligmakend: bij steeds groter wordende disks, wordt dit systeem steeds minder aantrekkelijk door de afmetingen die de FAT uiteindelijk gaat aannemen. Nemen we een disk van 64Mb, dan hebben we 64K blokken, dus een FAT die 64K elementen bevat. Die elementen (pointers) moeten daarom 2 byte lang zijn, wat resulteert in een FAT van 128K!
Operating systems
182
9.3.5.
Werken met i-nodes
Wanneer we de essentie halen uit de problemen die voortkomen uit een FAT-gebruik, dan blijkt die te liggen in het feit dat de pointers die horen bij één bestand, verspreid liggen over de ganse FAT. Het gevolg daarvan is dat zelfs indien slechts één bestand benaderd moet worden, toch de ganse FAT bereikbaar moet zijn. IN UNIX is dat probleem op een uiterst krachtige manier opgelost door heel eenvoudig de pointers, horend bij één bestand, te groeperen onder één geheel dat de naam i-node gekregen heeft. Omdat het hier gaat om een schoolvoorbeeld van ontwerpstrategie - elegant, eenvoudig en krachtig - bekijken we dit systeem even in detail (afbeelding 9-8). Geassocieerd met elk bestand, bestaat op disk een klein tabelletje (i-node) met alle relevante gegevens over de file in kwestie: naam, eigenaar, afmetingen, ... De laatste 13 elementen van de i-node zijn hier van het meeste belang. De eerste 10 daarvan kunnen verwijzingen naar data blokken bevatten. Voor file A uit vorige paragrafen zou daar dus inzitten: 6, 8, 4 en 2. Dat betekent dat voor files die tot 10 blokken lang zijn, slechts één supplementaire disk access nodig is om te weten waar alle blokken zich bevinden: het lezen van de i-node. Wanneer de file groeit boven de 10 blokken (hier dus 10K), wordt een extra blok geassocieerd. De plaats van dat blok staat in het elfde element (single indirect) en het dient enkel en alleen om pointers naar data blokken te bevatten. Bij een blok van 1K en pointers van 32 bit, zijn dat er dus 256. Deze toestand volstaat dus voor files tot 256 + 10 = 266 blokken lang, en dit slechts met nog één extra disk access.
Operating systems
183
10.
Deadlocks
10.1. Figuur 9-8
Probleemstelling en terminologie
In afbeelding 10-1 is een verkeerssituatie weergegeven die alle eigenschappen bezit van wat we zullen definiëren als een deadlock-situatie: het is de voorstelling van
een kruispunt met tramsporen waarop twee lange tramstellen in een vrij uitzichtloze situatie verzeild geraakt zijn (een tramstel kan niet of heel moeilijk achteruit rijden).
Afb 10 - 1 Deadlock situatie in het
Met het oog op wat gaat volgen kunnen we deze situatie als volgt omschrijven: De twee processen (het rijden van de trams) zijn geblokkeerd. Ze staan beide te wachten op een gebeurtenis (het vrijmaken van een spoor) die enkel kan gegenereerd worden door één van beide. Gevolg: zonder hulp van buitenaf zullen ze daar héél lang staan wachten.
Waarschijnlijk wordt het systeem duidelijk: boven 266 blokken wordt de dubbele indirecte pointer gebruikt om te verwijzen naar een disk blok die plaats biedt aan maximaal 256 pointers. Die laatste pointers wijzen nu echter niet naar data blokken maar naar enkelvoudige indirecte blokken van de vorige soort zodat we nu zitten aan een totaal van 10 + 256 + (256 * 256) = 65802K. Op analoge manier wordt nog een hoger niveau van indirectie gebruikt voor bestanden van meer dan 64M zodat we met nog slechts één disk access meer een willekeurige plaats kunnen bereiken in een bestand van maximum 16 gigabytes lang! Om files van vergelijkbare afmetingen met behulp van een FAT te manipuleren, zou die FAT halucinante afmetingen gaan aannemen. In MINIX is hetzelfde principe geïmplementeerd maar dan in een zwakkere vorm: er zijn slechts 7 data blok pointers in de i-nodes en het derde niveau van indirectie ontbreekt. Dit alles is echter nog steeds goed voor files van 262M !
Operating systems
184
Binnen een actief computersysteem kunnen op veel plaatsen gelijkaardige situaties ontstaan. Dit hoofdstuk behandelt de beschrijving van zo'n situaties, hun ontstaan en vooral enkele strategieën om ze op een adequate manier te vermijden en/of op te lossen. Om deze problematiek binnen een computersysteem te laten aanvoelen, bekijken we enkele eenvoudige voorbeelden.
10.1.1.
Voorbeeld 1
Een systeem beschikt over een printer en een plotter, beide ongespoold. Een proces A wil iets afdrukken. Dat verloopt volgens het volgende scenario: o o o
A vraagt aan het O.S. tijdelijk het exclusieve gebruik van de printer; de vraag wordt ingewilligd; A gebruikt de printer.
Hetzelfde gebeurt voor een proces B en de plotter. Beide randapparaten zijn dus 'bezet'. Operating systems
185
A wil nu ook de plotter gebruiken, maar is nog niet klaar met de printer. Het O.S. zal aan A melden dat de plotter niet vrij is en A zal wachten. Wanneer nu toevallig B de printer nodig heeft zal dit leiden tot een situatie met dezelfde kenmerken als het voorbeeld uit afbeelding 9.1.
10.1.2.
Voorbeeld 2
1. 2. 3.
Een cirkel met een naam erin stelt een proces voor. Een vierkant met een naam erin stelt een resource voor. Beide symbolen kunnen verbonden worden door een pijl.
De twee mogelijke combinaties zijn voorgesteld in afbeelding 10-2.
Wanneer een proces een record uit een gegevensbestand manipuleert dan wordt dat record tijdelijk ontoegankelijk gemaakt voor andere processen. Dat is noodzakelijk: denk wat er bijvoorbeeld zou gebeuren als een proces A een record aan het veranderen is en een proces B tegelijkertijd dit record wil uitlezen. Dit 'afsluiten' wordt gewoonlijk record locking genoemd. Bekijken we nu de situatie: o o o o
A B A B
P
Afb 10 - 2 Deadlock formalisme
benadert record X (X locked) benadert record Y (Y locked) wil Y benaderen: A moet wachten wil X benaderen: B moet wachten
A
Gevolg: Deadlock. Deze twee voorbeelden illustreren o.a. het feit dat de 'dingen' (printer, plotter, records,...) die oorzaak kunnen zijn van een deadlock van heel uiteenlopende aard
kunnen zijn maar wel gemeen hebben dat ze tijdelijk exclusief toegekend worden aan een proces. Zulke 'dingen' krijgen de naam resources. Wanneer we in de toekomst dus praten over resources dan gaat het steeds om 'dingen' die deze eigenschap hebben, ongeacht hun fysische geaardheid (randapparatuur, records, variabelen,...).
Q
(a) (b)
B
resource P is toegekend aan proces A. proces B wacht op resource Q.
De situatie uit het tweede voorbeeld (record locking) kan nu heel overzichtelijk weergegeven worden (Afbeelding 10-3). We zien nu heel duidelijk dat een deadlock gekenmerkt wordt door het feit dat er op de tekenig een gesloten traject kan gevolgd worden.
De benaderingswijze van een resource door een proces verloopt steeds volgens hetzelfde stramien: o o o o
Het proces dient een aanvraag in bij het O.S. De resource wordt toegewezen, indien mogelijk Het proces gebruikt de resource Het proces geeft de resource terug vrij.
10.2.
Afb 10 - 3 Een deadlock situatie
Het beschrijven van deadlocks
Uit de tekst van de voorbeelden blijkt dat het beschrijven van situaties in verband met toekennen en vrijgeven van resources vrij omslachtig is. Uit zo'n beschrijving dan nog eens afleiden of er een deadlock ontstaat of niet is ondoenbaar. Bedenk immers dat de twee voorbeelden vrij triviaal zijn en dat in de praktijk veel meer processen en resources erbij betrokken zullen zijn! We zullen dan ook gebruik maken van een eenvoudig, doch zeer krachtig grafisch formalisme, speciaal ontworpen om deadlock-situaties te beschrijven (Holt 1972).
Ter illustratie zijn in afbeelding 10-4 op de volgende bladzijde enkele scenario's weergegeven met behulp van dit formalisme.
Het formalisme bevat drie symbolen: Operating systems
186
Operating systems
187
10.3.
Nodige voorwaarden voor deadlocks
10.4.
Onderzoek heeft uitgewezen dat aan vier voorwaarden voldaan moet zijn opdat in een systeem deadlocks kunnen optreden. Na het voorgaande zijn die voorwaarden
waarschijnlijk vrij eenvoudig te interpreteren.
Behandelen van deadlocks
Men onderscheidt vier soorten strategieën om de deadlock-problematiek te benaderen: Het probleem gewoonweg negeren. De mogelijkheid inbouwen in het O.S. om deadlocks te detecteren en indien er een optreedt, de situatie alsnog te redden. o Het O.S. zo te construeren dat aan minstens één van de eerdergenoemde voorwaarden nooit voldaan is. o Door een constante evaluatie van de toestand van het systeem, ervoor zorgen dat deadlocks ontweken worden. In de rest van dit hoofdstuk zullen die verschillende strategieën heel summier besproken worden. o o
Afb 10 - 4
10.4.1.
Het Struisvogelalgoritme
De eerste en ontegensprekelijk eenvoudigste strategie bestaat erin het probleem gewoonweg te negeren. Deze op het eerste gezicht eigenaardige redenering kan echter in een hele boel gevallen de meest aangewezen zijn. Bekijken we daarvoor een systeem met de volgende eigenschappen: o o
o
Een crash van het systeem ten gevolge van een deadlock gebeurt gemiddeld om de zes maanden. Ten gevolge van een heleboel andere oorzaken ervaart men het genoegen van een crash gemiddeld om de maand (hardware, bugs in O.S., ...). Het aanpassen van het O.S. met betrekking tot deadlock-situaties is een vrij dure aangelegenheid.
Voor dit systeem is het vrij duidelijk dat men 'moet leren leven' met de mogelijkheid van een deadlock en dat dit ook niet zo dramatisch is.
1. De resources worden benaderd op basis van mutual exclusion. Elke resource is dus vrij ofwel exclusief toegekend aan een proces. 2. Een proces mag resources bijvragen. 3. Op de resources wordt geen preemption toegepast. Er is dus geen instantie buiten de 'bezitter' van het proces die beslist wanneer de resource wordt vrijgemaakt. 4. Er moet sprake zijn van minimum 2 processen en 2 resources zodat de mogelijkheid bestaat in het gebruikte grafisch formalisme een gesloten traject te laten ontstaan. Bij het oplossen van deadlock-problemen door het O.S. zullen deze vier voorwaarden verder aan bod komen.
Operating systems
188
10.4.2.
Detecteren en corrigeren
Wanneer het O.S. intern een voorstelling van de processen en hun resources bijhoudt, net zoals wij dat in onze voorbeelden hebben gedaan, dan kan er ook eenvoudig nagegaan worden wanneer een deadlock is opgetreden (gesloten traject). Wanneer het O.S. in zo'n situatie een willekeurig proces vernietigt, is de kans vrij groot dat de keten onderbroken werd, met andere woorden dat de deadlock vernietigd werd. Het O.S. moet er daarna echter wel zelf voor zorgen dat het vernietigde proces terug gecreëerd wordt. Deze manier van werken is eerder aangewezen in een batch-verwerkende omgeving waar het niet zo héél erg is dat een proces vernietigd wordt en daarna terug opgestart.
Operating systems
189
10.4.3.
Deadlock-preventie
In een vorige paragraaf werden vier nodige voorwaarden opgesomd om deadlocks te kunnen tegenkomen. Indien we nu in een O.S. inbouwen dat aan minstens één van die voorwaarden nooit voldaan is, dan zijn we verzekerd van het feit dat we nooit deadlocks gaan tegenkomen. We zullen nu bekijken welke van die vier voorwaarden daarvoor eventueel in aanmerking kan komen.
10.4.4.
10.4.6.
Het laten vallen van deze eis is duidelijk weinig belovend: op dat ogenblik worden we terug geconfronteerd met de ganse problematiek die als inleiding tot de interprocescommunicatie uit de doeken werd gedaan. Processen kunnen dan onderbroken worden op ogenblikken waarop ze nog niet klaar zijn met wat ze aan het doen waren met een resource.
Mutual exclusion
10.4.7.
Door de eis te laten vallen dat resources exclusief toegekend worden aan
processen, zijn we verlost van alle deadlock-problemen. Er is echter weinig fantasie voor nodig om enkele consequenties van deze optie aan te voelen: o o o
Vermijden van gesloten trajecten
Er bestaan verschillende benaderingswijzen die ervoor zorgen dat er nooit een gesloten traject ontstaat. We zullen er hier één bekijken om een beeld te krijgen van de mogelijkheden.
Twee processen printen samen op één printer. Hetzelfde kan zich voordoen bij elke soort I/O! Wanneer een record in een gegevensbank aangepast moet worden, bestaat geen zekerheid dat dit wel degelijk volledig kan gebeuren.
Een principiële oplossing zou eruit kunnen bestaan alle resources te benaderen via een spooler. Moeilijkheid daarbij is echter dat niet alle resources zich daartoe lenen. Deze voorwaarde blijkt dus niet geschikt te zijn.
10.4.5.
Geen preëmption
Alle resources worden genummerd. Wanneer een proces resources bijvraagt, mag dat enkel gebeuren in de volgorde van die nummering. Dat houdt in dat nooit een resource met een lager nummer mag bijgevraagd worden. Een eenvoudige situatie is voorgesteld in afbeelding 9-5.
Het bijvragen van resources
De negatie van deze voorwaarde kan gerealiseerd worden door te eisen dat elk proces, bij het opstarten, aan het O.S. meedeelt welke resources het nodig heeft. Indien die vrij zijn, wordt het proces opgestart, in het bezit van zijn aangevraagde resources. In het andere geval start het slechts op wanneer al zijn benodigde resources beschikbaar zijn. In zo'n situatie is het dus niet nodig dat er tijdens het verloop van een proces resources bijgevraagd worden en zijn we dus verlost van deadlocks.
Afb 10 - 5 Vermijden van gesloten
Aan deze werkwijze zijn echter weer enkele nadelen verbonden: Veel processen weten op voorhand niet hoeveel en welke resources ze nodig zullen hebben. Denk daarbij bijvoorbeeld aan sterk interactieve processen. De resources worden heel inefficiënt benut. Onderstel een proces waarvan de activiteiten bestaan uit: 1. Gegevens van een disk lezen. 2. Deze gegevens worden onderzocht en verwerkt gedurende een uur. 3. De resultaten worden terug op disk geschreven. 4. De verwerkte gegevens worden naar een plotter gestuurd. Dit proces houdt de disk en de plotter bezet gedurende een vol uur en dit terwijl die apparaten slechts een fractie van die tijd benut worden. Een mogelijke variant die deze gebreken in mindere mate bezit, is de werkwijze waarbij alvorens een resource bij te vragen, eerst alle andere in bezit even worden vrijgegeven.
Operating systems
190
Het volgende scenario kan zich voordoen: o
Proces A vraagt en krijgt drive b: (2).
o
Proces B vraagt en krijgt de tape (4).
Operating systems
191
o
Proces A vraagt de tape (4) maar moet wachten. De vraag was geoorloofd vermits de tape een hoger nummer heeft dan drive b:.
We gaan nu telkens de toestanden evalueren in termen van veilig of gevaarlijk. Een toestand noemen we veilig indien er een sequentie van toestanden bestaat die
o
Een deadlock zou kunnen ontstaan door de vraag van proces B naar drive b:. Die vraag gaat niet gesteld worden vermits drive b: een lager nummer heeft dan de tape. De kring kan dus nooit gesloten worden.
aan alle klanten toelaat hun maximum krediet op te halen. Toestand (b) is veilig vermits er een scenario bestaat waarin iedereen de kans heeft gehad zijn maximum krediet op te halen: o
10.4.8.
Dynamische systemen: deadlocks vermijden
o o o
De vorige strategieën hebben gemeen dat er op voorhand in het O.S. een aantal
restricties voor processen zijn ingebouwd. Het is echter ook mogelijk een aantal algoritmen te implementeren die ervoor zorgen dat deadlocks ontweken worden indien er gevaar voor bestaat. We zullen er hier een tweetal van bekijken. 10.4.8.1. Het Bankiersalgoritme (Dijkstra 1965) Het principe valt het best te begrijpen met behulp van een analogie. We bekijken een kleine bank die aan een aantal klanten krediet verleent. Elke klant heeft een verschillend bedrag dat maximaal kan opgenomen worden. We zullen enkele mogelijke situaties bekijken. Hieronder zien we in toestand (a) vier klanten met hun grootst mogelijk krediet en de bedragen die ze reeds ontleend hebben (niets, in deze situatieschets). De bankier zou theoretisch geconfronteerd kunnen worden met het feit dat al zijn klanten tegelijkertijd hun maximum toegelaten bedrag komen opnemen. Vermits die kans vrij klein is, neemt de bankier per dag slechts een redelijk geacht gedeelte van dat totaalbedrag (hier 22 Megaballen) in voorraad: 10 Megaballen. Naam Chris Frank Wim Diane
geleend 0 0 0 0
maximum 6 5 4 7
Een tijdje later ziet de toestand er zo uit:
Toestand (c): Naam geleend Chris 1 Frank 2 Wim 2 Diane 4
maximum 6 5 4 7
Nog in voorraad: 1 Toestand (c) is gevaarlijk: indien nu plots alle klanten hun maximum bedrag zouden
vragen, zou niemand bediend kunnen worden, maar zou er ook geen geld terug binnenkomen om de kas te spijzen! Gevolg: een deadlock. Merk op dat een gevaarlijke toestand niet noodzakelijk leidt naar een deadlock maar wel dat de kans bestaat. Bij een veilige toestand is dat niet het geval. De taktiek (algoritme) van de bankier moet er dus in bestaan om enkel veilige toestanden te creëren. Dat kan hij doen door telkens, alvorens een lening toe te staan, zorgvuldig te evalueren in welke toestand die hem zou brengen. Indien dat een gevaarlijke zou zijn, wordt de lening uitgesteld tot een later tijdstip.
o o o
maximum 6 5 4 7
bankier: O.S. klanten: processen geleende bedragen: resources.
10.4.8.2. Beschrijven van resource-trajecten Een ander hulpmiddel dat bruikbaar is in niet te ingewikkelde situaties, bestaat erin de evolutie van processen weer te geven in een grafiek. In afbeelding 9-6 zijn twee processen, X en Y, voorgesteld. Ze maken beide gebruik van een printer en een plotter. De tekening moeten we als volgt interpreteren:
Nog in voorraad: 2
Operating systems
Bekijken we nu wat de toestand zou worden indien na (b) Frank 1 Mb zou vragen en krijgen:
Deze redenering is in een aantal gevallen te transporteren naar een computersysteem wanneer we de 'spelers' door de gepaste instanties vervangen:
Nog in voorraad: 10
Toestand (b): Naam geleend Chris 1 Frank 1 Wim 2 Diane 4
Wim vraagt en krijgt zijn resterende 2 Mb. De anderen moeten wachten. De voorraad is nu leeg. Wim betaalt 4 Mb terug: er is terug 4 Mb in kas. Indien nu Frank of Diane hun maximum willen ophalen, is dit mogelijk. Nadat zij hebben terugbetaald, kan Chris haar grootste bedrag krijgen.
192
Operating systems
193
o o o
o
Op de Y-as worden het aantal uitgevoerde instructies van proces Y uitgezet. Analoog voor de X-as. Elk punt van de grafiek komt dus overeen met een gecombineerde toestand van X en Y. Het punt u duidt de voltooiing aan van zowel X als Y.
Afb 10 - 6 Een resource traject
11.
Protectie en beveiliging
Een protectiemechanisme heeft tot doel ervoor te zorgen dat alle mogelijke resources die tot het computersysteem behoren slechts benaderd kunnen worden door de processen (en gebruikers) die daarvoor de gepaste toestemming bezitten. Dat situeert zich op het gebied van bestanden, maar evengoed op het gebied van intern geheugen en randapparaten. Wanneer gepraat wordt over beveiliging, dan heeft dat meer betrekking op het computersysteem in relatie tot de omgeving: hoe kunnen de resources beveiligd worden tegen invloeden van buitenaf? Beide begrippen zijn nauw verwant en worden dan soms ook overlappend gehanteerd.
11.1.
Protectie
In een multitasking systeem moeten de verschillende processen ten opzichte van mekaar beschermd worden. Enkele voorbeelden ter illustratie: Merk op dat de mogelijke trajecten steeds evenwijdig aan één van beide assen lopen. Het stuk traject r-s bijvoorbeeld geeft aan dat Y actief was en X niet. De evolutie
gebeurt ook steeds naar rechts of naar boven vermits processen niet 'achteruit' kunnen lopen.
•
Bekijken we nu de situatie in verband met de resources: o o
•
•
Y heeft de printer nodig tussen ogenblik E en G; de plotter tussen F en H. X heeft de printer nodig tussen ogenblik A en C; de plotter tussen B en D.
De rechthoek, gevormd door de lijnen gaande door A, C, E en G, duidt dus het gebied aan waarin beide processen de printer nodig hebben. Het traject kan hier nooit in terechtkomen wegens de mutual exclusion op de printer. Hetzelfde geldt voor het 'plotter-gebied'. Dat heeft voor gevolg dat zodra het traject binnentreedt in de rechthoek, gevormd door de lijnen door C, D, E en F, we onherroepelijk afstevenen op een deadlock: die slaat toe in de rechterbovenhoek van dit rechthoekje! Het enig dat te doen valt in het punt t, is het proces Y laten evolueren tot op de lijn
door H. Vanaf daar is elk traject naar u veilig. Dit principe kan in een aantal niet te ingewikkelde situaties geïmplementeerd worden in een O.S.
In het hoofdstuk over het geheugenbeheer zagen we reeds waarom en hoe stukken intern geheugen die processen bevatten afgeschermd worden ten opzichte van elkaar. Binnen een bestandssysteem is het noodzakelijk dat er een mechanisme aanwezig is dat toelaat groepen bestanden af te schermen. Attributen kunnen daarvoor gebruikt worden. Alle aanwezige randapparaten hoeven niet beschikbaar te zijn voor elk proces: een typist(e) hoeft bijvoorbeeld geen toegang te hebben tot de communicatiekanalen van het bedrijf en in een kerncentrale zou het niet direct aangewezen zijn elke werknemer de mogelijkheid te bieden het controlemechanisme van de reactor te benaderen.
In dit gedeelte zullen we een model bekijken om zo'n algemeen protectiemechanisme te ontwerpen.
11.1.1.
De doelen van protectie op een rijtje
Oorspronkelijk ontstond protectie als een onderdeel van een multitasking systeem omdat daar nu eenmaal de aanwezig noodzaak was sommige delen van het systeem af te schermen ten opzichte van elkaar. In de eerste plaats betrof het dan stukken intern geheugen en groepen bestanden. Naarmate de systemen echter complexer en uitgebreider werden, bleek zo'n mechanisme echter onontbeerlijk voor de betrouwbare werking van gans het systeem zodat een meer algemene en systematische aanpak noodzakelijk werd. Protectie moet een mechanisme ter beschikking stellen dat een uitgestippeld
Operating systems
194
beleid (policy) dwingend oplegt met betrekking tot het gebruik van de aanwezige 195 resources.
Operating systems
Het is van belang een duidelijk onderscheid te maken tussen de termen 'protectiemechanisme' en 'protectiebeleid': het mechanisme legt vast hoe de
protectie verzekerd wordt, terwijl het beleid bepaalt wat er te gebeuren valt. Dat onderscheid is noodzakelijk met het oog op het ontwerpen van een flexibel beschermingssysteem.
elk op hun beurt bestaan uit een geordend koppel . Wanneer bijvoorbeeld process A mag lezen en schrijven op file X en mag lezen uit file Y, dan bestaat het domein van A uit <X, {read, write}> en . Domeinen kunnen disjunct zijn maar even goed kunnen ze mekaar gedeeltelijk overlappen. Een concrete situatie is geschetst in afbeelding 10-1.
Bekijken we eerst even het aspect van het beleid (policy). Dat komt tot stand op een variëteit van manieren. • • •
Sommige elementen zijn inherent aan het computersysteem zelf: denk aan de protectie die we reeds zagen bij het geheugenbeheer. Andere worden bepaald door de beheerder(s) van het systeem: wie mag welke resources benaderen en op welke wijze. Nog andere moeten door individuele gebruikers kunnen ingesteld worden: bescherming van eigen bestanden met het oog op beschadiging door derden of uit oogpunt van privacy.
Het is duidelijk dat al deze factoren onmogelijk in het ontwerp van een operating system kunnen vastgelegd worden omdat ze op elk concreet systeem anders liggen en dan ook nog eens wijzigingen ondergaan in functie van de tijd. Daarom is het absoluut noodzakelijk dat hetgeen aangeboden wordt door het operating system, een flexibel mechanisme is dat met behulp van parameters kan worden ingesteld. We bekijken nu de basisprincipes van zo'n mechanisme.
11.1.2.
Het 'domein' van protectie
Afb 11 - 1 Drie protectiedomeinen
Een actief computersysteem kunnen we bekijken als een verzameling van processen
en objecten, waarbij 'objecten' zowel kan slaan op hardware (CPU, stukken geheugen, plotters, ...) als op software (bestanden, programma's). Elk object is uniek identificeerbaar via een naam en wordt benaderd via een aantal bewerkingen, eigen aan het object.
Een proces, werkzaam in D1 mag file X.DOC lezen, file Y.EXE uitvoeren en mag printen met de matrixprinter. Een proces in D2 mag Z.EXE uitvoeren en de laserprinter gebruiken. De processen in D3 mogen M.EXE uitvoeren, op N.DTA lezen en schrijven en mogen eveneens de laserprinter gebruiken. De koppeling tussen processen kan statisch of dynamisch zijn: in het eerste geval verandert er tijdens de uitvoering van een proces niets wat betreft de toegang tot de verschillende objecten. In het tweede geval kan het domein variëren, naargelang de fase van utvoering waarin het proces zich bevindt. Deze werkwijze is uiteraard te verkiezen maar zal ook meer overhead vergen. We komen daar later nog op terug.
Voorbeelden: • • • •
stukken intern geheugen: lezen en schrijven; tape drives: lezen, schrijven, verder- en terugspoelen; data files: creëren, lezen, schrijven, openen, ...; programmabestanden: lezen, schrijven, uitvoeren, ...;
Een proces moet slechts toegang hebben tot die resources, en dan nog enkel via die bewerkingen die het op elk ogenblik nodig heeft om zijn taak naar behoren uit te voeren. Niet minder maar ook niet meer! Om deze idee te concretiseren met het oog op een implementatie in een mechanisme, wordt het concept van een
protectiedomein geïntroduceerd. Elk proces opereert binnen een eigen domein dat de resources bevat die het proces nodig heeft, samen met de bijhorende toegelaten operaties op die resources. Zo'n domein bestaat dus uit een verzameling (set) van objecten en de toegelaten operaties op die opbjecten. De mogelijkheid om een bewerking op een object uit te voeren noemt men een access right of toegangsrecht. Een domein is dus een verzameling access rights die Operating systems
196
11.1.3.
Toegangsmatrix
Het model dat we tot hiertoe bekeken kunnen we ook bekijken als een matrix waarbij
de rijen de domeinen voorstellen en de kolommen de objecten. Elk element bestaat dan uit een verzameling access rights. De toegangsmatrix voor de hiervoor beschreven situatie ziet er dan uit zoals in afbeelding 10-2. Door alle gegevens in verband met de protectie in de vorm van een matrix te brengen, hebben we een basis om aan de implementatie er van te beginnen denken. Een matrix Operating systems
197
is immers een datastructuur die vrij rechtstreeks en eenvoudig binnen een stuk software te implementeren valt. Een opmerking vooraf: meestal zal de toegangsmatrix redelijk 'leeg' zijn, waarmee wordt aangegeven dat slechts weinig elementen daadwerkelijk access rights bevatten. Deze bemerking heeft geleid tot verschillende benaderingen van implementaties.
toegangsmatrix. Bij de benadering van een object moet dan slechts de bijhorende lijst afgezocht worden op de aanwezigheid van het betreffende domein. 11.1.4.3. Capability lists Omgekeerd kunnen we met elk domein een lijst bijhouden van de bijhorende objecten met hun access rights. Bij de benadering van een object moet dan slechts de lijst, horend bij het domein afgezocht worden op de aanwezigheid van het betreffende object.
11.2.
Veiligheid
De verwantschap tussen protectie en beveiliging kan misschien het best geïllustreerd worden met behulp van een voorbeeldje. De wijze waarop bestanden via sleutelwoorden (passwords) kunnen afgeschermd worden, valt onder de protectie. Wanneer die sleutel echter zonder al te veel moeite door een niet-geauthoriseerde gebruiker kan gekend worden, is er sprake van een veiligheidsprobleem. In dit gedeelte zullen we proberen enige systematiek te brengen in het brede, vaag afgelijnde domein van beveiliging.
11.2.1.
Algemeen kan gesteld worden dat een systeem veilig is wanneer alle resources benaderd en gebruikt worden zoals bedoeld werd, en dit onder alle omstandigheden. Dit is utopisch. Waar naar gestreefd moet worden is een situatie waarbij verkeerd gebruik eerder uitzondering is dan regel. Ruw geschetst valt 'verkeerd gebruik' uiteen in twee categorieën:
Afb 11 - 2 Een toegangsmatrix
11.1.4.
Het veiligheidsprobleem
De implementatie van toegangsmatrices
We bekijken hier drie ontwerpstrategieën voor het implementeren van toegangsmatrices.
o o
11.1.4.1. Een globale tabel De eenvoudigste manier bestaat er in de ganse matrix in zijn geheel in een datastructuur te gieten. Wanneer een proces (dat dus tot een bepaald domein behoort) een bepaalde operatie wil utvoeren, wordt in de matrix gekeken of de bewerking in kwestie op het object in kwestie toegelaten is. Aan deze implementatiewijze zijn echter enkele nadelen verbonden. Normaal gesproken is de toegangsmatrix vrij groot -alhoewel hij slechts weinig elementen bevat- zodat hij meestal niet volledig in het interne geheugen kan zitten. De I/O die daarvan het gevolg is, zal de werking dus vertragen. Een ander bezwaar: wanneer een object bijvoorbeeld door iedereen gelezen mag worden, dan staat er in de kolom van dat object zoveel keer 'read' als dat er domeinen zijn. Dit is geen erg efficiënte wijze van representeren. 11.1.4.2. Toegangslijsten voor objecten
Toevallig en onbedoeld verkeerdelijk gebruik. Opzettelijk misbruik.
Accidenteel misbruik kan in grote lijnen meestal vermeden worden via een adequaat protectiemechanisme. Het is bij opzettelijk misbruik dat typische veiligheidsaspecten naar voor treden. Onder opzettelijk misbruik van het systeem vallen onder andere het zonder toestemming lezen, veranderen of vernietigen van data. Om een systeem daartegen te beveiligen moeten maatregelen getroffen worden op twee verschillende niveau's:
o Fysisch: de lokatie van het systeem moet afdoende beveiligd zijn tegen brand, indringers e.d.. o Menselijk: De authorisatie van gebruikers moet zeer zorgvuldig en doordacht gebeuren. Wij zullen hier enkel die elementen bekijken die rechtstreeks gekoppeld zijn aan het operating system. Hoe bedrijven er voor moeten zorgen dat tapes niet om de haverklap gestolen worden of hoe gebruikers gescreend moeten worden op hun betrouwbaarheid valt buiten het domein van deze cursus.
Een alternatieve benadering is om per object een lijst aan te leggen van de domeinen, samen met hun acces rights om alzo verlost te zijn van de lege plaatsen in de Operating systems
198
Operating systems
199
11.3.
Authenticiteit
Een belangrijk beveiligingsaspect is de mate waarin het computersysteem de individuele gebruikers kan identificeren. Anders gezegd: hoe 'weet' het systeem dat de gebruiker wel degelijk is wie hij of zij beweert te zijn? De authenticiteit van gebruikers wordt op dit ogenblik benaderd via een combinatie van drie wegen: o
Een voorwerp dat de gebruiker in bezit heeft: een pasje, een sleutel, ...
o Iets dat de gebruiker weet: een sleutelwoord, een cijfercombinatie, ... o
Een attribuut dat eigen is aan de individuele gebruiker: vingerafdruk, retinapatroon, handtekening, ...
Toegang verlenen via een sleutelwoord wordt zeer veel gebruikt omdat het redelijk eenvoudig te implementeren is en gemakkelijk te begrijpen. Dit aspect zullen we dan ook hier even van naderbij bekijken. Naar de gebruiker toe is het principe simpel: alvorens een bepaalde handeling te kunnen verrichten wordt gevraagd een sleutelwoord in te tikken. Dat wordt verwerkt door het aanwezige protectiemechanisme en indien de gebruiker de toestemming blijkt te bezitten te handeling uit te voeren, kan ze gebeuren. De problemen die bij zo'n mechanisme optreden hebben in grote mate te maken met de moeilijkheid de sleutelwoorden geheim te houden. • • •
Sleutelwoorden kunnen -al dan niet systematisch- geraden worden. Ze kunnen op toevallige wijze getoond worden aan iemand anders. Ze kunnen in het bezit komen van anderen door in het protectiemechanisme zelf binnen te dringen.
Er bestaan twee gebruikelijke manieren om een sleutelwoord te raden. De eerste maakt gebruik van informatie aangaande de rechtmatige eigenaar: heel dikwijls wordt blijkbaar een voor de hand liggende woord gekozen (de naam van de partner of huisdier, automerk, geboortedatum, ...). De tweede hanteert brute kracht. Een sleutel, bestaande uit 4 cijfers bijvoorbeeld, laat slechts 10000 combinaties toe. Het is kinderspel een programmaatje te schrijven dat al die combinaties genereert en met elk er van poogt toegang te verkrijgen. Langere sleutels zijn dus veiliger, maar daar tegenover staat dan weer dat ze moeilijker te onthouden zijn en dat het risico dat ze ergens op een papiertje worden opgeschreven groter wordt. Sommige systemen verplichten de gebruikers regelmatig hun sleutel te veranderen, zelfs na elke werksessie. Een belangrijk probleem is uiteraard het geheim houden van het bestand met alle sleutels. In UNIX is dat op een elegante manier opgelost. Elke gebruiker heeft een sleutelwoord. Het systeem bevat een functie die elke sleutel encrypteert. Die functie is van die aard dat ze eenvoudig te berekenen is maar (hopelijk) niet te inverteren. Anders gezegd, een sleutel wordt gecodeerd via een bepaald algoritme, maar uit het gecodeerde woord is het oorspronkelijke niet meer terug af te leiden. Hetgeen in het systeem bewaard wordt is dan ook een bestand met alle gecodeerde
sleutelwoorden, zodat er geen noodzaak bestaat dat bestand met alle mogelijke middelen geheim te houden. Wanneer een gebruiker inlogt, wordt de ingevoerde sleutel gecodeerd en opgezocht in het bestand met de gecodeerde sleutels. Deze methode is ook niet waterdicht. Men kan bijvoorbeeld alle woorden uit een woordenboek coderen en telkens vergelijken met de inhoud van het bestand met Operating systems
200
de sleutels. Wordt er een overeenkomst gevonden, heeft de indringer de oorspronkelijke sleutel. Op sommige systemen is het dan ook niet toegelaten woorden uit het woordenboek te kiezen.
11.3.1.
Programma- en systeembedreigingen
Wanneer programma's, gemaakt door een gebruiker (of groep) uitgevoerd mogen worden door andere gebruikers, dan kan dat een bedreiging vormen voor de veiligheid. Stel dat iemand een editor maakt en in dat programma zit code verstopt die bepaalde bestanden kopieert naar een speciaal gebied waar de maker van de editor toegang toe heeft. Wanneer die editor uitgevoerd wordt door een andere gebruiker, kunnen een aantal van diens bestanden onrechtmatig overgebracht worden naar de maker van het programma. Zo'n stuk code heet een Trojaans paard. Een worm is dan weer een programma dat zichzelf copieert doorheen gans het
systeem en alzo het normale gebruik van de resources in de war stuurt. Virussen daarentegen zijn programmaatjes die een ander programma als 'voertuig' hanteren om via de verspreiding van dit laatste ongemerkt het systeem binnen te dringen.
11.3.2.
Opsporing
Verschillende management technieken worden gehanteerd om de veiligheid van een bestaand systeem te verhogen. Het systeem kan zelf zoeken naar verdachte patronen van activiteit. Er kan bijvoorbeeld gevolgd worden hoeveel pogingen er plaatsgrijpen om in te loggen via een verkeerd sleutelwoord. Is dat aantal te hoog, dan is er waarschijnlijk iets link aan het gebeuren. Een andere techniek is een zogenaamde audit log. Voor elk object wordt
bijgehouden wie op welk ogenblik en op welke wijze de resource benaderd heeft. Een andere mogelijkheid bestaat erin op kalme momenten het ganse systeem te scannen op mogelijke veiligheidsrisico's: te korte of te vlug te raden sleutelwoorden, processen die abnormaal lang actief zijn, veranderingen in systeemprogrammatuur, ...
11.3.3.
Verborgen kanalen (Covert channels)
Zelfs in een optimaal beveiligd systeem is het steeds mogelijk informatie over te brengen van één proces naar een ander, daar waar dat absoluut niet zou mogen. We zullen enkele voorbeeldjes bekijken die illustreren dat zoiets zelfs niet zo héél ingewikkeld is. Bekijken we de volgende situatie waarin drie processen betrokken zijn: • • •
Een client die zijn belastingen wil laten berekenen. Daarvoor doet hij beroep op de diensten van een server: deze voert effectief de berekeningen uit. Een medeplichtige. Dit proces hoort toe aan de eigenaar van de server.
De situatie is geschetst in afbeelding 10-3. De client en de server vertrouwen mekaar niet: de client heeft schrik dat de server zijn gegevens zal doorspelen aan derden en de server verdenkt de client er van dat deze Operating systems
201
het kostbare berekeningsprogramma wil ontvreemden. Onderstel nu dat de server effectief met behulp van de medeplichtige de financiële gegevens van de client wil ontfutselen door ze te communiceren naar die medeplichtige. Vanuit het oogpunt van de veiligheid is het dus noodzakelijk de server te isoleren. Via het protectiemechanisme kan zonder probleem gezorgd worden dat de server niet naar een bestand kan schrijven dat gelezen kan worden door de medeplichtige. De normale interprocescommunicatie tussen beide laatste kan eveneens onderbroken worden.
Afb 11 - 3 Verborgen kanalen
Waar het voor de server op aankomt is een stroom bits op één of andere manier mee te delen aan de medeplichtige. Zelfs in een optimaal beveiligd systeem zijn de mogelijkheden legio: •
Wil de server een 1 doorsturen, dan begint hij gedurende een bepaalde tijd héél hard te rekenen. Wil hij een 0 communiceren, dan doet hij gedurende dezelfde tijd niets. De medeplichtige kan deze informatie detecteren door zorgvuldig zijn eigen responstijd te analyseren: die zal beter zijn bij een 0 dan bij een 1!
•
Het pagingmechanisme kan ook dienst doen: veel page faults voor een 1, weinig voor een 0.
•
Het aanvragen en terug vrijgeven van resources (plotter, tape driver, ...) is eveneens een kandidaat: de server vraagt de resource aan om een 1 te communiceren, vrijgeven binnen een bepaalde tijd betekent een 0.
•
De server creëert een vooraf afgesproken file bij een 1, en vernietigt ze bij een 0.
•
Het kan nog eenvoudiger. De server zal waarschijnlijk aan zijn (menselijke) eigenaar moeten melden hoeveel werk werd verricht in dienst van de client. Daarvoor zal de server een rekening moeten tonen. Onderstel dat die 1000 BEF is. Indien de (geheime) inkomsten van de client 56.000.000 BEF bedragen, hoeft de server slechts een rekening van 1000,56 BEF te tonen om de onrechtmatige datatransfer te realiseren.
Communicatiekanalen van dit type noemt men verborgen kanalen of covert
channels
Operating systems
202