Bevezetés az operációs rendszerekbe
Helló, Habr! Szeretném bemutatni figyelmedbe egy érdekes irodalmi sorozat fordítását — OSTEP. Az anyagban mélyebben megvizsgáljuk az unix-szerű operációs rendszerek működését, különös figyelmet szentelve a folyamatoknak, különböző ütemezőknek, memóriának, és más hasonló összetevőknek, amelyek modern operációs rendszert alkotnak. Az összes anyag eredeti forrását itt találod: . Kérlek vegyétek figyelembe, hogy a fordítás szakértelem nélküli (elég szabad), de remélem, az általános értelemben megmaradt.
A tantárgyhoz kapcsolódó laboratóriumi munkák itt találhatók:
— eredeti:
— eredeti:
— a személyes adaptációm:
Emellett látogathattok el a csatornámra a =)
A program működése
Mi történik, amikor egy program fut? A futtatott program egy egyszerű feladatot végez — végrehajtja az utasításokat. Másodpercről másodpercre milliók, sőt, talán milliárdok utasítást von le a processzor a memóriából, amelytől dekódolja őket (például felismeri, hogy milyen típusú utasításokkal áll szemben) és végrehajtja őket. Ezek lehetnek két szám összeadása, memóriahozzáférés, egy feltétel ellenőrzése, egy funkcióhoz való ugrás stb. Az utasítás végrehajtásának befejezése után a processzor a következő utasításra lép. Így utasításról utasításra, ezek folytatódnak, amíg a program be nem fejeződik.
Ez a példa természetesen leegyszerűsített — a valóságban a modern hardver lehetővé teszi, hogy a processzor rendelés nélkül hajtsa végre az utasításokat, előre számolja a lehetséges eredményeket, és párhuzamosan hajtja végre az utasításokat, és ehhez hasonló trükköket alkalmaz.
Von Neumann-modell
Akkor az általunk leírt leegyszerűsített forma hasonlít a Von Neumann-modellre. Von Neumann a számítógépes rendszerek egyik úttörője, egyben a játékelmélet egyik szerzője is.A program futása alatt számos más esemény is zajlik, sok más folyamat és külső logika működik, amelyek fő célja a rendszer indításának, működésének és karbantartásának egyszerűsítése.
Van egy szoftvercsomag, amely felelős a programok egyszerű indításáért (sőt, lehetővé teszi több program egyidejű futtatását is), és lehetővé teszi a programok számára, hogy megosszák ugyanazt a memóriát, valamint interakcióba lépjenek a különböző eszközökkel. Ezt a szoftvercsomagot alapvetően operációs rendszernek nevezik, és feladatai közé tartozik annak nyomon követése, hogy a rendszer megfelelően és hatékonyan működjön, valamint a rendszer kezelésének egyszerűsítése.
Operációs rendszer
Az operációs rendszer, röviden OS — egy egymással összefüggő programkészlet, amely a számítógép erőforrásainak kezelésére és a felhasználó számítógéppel való interakciójának megszervezésére szolgál..
Az operációs rendszer hatékonyságát elsősorban a legfontosabb technikán keresztül éri el — a virtualizáción. Az operációs rendszer interakciót folytat a fizikai erőforrásokkal (processzor, memória, lemez és hasonlók) és átalakítja azokat egy általánosabb, nagyobb képességekkel bíró, egyszerűbben használható formába. Ezért az operációs rendszer az általános megértés érdekében nagyon durván összehasonlítható egy virtuális géppel.
Ahhoz, hogy lehetővé tegye a felhasználók számára, hogy parancsokat adjanak az operációs rendszernek, így kihasználva a virtuális gép lehetőségeit (mint például program indítása, memória allokálása, fájl elérése stb.), az operációs rendszer biztosít egy interfészt, amelyet API (alkalmazásprogramozási interfész) néven ismerünk, amelyhez hívásokat (call) lehet végezni. Egy tipikus operációs rendszer lehetővé teszi, hogy több száz rendszerhívást lehessen végrehajtani.
Végül, mivel a virtualizáció lehetővé teszi, hogy számos program együttműködjön (így közösen használva a CPU-t), és egyidejűleg hozzáférjenek utasításaikhoz és adataikhoz (ezáltal megosztva a memóriát), valamint hozzáférjenek a lemezekhez (így közösen használva a bemeneti/kimeneti eszközöket), az operációs rendszert erőforráskezelőnek is nevezik. Minden processzor, lemez és memória a rendszer erőforrása, ezért az operációs rendszer egyik feladata az, hogy hatékonyan kezelje ezeket az erőforrásokat, igazságosan vagy éppen ellenkezőleg, attól függően, hogy milyen feladatra tervezték azt az operációs rendszert.
CPU virtualizáció
Nézzük meg a következő programot:
(https://www.youtube.com/watch?v=zDwT5fUcki4&feature=youtu.be)

Ez nem hajt végre különleges műveleteket, lényegében mindössze annyit tesz, hogy meghívja a spin() függvényt, amely feladatként ciklikusan ellenőrzi az időt, és visszatér, miután eltelt egy másodperc. Így végtelenül ismétli azt a sort, amit a felhasználó átadott argumentumként.
Futtassuk ezt a programot, és adjuk át neki az „A” karaktert argumentumként. Az eredmény nem túl érdekes — a rendszer egyszerűen végrehajtja a programot, amely időnként megjeleníti az „A” karaktert a képernyőn.
Most próbáljuk meg azt a variációt, amikor több példányt futtatunk ugyanabból a programból, de különböző betűket jelenítenek meg, hogy jobban érthető legyen. Ez esetben az eredmény valamivel más lesz. Annak ellenére, hogy egy processzorunk van, a program egyidejűleg fut. Hogy lehet ez? Az operációs rendszer, a hardver lehetőségeinek segítségével, illúziót teremt. Az illúziót, hogy a rendszerben több virtuális processzor létezik, egyetlen fizikai processzort elméletileg végtelen mennyiségre változtatva, és így lehetővé téve, hogy a programok látszólag egyidejűleg fussanak. Ezt az illúziót hívjuk CPU virtualizációnak..
Az ilyen kép számos kérdést vet fel, például, ha több program szeretne egyidejűleg elindulni, akkor melyik fog elindulni? E kérdésre az operációs rendszer „politikái” adnak választ. A politikák sok helyen találhatók az operációs rendszerben, és hasonló kérdésekre válaszolnak, valamint alapvető mechanizmusok, amelyeket az operációs rendszer megvalósít. Innen adódik az operációs rendszer szerepe, mint erőforrás-kezelőé.
Memória virtualizáció
Most nézzük meg a memóriát. A modern rendszerek fizikai memória modellje bájtok tömbjeként jelenik meg.Az olvasáshoz a memóriából meg kell adni az elérési címet, hogy hozzáférjünk. Az adatok írásához vagy frissítéséhez szintén meg kell adni az adatokat és azt a cella címét, ahová ezeket írni kell.
A memóriahoz való hozzáférés folyamatosan megtörténik a program futása során. A program tárolja a memória összes adatstruktúráját, és azt végrehajtva hivatkozik rá különböző utasításokkal. Az utasítások szintén a memóriában vannak tárolva, így a memória elérése minden következő utasítás kérésekor megtörténik.
A malloc() hívása
Tekintsük át a következő programot, amely memória területet foglal, egy hívás segítségével malloc() (https://youtu.be/jnlKRnoT1m0):

A program több dolgot végez. Először is, foglal egy bizonyos mennyiségű memóriát (7. sor), majd kiírja az lefoglalt cella címét (9. sor), és nullát ír az lefoglalt memória első helyére. Ezután a program belép egy ciklusba, amelyben növeli az értéket, amely a „p” változó címén található memóriában van. Ezenkívül kiírja a saját folyamata azonosítóját is. A folyamat azonosító egyedi minden futó folyamat számára. Ha több példányt indítunk, érdekes eredményre bukkanunk: az első esetben, ha nem csinálunk semmit és egyszerűen elindítunk több példányt, a címek különbözni fognak. De ez nem illeszkedik a mi elméletünkhöz! Igaz, mivel a modern disztribúciók alapértelmezett beállítása a memória véletlenszerűsítése. Ha ezt kikapcsoljuk, megkapjuk a várt eredményt - a két egyidejűleg futó program memóriacímei egyezni fognak.

Végül tehát kiderül, hogy két független program a saját privát címterének használatával dolgozik, amelyek viszont az operációs rendszer által a fizikai memóriára térképeződnek. Ezért a memória címek használata egyetlen programon belül nem befolyásolja a másikat, és minden program úgy érzi, hogy rendelkezik egy saját fizikális memória részecskéjével, amely teljes egészében az ő rendelkezésére áll. A valóság azonban az, hogy a fizikai memória egy megosztott erőforrás, amelynek kezelését az operációs rendszer végzi.
Következetesség
Az operációs rendszerek egyik fontos témája a következetesség. Ezt a kifejezést akkor használják, amikor olyan problémákról van szó a rendszerben, amelyek a programon belüli több dolog egyidejű végrehajtásakor léphetnek fel. A következetességi problémák még az operációs rendszeren belül is felmerülnek. Az előző példákban a memória és a processzor virtualizálásával kapcsolatosan megértettük, hogy az OS sok dolgot kezel egyidejűleg - először elindít egy folyamatot, majd a másodikat, és így tovább. Kiderült, hogy ez a viselkedés néhány problémát okozhat. Például a modern többszálú programok szembesülnek ilyen nehézségekkel.
Nézzük meg a következő programot:

A program in the main function creates two threads using the call Pthread_create(). In this example, a thread can be thought of as a function running in the same memory space alongside other functions, with the number of concurrently running functions being clearly more than one. In this example, each thread starts and executes the function worker(), which simply increments a variable,.
. We will run this program with the argument 1000. As you might have guessed, the result should be 2000 since each thread increments the variable 1000 times. However, it's not that simple. Let's try running the program with a much larger number of repetitions.

By inputting a number, for example, 100000, we expect to see the output number 200000. However, when running the number 100000 several times, we not only won’t see the correct answer, but we will also get different incorrect answers. The clue lies in the fact that three operations are required to increase the number — fetching the number from memory, incrementing it, and then writing the number back. Since all these instructions do not occur atomically (all at once), such strange things can happen. This problem is called in programming race condition — versenyhelyzet. When unknown forces can influence the execution of any of your operations at an unknown moment.
Forrás: habr.com
