Inleiding tot besturingssystemen
Hallo, Habr! Ik wil graag een serie vertaalde artikelen introduceren over een interessante literatuur — OSTEP. In dit materiaal wordt de werking van Unix-achtige besturingssystemen diepgaand besproken, namelijk de omgang met processen, verschillende planners, geheugen en andere vergelijkbare componenten die een modern OS vormen. Je kunt het origineel van al het materiaal 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:
En je kunt ook mijn kanaal bezoeken op =)
Het werk van een programma
Wat gebeurt er eigenlijk wanneer een programma draait? Een gestart programma voert één simpele taak uit — het voert instructies uit. Elke seconde worden miljoenen en zelfs mogelijk miljarden instructies door de processor uit het RAM gehaald, die op zijn beurt ze decodeert (bijvoorbeeld, herkent tot welk type deze instructies behoren) en uitvoert. Dit kan het optellen van twee getallen zijn, toegang tot het geheugen, het controleren van een voorwaarde, overgaan naar een functie, enzovoort. Na het uitvoeren van een instructie gaat de processor naar de volgende instructie. En zo worden instructie na instructie uitgevoerd totdat het programma is voltooid.
Dit voorbeeld is natuurlijk vereenvoudigd — in werkelijkheid maakt moderne hardware het mogelijk om instructies buiten de volgorde uit te voeren, mogelijke uitkomsten te berekenen, instructies gelijktijdig uit te voeren en dergelijke trucs.
Het Von Neumann-model van berekening
De vereenvoudigde vorm van werken die we hebben beschreven lijkt op het Von Neumann-model van berekeningen. Von Neumann was een van de pioniers van computersystemen en ook een van de auteurs van de speltheorie.Tijdens het draaien van een programma vinden nog tal van andere gebeurtenissen plaats, werken veel andere processen en externe logica, waarvan de belangrijkste doel is om de uitvoering, werking en onderhoud van het systeem te vereenvoudigen.
Er is een set software die verantwoordelijk is voor het eenvoudig opstarten van programma's (of zelfs voor het gelijktijdig uitvoeren van meerdere programma's). Het stelt programma's in staat om hetzelfde geheugen te delen en te communiceren met verschillende apparaten. Deze set software wordt in wezen een besturingssysteem genoemd en de taken ervan omvatten het bewaken van de correcte en efficiënte werking van het systeem, evenals het waarborgen van een gemakkelijke bediening van dit systeem.
Besturingssysteem
Een besturingssysteem, afgekort als OS, is een complex van onderling verbonden programma's die zijn ontworpen om de middelen van de computer te beheren en de interactie van de gebruiker met de computer te organiseren..
Een OS bereikt zijn efficiëntie voornamelijk door de belangrijkste techniek — de techniek virtualisatie. Het OS communiceert met de fysieke bronnen (zoals de processor, het geheugen, de schijf, enzovoort) en transformeert deze naar een meer algemene, grotere en gebruiksvriendelijkere vorm van zichzelf. Daarom kan men voor een algemeen begrip het besturingssysteem heel ruwweg vergelijken met een virtuele machine.
Om gebruikers commands aan het besturingssysteem te laten geven en zo de mogelijkheden van de virtuele machine te benutten (zoals: programma starten, geheugen toewijzen, toegang tot een bestand, enzovoort), biedt het besturingssysteem een bepaalde interface, genaamd API (application programming interface) waaruit oproepen (calls) gedaan kunnen worden. Een typisch besturingssysteem biedt de mogelijkheid om honderden systeemoproepen te doen.
En tot slot, omdat virtualisatie het mogelijk maakt dat meerdere programma's (bij wijze van spreken, de CPU delen) en gelijktijdig toegang krijgen tot hun instructies en gegevens (waarbij het geheugen wordt gedeeld), evenals toegang tot schijven (waardoor invoer- en uitvoerapparaten worden gedeeld), wordt het besturingssysteem ook wel resource manager genoemd. Elke processor, schijf en geheugen is een systeemresource, en zo wordt een van de taken van het besturingssysteem het efficiënt beheren van deze bronnen, op een eerlijke of, afhankelijk van de taak waarvoor dit besturingssysteem is ontworpen, de nodige manier.
CPU-virtualisatie
Laten we het volgende programma bekijken:
(https://www.youtube.com/watch?v=zDwT5fUcki4&feature=youtu.be)

Het voert geen speciale acties uit; in wezen roept het gewoon de functie aan spin(), die als taak heeft de tijd cyclisch te controleren en terug te geven, nadat er één seconde is verstreken. Hierdoor herhaalt het oneindig de string die de gebruiker als argument heeft doorgegeven.
Laten we dit programma uitvoeren en als argument het symbool 'A' doorgeven. Het resultaat is niet bijzonder interessant — het systeem voert gewoon het programma uit dat periodiek het symbool 'A' op het scherm weergeeft.
Laten we nu een variant proberen, waarbij meerdere exemplaren van hetzelfde programma worden uitgevoerd, maar die verschillende letters weergeven, zodat het duidelijker is. In dit geval zal het resultaat iets anders zijn. Hoewel we één processor hebben, wordt het programma gelijktijdig uitgevoerd. Hoe kan dit gebeuren? Het komt doordat het besturingssysteem, met behulp van de mogelijkheden van de hardware, een illusie creëert. De illusie dat er meerdere virtuele processors in het systeem zijn, waardoor één fysieke processor in theorie oneindig aantal wordt en hierdoor de programma's ogenschijnlijk gelijktijdig worden uitgevoerd. Deze illusie wordt CPU-virtualisatie.
Een dergelijke situatie roept veel vragen op. Bijvoorbeeld, als meerdere programma's gelijktijdig willen worden uitgevoerd, welk programma zal dan worden uitgevoerd? Dit vraagstuk wordt beantwoord door de 'beleid' van het besturingssysteem. Beleid wordt op veel plaatsen in het besturingssysteem gebruikt en beantwoordt dergelijke vragen, en fungeert ook als basale mechanismen die het besturingssysteem implementeert. Vandaar de rol van het besturingssysteem als resourcebeheerder.
Geheugenvirtualisatie
Laten we nu het geheugen bekijken. Het fysieke model van geheugen in moderne systemen wordt weergegeven als een array van bytes. Om uit het geheugen te lezen, moet je het adres van de celopgeven, zodat je er toegang toe kunt krijgen. Om gegevens te schrijven of bij te werken, moet je ook de gegevens en het adres van de cel opgeven waar je ze wilt schrijven.
Toegang tot het geheugen gebeurt voortdurend tijdens de werking van het programma. Het programma slaat de gehele datastructuur in het geheugen op en benadert deze terwijl het verschillende instructies uitvoert. De instructies zelf worden ook in het geheugen opgeslagen, dus toegang tot het geheugen vindt ook plaats bij elk verzoek voor de volgende instructie.
Aanroep malloc()
Laten we het volgende programma bekijken dat geheugenruimte toewijst met behulp van de aanroep malloc() (https://youtu.be/jnlKRnoT1m0):

Het programma doet verschillende dingen. Ten eerste wordt er een hoeveelheid geheugen toegewezen (regel 7), vervolgens wordt het adres van de toegewezen cel weergegeven (regel 9) en wordt de waarde nul in de eerste positie van het toegewezen geheugen geschreven. Daarna gaat het programma de lus in, waarin het de waarde die in het geheugen op het adres van de variabele “p” is opgeslagen, incrementeert. Ook toont het zijn eigen proces-ID. De proces-ID is uniek voor elk gestart proces. Als we echter meerdere exemplaren opstarten, komen we een interessant resultaat tegen: in de eerste instantie, als we niets doen en gewoon meerdere exemplaren starten, zijn de adressen verschillend. Maar dat valt niet binnen onze theorie! Dat klopt, omdat in moderne distributies standaard de functie van geheugenrandomisatie is ingeschakeld. Wanneer we deze uitschakelen, krijgen we het verwachte resultaat — de geheugenadressen van twee gelijktijdig draaiende programma's zullen overeenkomen.

Uiteindelijk blijkt dat twee onafhankelijke programma's werken met hun eigen privé-adresseruimten, die op hun beurt door het besturingssysteem in fysieke geheugen worden weergegeven.Daarom zal het gebruik van geheugenadressen binnen één programma geen invloed hebben op andere programma's en elk programma denkt dat het zijn eigen stuk fysieke geheugen heeft, volledig ter beschikking gesteld. De realiteit is echter dat fysiek geheugen een gedeelde bron is, waarvan het gebruik door het besturingssysteem wordt beheerd.
Consistentie
Een ander belangrijk onderwerp binnen besturingssystemen is consistentie. Deze term wordt gebruikt wanneer het gaat om problemen in het systeem die kunnen optreden bij het werken met meerdere dingen tegelijkertijd binnen één programma. Consistentieproblemen komen zelfs in het besturingssysteem zelf voor. In de vorige voorbeelden met geheugen- en procesvirtualisatie hebben we begrepen dat het besturingssysteem veel dingen gelijktijdig beheert — het start het eerste proces, daarna het tweede, enzovoort. Zoals het blijkt, kan dit gedrag leiden tot enkele problemen. Moderne multithreaded programma's ondervinden bijvoorbeeld dergelijke moeilijkheden.
Laten we het volgende programma bekijken:

Het programma in de hoofdfunctie maakt twee threads aan met behulp van de aanroep Pthread_create(). In dit voorbeeld kan een thread worden gezien als een functie die wordt uitgevoerd in hetzelfde geheugen naast andere functies, waarbij het aantal gelijktijdig draaiende functies duidelijk meer dan één is. In dit voorbeeld start elke thread en voert de functie uit worker(), die op zijn beurt gewoon een variabele incrementeert.,.
Laten we dit programma uitvoeren met het argument 1000. Zoals je al had kunnen raden, zou het resultaat 2000 moeten zijn, aangezien elke thread de variabele 1000 keer heeft geïncrementeerd. Maar zo eenvoudig is het niet. Laten we proberen het programma uit te voeren met een veel groter aantal herhalingen.

Door als invoer een nummer, bijvoorbeeld 100000, te geven, verwachten we als uitvoer het nummer 200000 te zien. Echter, wanneer we het nummer 100000 meerdere keren uitvoeren, zullen we niet alleen het juiste antwoord niet zien, maar ook verschillende foute antwoorden krijgen. De oplossing ligt in het feit dat het verhogen van het getal drie bewerkingen vereist: het getal uit het geheugen halen, het incrementeer en het getal vervolgens weer terugschrijven. Aangezien al deze instructies niet atomair worden uitgevoerd (niet allemaal tegelijk), kunnen dergelijke vreemde dingen gebeuren. Dit probleem wordt in de programmering race condition — race condition. Wanneer onbekende krachten op een onbekend moment invloed kunnen uitoefenen op de uitvoering van een van je bewerkingen.
Bron: habr.com
