Inleiding tot besturingssystemen
Hallo, Habr! Ik wil jullie een serie vertaalde artikelen voorstellen van een literatuur die ik interessant vind - OSTEP. In dit materiaal wordt diep ingegaan op de werking van Unix-achtige besturingssystemen, met name - hoe processen werken, verschillende planners, geheugen en andere soortgelijke componenten die een modern besturingssysteem vormen. Het origineel van al het materiaal kunt u hier bekijken . Houd er rekening mee dat de vertaling niet professioneel is uitgevoerd (vrij vrij), maar ik hoop dat ik de algemene betekenis heb behouden.
Laboratoriumopdrachten voor dit onderwerp kun je hier vinden:
- origineel:
- origineel:
- mijn persoonlijke aanpassing:
Andere delen:
En je kunt ook mijn kanaal bezoeken op =)
Laten we de meest fundamentele abstractie bekijken die het besturingssysteem aan gebruikers biedt: het proces. De definitie van een proces is vrij eenvoudig - het is een draaiend programma. Een programma op zich is een levenloos ding, opgeslagen op de schijf - het is een set instructies en mogelijk enkele statische gegevens die wachten op het moment van uitvoering. Het besturingssysteem pakt deze bytes en start ze, en transformeert het programma in iets nuttigs.
Meestal willen gebruikers meerdere programma's tegelijk draaien, bijvoorbeeld, je kunt op je laptop een browser, een spel, een mediaspeler, een teksteditor enzovoort openen. In feite kan een typische systeem tientallen en honderden processen tegelijk uitvoeren. Dit maakt het systeem gebruiksvriendelijker, je hoeft je nooit zorgen te maken of de CPU vrij is, je start gewoon de programma's.
Hieruit volgt het probleem: hoe creƫer je de illusie van meerdere CPU's? Hoe kan het besturingssysteem de illusie van praktisch een onbe aantal CPU's creƫren, zelfs als je maar ƩƩn fysieke CPU hebt?
Het besturingssysteem creƫert deze illusie door CPU-virtualisatie. Door ƩƩn proces te starten, het vervolgens te stoppen, een ander proces te starten, enzovoort, kan het besturingssysteem de illusie in stand houden dat er meerdere virtuele CPU's zijn, hoewel het in werkelijkheid ƩƩn of meerdere fysieke processors zijn. Deze techniek wordt genoemd tijdsdeling van CPU-bronnen. Deze techniek stelt gebruikers in staat om zoveel gelijktijdige processen te draaien als ze willen. De prijs van deze oplossing is de prestaties - omdat als de CPU door meerdere processen wordt gedeeld, elk proces langzamer zal worden verwerkt.
Voor het realiseren van CPU-virtualisatie, en vooral om dit goed te doen, heeft het besturingssysteem zowel laagdrempelige als hoogdrempelige ondersteuning nodig. Laagdrempelige ondersteuning wordt genoemd mechanismen ā dit zijn laagniveau methoden of protocollen die het benodigde deel van de functionaliteit implementeren. Een voorbeeld van dergelijke functionaliteit is contextwisseling, die het besturingssysteem in staat stelt om ƩƩn programma te stoppen en een ander programma op de processor te starten. Deze tijdsdeling is aanwezig in alle moderne besturingssystemen.
Bovenop deze mechanismen ligt enige logica, ingebouwd in het besturingssysteem, in de vorm van "beleid". Beleid is een bepaalde algoritme voor besluitvorming door het besturingssysteem. Dergelijke beleidsregels bepalen bijvoorbeeld welk programma (uit de lijst van opdrachten) als eerste moet worden uitgevoerd. Zo zou bijvoorbeeld een beleid genaamd scheduler (planningsbeleid) bij het maken van beslissingen rekening houden met gegevens zoals: opstartgeschiedenis (welk programma het langst is gedraaid in de afgelopen minuut), welke belasting dit proces genereert (welke soorten programma's zijn uitgevoerd), prestatie-indicatoren (is het systeem geoptimaliseerd voor interactieve interactie of voor doorvoersnelheid) en dergelijke.
Abstractie: proces
De abstractie van een draaiend programma, uitgevoerd door het besturingssysteem, is wat we noemen proces. Zoals eerder vermeld, is een proces simpelweg een draaiend programma, op elk willekeurig moment. Een programma waarmee we samengevoegde informatie uit verschillende systeembronnen kunnen verkrijgen, en tot welke bronnen deze programma toegang heeft of die door het programma worden beĆÆnvloed tijdens de uitvoering.
Om de onderdelen van een proces te begrijpen, is het nodig om de toestanden van het systeem te begrijpen: wat een programma kan lezen of wijzigen tijdens zijn werking. Op elk moment moet duidelijk zijn welke elementen van het systeem belangrijk zijn voor de uitvoering van het programma.
Een van de voor de hand liggende elementen van de systeemstatus die door een proces wordt opgenomen, is geheugen. Instructies worden in het geheugen geplaatst. Gegevens die het programma leest of schrijft, bevinden zich ook in het geheugen. Dus het geheugen dat het proces kan adresseren (het zogenaamde adresruimte) maakt deel uit van het proces.
Een belangrijk onderdeel van de status van het systeem zijn de registers. Veel instructies zijn gericht op het wijzigen van de waarden van registers of het lezen van hun waarden, waardoor registers ook een belangrijke rol spelen in de werking van het proces.
Bovendien bestaat de status van de machine ook uit enkele speciale registers. Bijvoorbeeld, IP ā instruction pointer ā de aanwijzer naar de instructie die het programma op dat moment uitvoert. Daarnaast zijn er stack pointer en de bijbehorende frame pointer, die worden gebruikt voor het beheren van: functieparameters, lokale variabelen en retouradressen.
Ten slotte hebben programma's vaak toegang tot ROM (read-only memory). Informatie over 'I/O' (invoer-uitvoer) moet een lijst bevatten van bestanden die momenteel door het proces zijn geopend.
Process API
Om de werking van een proces beter te begrijpen, onderzoeken we voorbeelden van systeemaanroepen die in elke interface van een besturingssysteem moeten worden opgenomen. Deze API's zijn in enige vorm beschikbaar op elk besturingssysteem.
ā Create (creĆ«ren): Het besturingssysteem moet enige methode bevatten die het mogelijk maakt om nieuwe processen aan te maken. Wanneer u een opdracht in de terminal invoert of een applicatie opent door op het pictogram te dubbelklikken, wordt er een verzoek naar het besturingssysteem gestuurd om een nieuw proces aan te maken en het opgegeven programma te starten.
ā Verwijderen: Aangezien er een interface is voor het aanmaken van een proces, moet het besturingssysteem ook de mogelijkheid bieden om een proces geforceerd te beĆ«indigen. De meeste programma's zullen van nature vanzelf opstarten en beĆ«indigen tijdens hun uitvoering. In elk geval wil de gebruiker de mogelijkheid hebben om ze te beĆ«indigen, dus een interface voor het stoppen van een proces is noodzakelijk.
ā Wait (wachten): Soms is het nuttig om te wachten totdat een proces is voltooid, daarom worden er bepaalde interfaces aangeboden die de mogelijkheid tot wachten bieden.
ā Misc Control (diverse controles): Naast het beĆ«indigen en wachten op een proces zijn er ook andere verschillende controlemethoden. De meeste besturingssystemen bieden bijvoorbeeld de mogelijkheid om een proces te bevriezen (het uitvoeren ervan tijdelijk te stoppen) en later te hervatten (het uitvoeren voortzetten).
ā Status (status): Er zijn verschillende interfaces om informatie over de status van een proces te verkrijgen, zoals de duur van zijn werking of in welke staat het zich momenteel bevindt.

Proces creatie: details
Een van de interessante zaken is hoe programma's precies worden omgevormd tot processen. Vooral hoe het besturingssysteem een programma laadt en uitvoert. Hoe wordt een proces precies aangemaakt.
Allereerst moet het besturingssysteem de code van het programma en de statische gegevens in het geheugen laden (in de adresruimte van het proces). Programma's bevinden zich meestal op een schijf of solid-state drive in een bepaald uitvoerbaar formaat. Daarom vereist het laadproces van het programma en de statische gegevens dat het besturingssysteem deze bytes van de schijf kan lezen en ze ergens in het geheugen kan plaatsen.
In vroege besturingssystemen werd het laadproces ongeduldig uitgevoerd, wat betekent dat de code volledig in het geheugen werd geladen voordat het programma werd uitgevoerd. Moderne besturingssystemen doen dit lui, dat wil zeggen, ze laden stukjes code of gegevens alleen wanneer deze door het programma tijdens de uitvoering nodig zijn.
Nadat de code en statische gegevens in het geheugen zijn geladen, moet het besturingssysteem nog enkele dingen doen voordat het proces kan worden gestart. Er moet een hoeveelheid geheugen worden toegewezen voor de stack. Programma's gebruiken de stack voor lokale variabelen, functietparameters en terugroeppunten.. Het besturingssysteem wijst dit geheugen toe en geeft het aan het proces. De stack kan ook worden toegewezen met enkele argumenten, specifiek het vult de parameters van de main() functie, zoals de arrays argc en argv.
Het besturingssysteem kan ook een hoeveelheid geheugen voor de heap van het programma toewijzen. De heap wordt door programma's gebruikt voor expliciet aangevraagde dynamisch toegewezen gegevens.. Programma's vragen deze ruimte aan door de functie aan te roepen. malloc() en maakt expliciet schoon door de functie aan te roepen. free(). Een heap is nodig voor datatypes zoals: gekoppelde lijsten, hash-tabellen, bomen en andere. Aanvankelijk wordt er een klein stukje geheugen toegewezen aan de heap, maar na verloop van tijd kan de heap, via een bibliotheek-API aanroep van malloc(), om meer geheugen vragen. Het besturingssysteem is betrokken bij het proces van het toewijzen van meer geheugen om deze verzoeken te helpen vervullen.
Het besturingssysteem zal ook initialisatietaken uitvoeren, met name die welke betrekking hebben op invoer en uitvoer. In UNIX-systemen heeft ieder proces standaard 3 geopende bestandsdescriptoren voor de standaardinvoer, -uitvoer en -fouten. Deze descriptoren stellen programma's in staat om invoer uit de terminal te lezen, evenals informatie op het scherm uit te voeren.
Zo bereidt het besturingssysteem, door de code en statische gegevens in het geheugen te laden, de stack te creƫren en te initialiseren, evenals andere taken met betrekking tot invoer- en uitvoeroperaties uit te voeren, de grondslag voor de uitvoering van het proces voor. Uiteindelijk blijft er nog ƩƩn taak over: het programma uitvoerbaar maken via het invoerpunt, dat de functie main() wordt genoemd. Bij het uitvoeren van de functie main() geeft het besturingssysteem de controle over aan de CPU van het nieuw aangemaakte proces, waardoor het programma begint te draaien.
De status van het proces
Nu we een goed begrip hebben van wat een proces is en hoe het wordt aangemaakt, laten we de processtatussen opsommen waarin het zich kan bevinden. In de eenvoudigste vorm kan een proces zich in een van deze staten bevinden:
ā Running. In een uitvoerbare staat wordt het proces op de processor uitgevoerd. Dit betekent dat instructies worden uitgevoerd.
ā Klaar. In een klaarstaat is het proces klaar om te beginnen met uitvoeren, maar om een of andere reden voert het besturingssysteem het niet op dat specifieke moment uit.
ā Geblokkeerd. In een geblokkeerde staat voert het proces een of andere operatie uit die het verhindert klaar te zijn om uitgevoerd te worden totdat er een bepaald evenement plaatsvindt. Een veelvoorkomend voorbeeld is wanneer een proces een IO-operatie initialiseert, waarbij het geblokkeerd raakt zodat een ander proces de processor kan gebruiken.

Stel je deze toestanden voor als een grafiek. Zoals we op de afbeelding kunnen zien, kan de status van een proces veranderen tussen RUNNING en READY naar goeddunken van het besturingssysteem. Wanneer de status van een proces verandert van READY naar RUNNING, betekent dit dat het proces is ingepland. In de tegenovergestelde richting - het is van de planning gehaald. Op het moment dat het proces BLOCKED wordt, bijvoorbeeld bij het initialiseren van een IO-operatie, houdt het besturingssysteem het in deze status tot een bepaald evenement plaatsvindt, zoals de voltooiing van de IO. Op dat moment gaat het naar de status READY en mogelijk meteen naar RUNNING, als het besturingssysteem dat beslist.
Laten we kijken naar een voorbeeld van hoe twee processen deze toestanden doorlopen. Eerst stellen we ons voor dat beide processen draaien en elk alleen de CPU gebruiken. In dit geval zullen hun toestanden er als volgt uitzien.

In het volgende voorbeeld vraagt het eerste proces na een bepaalde tijd om IO en verandert het naar de status BLOCKED, waardoor het andere proces de kans krijgt om te draaien (AFB 1.4). Het besturingssysteem ziet dat proces 0 de CPU niet gebruikt en start proces 1. Tijdens de uitvoering van proces 1 - voltooit de IO en verandert de status van proces 0 naar READY. Uiteindelijk is proces 1 voltooid en bij zijn beƫindiging wordt proces 0 gestart, uitgevoerd en voltooid.

Gegevenstructuur
Het besturingssysteem zelf is een programma, en net als elk ander programma heeft het enkele sleutelgegevensstructuren die verschillende relevante stukjes informatie bijhouden. Om de status van elk proces bij te houden, zal het besturingssysteem sommige processenlijst bijhouden voor alle processen in de status READY en wat aanvullende informatie om de processen die momenteel actief zijn, bij te houden. Ook moet het besturingssysteem de geblokkeerde processen monitoren. Na de voltooiing van de IO is het besturingssysteem verplicht om het benodigde proces wakker te maken en het in de status gereed om te starten te zetten.
Bijvoorbeeld, het besturingssysteem moet de status van de CPU-registers behouden. Op het moment van stopzetting van het proces wordt de status van de registers opgeslagen in de adresruimte van het proces, en op het moment dat het proces weer wordt hervat, worden de waarden van de registers hersteld en zo wordt de uitvoering van dit proces voortgezet.
Naast de staten ready, blocked en running zijn er nog enkele andere staten. Soms kan een proces bij het aanmaken de staat INIT hebben. Tot slot kan een proces in de staat FINAL worden geplaatst, wanneer het al is voltooid, maar de informatie over het nog niet is opgeruimd. In UNIX-systemen wordt deze staat genoemd zombie-proces. Deze staat is nuttig in situaties waarin het oudersuccesproces de retourcode van de nakomeling wil weten, bijvoorbeeld, meestal geeft 0 een succesvolle voltooiing aan, terwijl 1 een fout communiceert, maar programmeurs kunnen extra uitvoercodes maken, die verschillende problemen signaleren. Bij de voltooiing doet het ouderproces de laatste systeemaanroep, bijvoorbeeld wait(), om te wachten op de voltooiing van het nakomelingsproces en het besturingssysteem te informeren dat het de gegevens die met het voltooide proces zijn verbonden, kan opruimen.

Belangrijke punten van de lezing:
ā Proces is de belangrijkste abstractie van een draaiend programma in het besturingssysteem. Op elk moment kan een proces worden beschreven door zijn staat: de inhoud van het geheugen in zijn adresruimte, de inhoud van de registers van de processor, inclusief de instruction pointer en stack pointer, evenals informatie over I/O, bijvoorbeeld de open bestanden die worden gelezen of geschreven.
ā Process API bestaat uit aanroepen die programma's kunnen maken met betrekking tot processen. Gewoonlijk zijn dit aanroepen van creatie, verwijdering of andere.
ā Een proces bevindt zich in een van de vele staten, waaronder running, ready, blocked. Verschillende gebeurtenissen, zoals planning, ontwijking van planning of wachten, kunnen de staat van een proces van de ene naar de andere veranderen.
ā Proceslijst bevat informatie over alle processen in het systeem. Elke vermelding daarin wordt een process control block genoemd, dat in feite een structuur is die alle benodigde informatie over een specifiek proces bevat.
Bron: habr.com
