11. Gyakorlat Az operációs rendszer szintje Az operációs rendszer szintű utasítások az alkalmazói programozók rendelkezésére álló teljes utasításkészletet jelentik. Tartalmazzák majdnem az összes ISA-szintű utasítást, valamint az operációs rendszer szintű plusz utasításokat. Ezeket az utasításokat rendszerhívásoknak nevezzük. Ténylegesen minden rendszerhívás az operációs rendszer valamely előre definiált szolgáltatását hívja meg. Ha egy felhasználói program az operációs rendszer szintű utasítást végez, az operációs rendszer ezt az utasítást lépésenként hajtja végre. Amikor azonban az ISA-szintű utasítást hajt végre a program, ezt az operációs rendszer közreműködése nélkül közvetlenül az alatta lévő mikroarchitektúra szintje végzi el. Virtuális memória Régebben a programozóknak kellett megoldania, hogy a program elférjen a memóriában. A megoldás egy másodlagos memória, pl. a lemez igénybevétele. A programozó olyan átfedéseknek nevezett kisebb részekre osztotta fel programját, amelyek külön-külön elfértek a memóriában. A program futásakor először az első rész töltődött be, futott egy darabig, azután amikor befejeződött, betöltődött a második rész, és így tovább. A programozónak kellett gondoskodni az átfedésekre darabolásról, az egyes részek elhelyezkedéséről a másodlagos memóriában, a memória és a lemez közötti mozgatásukról, és általában az átfedéses rendszer kezeléséről. A virtuális memória megszabadítja a programozót minden ilyen adminisztrációtól. Lapozás A gépeken a használható címtartomány és a memóriacímek különböznek. A címtartomány és a memóriacímek elkülönítésnek elve a következő. Bármely időpillanatban közvetlenül 4096 szó érhető el a memóriából, de ezek nem feltétlenül a 0... 4095 közötti memóriacímeknek felelnek meg. A címekhez memóriacímeket rendelünk. Valós és virtuális címek közötti leképezés
Az átfedések kezelésének ezen technikáját lapozásnak hívjuk, a lemezről beolvasott programrészeket lapoknak nevezzük. Azon címeket, amelyekre a program hivatkozni tud virtuális címtartománynak, míg a tényleges, "hardveres" címeket fizikai címtartománynak nevezzük. A memóriatérkép, vagy laptábla a virtuális címeket kapcsolja össze a fizikai címekkel. Feltehető, hogy a lemezen van elég hely a teljes virtuális címtartomány számára. A lapozásnak köszönhetően a programozónak nem is kell tudni a virtuális memória létezéséről, csak azt látja, hogy mennyi hely van még a memóriában. Csak az operációs rendszer íróinak kell tudni, hogyan tartható fenn ez az illúzió. A virtuális címtartományt azonos méretű lapokra szokás felosztani. A ma szokásos lapméretek 512 bájt és 64 kb közé esnek, de előfordul a 4 MB-os lap is. A lapméret mindig 2- hatvány. A fizikai címtartományt hasonlóan osztják fel. A darabok mérete megegyezik a lapmérettel, így mindegyik darab egy lap tárolására alkalmas. Azokat a memóriadarabokat, amelyekbe lapokat töltünk be lapkereteknek nevezzük. Minden virtuális memóriával ellátott gép tartalmaz a virtuális címről fizikai címre leképező eszközt. Ez az MMU (Memory Management Unit, memóriakezelő egység), amely lehet a CPU lapkán, vagy külön lapkán, amely szorosan együttműködik a CPU-val. A következő példában az MMU 32 bites virtuális címeket 15-bites fizikai címekre képez le. Ehhez 32 bites bemeneti és 15 bites kimeneti címekre van szüksége. A 32 bites virtuális címet az MMU 20 bites virtuális lapszámra és a lapon belüli 12 bites offszetre bontja fel (a 20 bit a 4 K-s lapméretből adódik). A virtuális lapszámot indexként használva keresi ki a laptáblából a megfelelő bejegyzést. Az alábbi ábrán a virtuális lapszám 3, így a laptábla 3. elemét veszi. Először megvizsgálja, hogy a hivatkozott lap nincs-e a memóriában, ehhez egy jelenlét /hiány bitet használ. Ebben a példában a bit értéke 1, vagyis a laptáblában van. A következő lépésben a kiválasztott bejegyzésben szereplő lapkeretértéket átmásolja a 15 bites kimeneti regiszter felső 3 bitjére. Azért van szükség 3 bitre, mert a fizikai memória nyolc lapkeretből áll. A virtuális cím alsó 12 bitje átmásolódik a kimeneti regiszter alsó 12 bitjére. Ezt a 15 bites címet küldi tovább a gyorsítótárhoz, vagy a memóriához.
Kérésre lapozás és munkahalmaz modell Nem biztos, hogy a hivatkozott virtuális lap a memóriában van. Az olyan hivatkozás, amely nem a memóriában lévő lapon található címre vonatkozik, laphibát okoz. Laphiba felléptekor az operációs rendszernek be kell olvasni a lemezről a kért lapot, be kell írni új fizikai helyét a laptáblába, és meg kell ismételnie a hibát okozó utasítást. A virtuális memóriával rendelkező gépen akkor is elindíthatunk egy programot, ha egyetlen része sincs a memóriában. Csupán úgy kell beállítani a laptáblát, jelezze, a programhoz tatozó lapok közül egyik sincs a memóriában, mind a másodlagos tárolón helyezkedik el. Amikor a CPU megpróbálja az első utasítást betölteni, akkor rögtön laphiba lép fel, melynek hatására az első utasítást tartalmazó lap betöltődik a memóriába és ez bekerül a laptáblába is. Ezután kezdődhet az első utasítás végrehajtása. Ha az első utasítás 2 címet tartalmaz, s ezek már a betöltöttől két különböző lapon találhatók, akkor újabb két laphiba lép fel és két újabb lap töltődik be mielőtt az utasítás végrehajtódna. A virtuális memóriának ezt a módszerét kérésre lapozásnak nevezzük. A legtöbb program nem egyenletesen hivatkozik a címtartományára, hanem a néhány lap körül "sűrűsödnek össze" a hivatkozások. Bármely t időpillanatban tekinthetjük a legutóbbi k memóriahivatkozásban szereplő lapok halmazát. Ezt munkahalmaznak nevezzük. A programozók ritkán tudják, hogy mely lapok tartoznak a munkahalmazhoz, ezt az operációs rendszernek kell kiderítenie. Ha a program olyan lapra hivatkozik, amely nincs a memóriában, a szükséges lapot be kell tölteni lemezről. Legtöbbször azonban helyet kell csinálni neki, bizonyos lapokat vissza kell írni a lemezre. A legtöbb operációs rendszer megpróbálja megjósolni, hogy melyek a memóriában lévő leghaszontalanabb lapok, amelyek eltávolítása a legkisebb mértékben zavarja a futó programot. Ennek egyik módja az lehet, hogy minden lapra megjósolja, hogy mikor lesz rá legközelebb hivatkozás, és azt a lapot távolítja el, amelynél ez az időpont a legtávolibb. Az LRU algoritmus a legrégebben használt lapot távolítja el a memóriából. Az alábbi példában csak 8 lap fér a memóriába. A 7. laphoz ért a program futása, majd olyan utasításhoz érünk, amely a 8. lapról próbálna meg utasítást betölteni. Az LRU algoritmus szerint a 0. lapot kell eltávolítani, mert ezt használtuk legrégebben. A 8. lapon lehet olyan utasítás, amely visszatérne a 0. lapra. Ekkor a 0. lapot vissza kell hozni a memóriába az 1. lap helyére.
Ha a rendelkezésre álló memória mérete nagyobb a munkahalmazénál, akkor az LRU algoritmus minimalizálni fogja laphibák számát. Egy másik elv a FIFO (First In First Out), amely minden laphoz egy számlálót rendel. Laphiba esetén a számlálókat eggyel növeljük. Ha új lapot hozunk be, akkor annak a számlálóját 0-val kell inicializálni. Azt a lapot kell eltávolítani, amelynek a számlálója a legnagyobb. Ha a munkahalmaz nagyobb a rendelkezésre álló lapkeretek számánál, akkor laphibák gyakoriak lesznek, és egyetlen algoritmus sem adhat jó eredményeket. Azt a jelenséget, amikor egy program gyakran, szinte folyamatosan laphibát generál, vergődésnek nevezzük. Meg kell jegyezni, hogy egy lapot csak akkor kell visszaírni a lemezre, hogyha a tartalma megváltozott. Azokat a lapokat, amelyek beolvasásuk óta nem változtak, tiszta (clean) lapoknak, míg a módosított lapokat szennyezett (dirty) lapoknak hívjuk. Egy-egy bittel nyilvántarthatjuk, hogy az adott lapot módosítottuk-e, és csak akkor kell visszaírni a lemezre, ha igen. Lapméret és elaprózódás Ha a programok és adatok a lapon pontosan elférnek, akkor nem pocsékolunk el helyet. Ellenkező esetben valamennyi hely kihasználatlanul marad. Az elvesztegetett bájtokkal kapcsolatos problémát belső elaprózódásnak nevezzük. Kis lapméreteknél minimális a veszteség, azonban nagy laptáblát kell és sok lapot kell nyilvántartani, amelynek hardveres megvalósítása drága, szoftveresen viszont sok időbe telik. Előnye viszont az, hogy kisebb a vergődés esélye. Szegmentálás A fordítóprogramoknak fordításkor sokféle táblázatuk lehet: változók nevét és attribútumait tartalmazó szimbólumtábla kilistázáshoz megőrzött forráskód az összes felhasznált egész és lebegőpontos konstanst tartalmazó tábla. a program szintaktikus elemzésekor létrehozott elemzési fa a fordítóprogramon belüli eljáráshíváshoz tartozó verem
Az első négy tábla mérete folyamatosan nő a fordítás előrehaladtával, az utolsóé előre nem látható módon nő vagy csökken. Egy megoldás erre a problémára sok teljesen független címtartomány, szegmens bevezetése. A szegmenscímek 0-tól valamely maximális értékig terjednek. Az egyes szegmensek hossza 0-tól a megengedett maximumig bármekkora lehet, még változhat is a végrehajtás során. A szegmensek elvileg be is telhetnek, de általában ez ritkán fordul elő. Ilyen szegmentált memória esetén a programok két részből álló címmel adják meg tényleges címet. Az első a szegmens cím, a második a szegmensen belüli cím. A szegmensek nem csak az az előnye, hogy a változó méretű adatstruktúrát könnyebben kezeli, hanem az is, hogyha csak csak egy részt módosítunk a programban, akkor nem fog a teljes program újra lefordulni, csak az a része, amely szegmensben változás történt. Mivel minden szegmens a programozó által is látható logikai egységet képez, a szegmensek védelme eltérő lehet. Az eljárás-szegmensek lehetnek csak végrehajthatók, vagyis írásukat és olvasásukat megtilthatjuk. A lebegőpontos tömbök lehetnek írhatóak és olvashatóak, de nem végrehajthatóak. Szegmentált memória esetén a felhasználó tudja, hogy mi van az egyes szegmensekben. Szegmentálás megvalósítása A szegmentálás megvalósítása kétféle módon történhet, cseréléssel vagy lapozással. Cserélésnél minden időpillanatban a szegmensek bizonyos halmaza van a memóriában. Ha olyan szegmensre történik hivatkozás, amely nincs a memóriában, akkor az betöltődik. Ha nincs elég hely számára, akkor egy másikat lemezre kell írni, és a helyére betölteni a másik szegmenst. Ez hasonlít a kérésre lapozáshoz. A különbség annyi, hogy a lapméret fix, míg a szegmensek mérete változó. A szegmensek eltávolításakor és egy új szegmens betöltésekor lyukak keletkezhetnek, mivel az új szegmens nem biztos, hogy kitölti a régi helyét. Ezt külső elaprózódásnak nevezzük. A lyukacsosodás megszüntetésének egyik lehetséges módja, hogy a lyuk mögötti szegmenseket a 0. memóriacím felé toljuk. Ekkor a memória végén keletkezik egy nagyobb lyuk. Ezt a módszert összepréselésnek nevezzük. Az összepréselés hátránya, hogy sok időt vesz igénybe. Egy másik megoldás, hogy a lyukak címét és méretét egy listába rakjuk, és a kért szegmenst abba a legkisebb lyukba másoljuk, amibe még belefér. Az eljárás neve legjobb illesztés. Ezáltal a kis lyukakba kisméretű szegmenseket, a nagy lyukakba nagyméretű szegmenseket másolhatunk. Egy másik módszer, hogy egy új szegmens betöltésekor végigmegyünk a listán és azt az első lyukat választjuk, amelybe belefér a szegmens. A módszer neve első illesztés. Ez utóbbi gyorsabb és jobb is a legjobb illesztésnél, azonban sok kis lyukat hozhat létre. A kis lyukak létrejöttét elkerülhetjük, ha olyan szegmenst törlünk, amely előtt és után kis lyuk áll, vagyis ekkor egy nagy lyuk keletkezik. A lapozáskor minden szegmenshez külön laptábla kell. Mivel minden szegmens egy lineáris címtartomány, az összes eddigi lapozási technika alkalmazható a szegmensekre, az eltérés csupán annyi, hogy minden szegmens saját laptáblát kap.
Virtuális B/K utasítások Bevitel/kivitel tekintetében a operációs rendszer szintje lényegesen eltér az ISA-szinttől. Az a felhasználó, aki valódi ISA-szintű utasításokat tudna végrehajtani, hozzájuthatna a rendszerben tárolt bizalmas adatokhoz, be tudna lépni más gépére, stb. A virtuális B/K megszervezésének egyik módja a fájl nevű absztrakció. Legegyszerűbb formájában egy B/K-ra írt bájtsorozatot jelent. Az operációs rendszer szempontjából a fájl általában csak egy bájsorozatot jelent. További struktúrája a felhasználói programoktól függ. A fájl olvasására, írására, lezárása külön utasítások vannak. Amikor egy fájlt olvasunk, akkor a fájlt meg kell keresni a lemezen és be kell tölteni a memóriába. Megnyitás után a fájlok olvashatók. Minden megnyitott fájlhoz tartozik egy pointer, amely a legközelebb kiolvasható bájtra mutat. Értéke a beolvasott bájtok számának megfelelően változik. Ez lehetővé teszi tetszőleges adatblokkok olvasását a fájlból. A pointert tetszőleges értékre is be lehet állítani, így a fájl bármely részét véletlenszerűen el lehet érni. Olvasás után a program lezárhatja a fájlt, ezzel jelzi az operációs rendszernek, hogy már nem fogja használni. Nagygépes operációs rendszerekben a fájlokat logikai rekordok sorozataként képzelik el. Bizonyos operációs rendszerek megkülönböztetnek olyan fájlokat, amelyek csak típusú rekordokat tartalmaznak, és amelyek különböző rekordokat tartalmaznak. Virtuális B/K utasítások megvalósítása A fájlok a lemezen lehetnek egymás követő szektorokban vagy pedig nem egymást követő szektoraiban. Akkor érdemes egymást követő szektorokba rendezni a fájlokat, ha biztosak lehetünk benne, hogy a fájlok mérete nem változik (pl. CD lemez). Ha a fájl nem egymást követő szektorokban helyezkedik el, akkor egy tetszőleges bájt vagy logikai rekord pozícióját egy fájlindex nevű táblázatban kell nyilvántartani, amely tényleges lemezcímeket tartalmazza. A fájl helyfoglalási egységeit úgy is megkereshetjük, hogy láncolt listaként minden helyfoglalási egység tartalmazza a rákövetkező címét. Ha a fájlok egymást követő szektorokban vannak, akkor könnyű megállapítani a fájlok kezdőcímét és méretét, viszont nehezen - vagy egyáltalán nem - lehet bővíteni a fájlok méretét. A második megoldás esetében, akkor fájlméretet könnyen változtathatjuk. Az operációs rendszernek nyomon kell követnie a szabad és a lefoglalt blokkokat. Az egyik megoldás, hogy lyukakat egy listára fűzzük. Ezt a listát szabad listának nevezzük. Előnye, hogy könnyű vele adott méretű lyukakat találni, hátránya, hogy a lista változó méretű. Egy másik megoldás, hogy helyfoglalási egységenként 1 bitet tartalmazó bittérképpel dolgozunk. 0
bit a szabad, az 1 bit a foglalt helyfoglalási egységeket jelöli. A bittérkép előnye, hogy mérete állandó és könnyű a helyfoglalási egységek állapotát szabadról foglaltra (és vissza) változtatni. Nehéz azonban meghatározni a szabad blokkok méretét. Könyvtárkezelő utasítások A számítógép szempontjából megkülönböztetünk online (emberi beavatkozás nélküli) és offline (csak emberi közreműködéssel hozzáférhető) információkat. Az utóbbira példa a CD-ROM. Az on-line információkat fájlokban tároljuk, amelyeket könyvtárakban csoportosíthatunk. A fájlokat, könyvtárakat különböző módon védhetjük, pl. jelszóval, vagy megadhatjuk, hogy bizonyos felhasználók milyen műveleteket végezhetnek a fájlon. A párhuzamos feldolgozás virtuális utasításai Bizonyos számításokat két vagy több párhuzamos processzusként a legkényelmesebb végrehajtani. A párhuzamos processzusoknak előnye még, hogy ezáltal csökkenthető a végrehajtási idő. A párhuzamos processzusok végrehajtásához viszont virtuális utasításokra van szükség. Több CPU-s gépeken a processzusokat egy-egy CPU-hoz rendelhetjük. Az egy CPU-s gépeken a párhuzamos végrehajtást időosztással szimulálják. Az a) ábra párhuzamos processzusokat mutat be, a b) ábrán három processzus időosztással történő végrehajtása látható
Processzusok létrehozása A végrehajtandó programok mindig valamilyen processzus részeként futnak. Ez a processzus a többihez hasonlóan állapottal és azzal a címtérrel jellemezhető, melyen keresztül a program adatai elérhetők. Az állapot minimálisan a programszámlálót, a programstátusz szót, a veremmutatót és az általános regisztereket tartalmazza. A legtöbb modern operációs rendszer megengedi a processzusok dinamikus létrehozását és befejezését. Ehhez külön rendszerhívások kellenek. A rendszerhívás vagy csak egyszerű másolatot készít a hívóról, vagy megengedheti azt is, hogy a hívó állítsa be az állapotát. Néha a létrehozó "szülő" processzus teljesen vagy részben ellenőrzése alatt tarthatja a létrehozott "gyerek" processzust. A párhuzamosan futó processzusok feladataikat sokszor csak úgy képesek megoldani, ha kommunikálnak és működésüket szinkronizálják. A Java program szálakat használ a processzusok szimulálására. A szálak processzusokhoz hasonlítanak, azzal a kivétellel, hogy ugyanazon Java programhoz tartozó szálak ugyanazon címtartományban futnak. Ha egy gépben több CPU van, akkor minden szál másik CPU-n futhat. Ha csak egy CPU-van, akkor a párhuzamos végrehajtást időosztással szimulálják. A processzusok szinkronizációját és versengését az operációs rendszer két nemnegatív egész változóval, a szemaforokkal irányítja. A szemafor műveletek lényeges tulajdonsága oszthatatlanságuk. Miután egy művelet végrehajtása megkezdődött, a szemafort egyetlen másik processzus sem használhatja, amíg az első processzus be nem fejezte a műveletet, vagy nem tért "alvó" állapotba.