Ugrás a tartalomhoz

Operációs rendszerek mérnöki megközelítésben

Benyó Balázs, Fék Márk, Kiss István, Kóczy Annamária, Kondorosi Károly, Mészáros Tamás, Román Gyula, Szeberényi Imre, Sziray József

Panem Kiadó

5.5. POSIX

5.5. POSIX

A számítógépes rendszerek fejlődésével a ’80-as évek végére egyre inkább igény mutatkozott egy hordozható rendszerinterfész megalkotására. Ezt erősítette, hogy a UNIX-rendszerek fejlődésének akkor közel 20 éve során a UNIX a legkülönfélébb hardverkörnyezetekben jelent meg a kis- és nagyképeken egyaránt, de sajnos némileg eltérő irányzatot képviselve. A változatok közötti kis eltérések azonban megkeserítették a szoftvergyártók életét, akik előtt a UNIX-rendszerek széles körű elterjedésével és elfogadásával fel­csillant a lehetőség, hogy ne csak egy hardver platformhoz kötődő alkalmazásokat készítsenek. Ezen probléma megoldásaként született meg a POSIX (Portable Operating System Interface) szabvány, amely az alkalmazói programok szempontjából egy általános felületet kívánt definiálni, hordozhatóvá téve ezzel a szabványt betartó alkalmazásokat.

Amint a betűszó is utal rá, egy hordozható felületet kívántak a szabványban rögzíteni, de nem titkoltan a UNIX különböző változatainak figyelembevételével. Ez a magyarázata az X megjelenésének a POSIX betűszóban. Az 1990-ben megjelent szabványt röviden POSIX vagy POSIX.1 néven szokás emlegetni. A különböző szabványügyi szervezetek által használt nevek a következők:

  • ISO/IEC IS 9945-1:1990

  • ANSI/IEEE 1003.1-1990.

Ez a szabvány C nyelven definiál egy operációs rendszer interfészt, ami az alkalmazások szempontjából ír le egy szabványos rendszerfelületet. A POSIX.1 kidolgozásával párhuzamosan több 1003.x jelű IEEE szabvány kidolgozása is megkezdődött, melyeket gyakran POSIX.x szabványoknak neveznek. Ezek többnyire az 1003.1 előírásait tekintik alapnak és arra épülnek. Ezek közül a legfontosabbak az 5.42. ábra szerintiek.

A szabványosítási törekvések eredményeként számos más, az alkalmazások hordozhatósága szempontjából fontos szabvány is készült. Ezek nagy száma miatt azonban hamar tarthatatlanná vált az összes egymás melletti, és egymásra épülő szabvány minden kombinációjának megtartása, hiszen adott esetben még az is előfordulhatna, hogy két alkalmazás azért nem kompatíbilis, mert az alkalmazás számára előírt szabványok ellentétesek egymással. Ezt az elvi „rendetlenséget” próbálják feloldani az ún. alkalmazási profilok (AEP: Application Environment Profile). Ezek az alkalmazások különböző jellegéhez alakított szabványhalmazok. Így külön szabványhalmaz definiálja például a hálózati alkalmazások környezetét,a CAD alkalmazások környezetét stb.

A továbbiakban összefoglaljuk az egyik legfontosabb szabvány, a POSIX.1 célkitűzéseit és a szabvány környezetét, felépítését, fontosabb témáit. Az egyszerűség kedvéért az 1003.1 jelű szabványra egyszerűen csak POSIX néven hivatkozunk, ahol ez nem zavaró.

5.42. ábra. ábra - A legfontosabb POSIX.x szabványok

A legfontosabb POSIX.x szabványok


5.5.1. Alapfogalmak, felépítés

A POSIX.1 alapvető célkitűzése, hogy olyan rendszerinterfészt definiáljon, amely alkalmas hordozható alkalmazások készítésére. Ezt több mint 200 C függvény segítségével adja meg, melyek többsége a két nagy UNIX-irányzat, a BSD és a System V rendszerhívásával vagy rendszerfüggvényével azonos. Van azonban néhány terület, ahol a szabvány – szakítva a BSD és a System V megoldásaival – egy harmadik megoldást ír elő.

Fontos hangsúlyozni, hogy a szabvány csupán rendszerfelületet definiál, és nem írja elő annak megvalósítási módját, így nem írja elő azt sem, hogy egy adott függvény valójában egy rendszerhívás, vagy csupán egy könyvtári függvény. A megvalósítás oldaláról nézve a UNIX csupán egy lehetséges megvalósítás, de lehet más rendszer is POSIX-megfelelő.

Ha POSIX-megfelelőségről beszélünk, akkor értelemszerűen azt vagy az operációs rendszer oldaláról, mint egy POSIX-implementációról, vagy az alkalmazás oldaláról tesszük. Implementáció oldalról a megfelelőség azt jelenti, hogy egy adott operációs rendszer mennyire felel meg a szabvány előírásai­nak. Egy konkrét operációs rendszer megfelelőségét dokumentációval kell alátámasztani. A dokumentáció szerkezetének pontosan követni kell a POSIX-szabvány szerkezetét, amelyben nyilatkozni kell a megvalósítás megfelelőségéről. Maga a szabvány 6 különböző kategóriába sorolja az előírásokat, attól függően, hogy milyen jellegű az előírás. Ezek a kategóriák a következők:

  • Implementáció által definiált (implementation-defined). Ezzel olyan működést, vagy értéket jelöl, amelyet a szabvány nem köt meg, de az implementáció megfelelőségi dokumentumában pontosan meg kell adni annak értékét, vagy le kell írni a pontos működést. Az ilyen jellegű dolgokat alkalmazás oldalról ki lehet használni, de lehetőleg kerülni kell.

  • Nem specifikált (unspecified). Ezzel olyan működést vagy értéket jelöl a szabvány, mellyel szemben nincs semmilyen követelmény. Az implementáció szabadon rendelkezhet és a konkrét működést, vagy értéket nem kell dokumentálni.

  • Nem definiált (undefined). Ez olyan működést, vagy értéket jelent, mellyel szemben egy hibátlan program nem támaszthat követelményeket. Természetesen ilyet az alkalmazás oldalról nem szabad kihasználni.

  • Kell (shall). Az implementáció pontosan előírja a működést vagy a kérdéses értéket.

  • Kellene (should). Ez egy javaslatot ír elő az adott értékre, vagy működésre, de az implementáció ettől eltérhet.

  • Lehet (may). Ez az implementáció számára egy opciót jelöl, amit nem kötelező megvalósítani.

Az alkalmazás oldalról a megfelelőség tekintetében négy egymásra épülő szintet különböztetünk meg (5.43. ábra).

  • Szigorúan megfelelő alkalmazás. Olyan alkalmazás, amely csak a POSIX-szabványra épül, és hibátlanul működik a szabványban nem specifikált és implementáció által definiált tulajdonságok és paraméterek tetszőleges értékével.

  • ISO/IEC-megfelelő alkalmazás. Olyan alkalmazás, amely a POSIX-szabvány mellett felhasznál más ISO/IEC szabványokat is.

  • Nemzeti szabványokat is felhasználó alkalmazás. Olyan POSIX-alkalmazás, amely nemzeti szabványokat is felhasznál.

  • Egyéb nem szabványos kiterjesztéseket is felhasználó alkalmazás. Olyan POSIX-alkalmazás, amely felhasznál nem szabványos kiterjesztéseket is.

5.43. ábra. ábra - Alkalmazások megfelelősége

Alkalmazások megfelelősége


5.5.2. POSIX környezet

A szabvány értelmezési és felhasználási területét definiálja az a leírás, amely a környezetet adja meg. Ez a környezet sokban hasonlít a UNIX-rendszerhez, de az implementációt tekintve nem feltétlenül UNIX, hiszen a szabvány nem definiál implementációt, csak felületet. A környezet legfontosabb elemei a következők:

  • Többfelhasználós, több folyamat konkurens futtatására alkalmas kör­nyezet, melyben a folyamatoknak és a felhasználóknak egyedi azonosítójuk van.

  • Hierarchikus, nem tisztán fa szerkezetű állományrendszer. Fontos kiemelni, hogy a hierarchianevek szintjén, azaz egy névtérben jelenik meg. Az állományoknak van ugyan egy egyedi sorszám-azonosítója, de a megvalósítást a szabvány nem írja elő.

  • A felhasználók adatainak elérését védelmi rendszer szabályozza, amely hasonlít a UNIX-rendszeréhez, de lehet attól szelektívebb, azaz szigorúbb is.

A szabvány a UNIX-implementációk felhasználásával keletkezett ugyan, de a fenti környezetet tekintve nem zár ki más rendszereket (például VMS, OS/2 MVS) sem.

5.5.3. Hordozható alkalmazások

Ahogyan azt a szabvány fogalmainak ismertetéskor láttuk, az implementáció tekintetében vannak választási lehetőségek (should, may). Ezek elsősorban a különböző UNIX-változatoknak a szabványhoz való igazítása miatt jöttek létre. A legfontosabb ilyen implementáció által definiált területek a következők:

  • Job-kontroll megvalósítása.

  • Elmentett set-uid megvalósítása.

  • chown() kiterjesztett megvalósítása. Ez azt jelenti, hogy az implementáció lehetősége, hogy a System V filozófiának megfelelően minden tulajdonos használhatja a chown() rendszerhívást, vagy csak a superuser (POSIX-terminológiával: megfelelő user).

  • Hosszú állománynevek kezelése.

  • Speciális karakterek kezelésének tiltása.

A hordozhatóság érdekében a POSIX-szabvány által megengedett lehetőségekhez az alkalmazásokat úgy kell elkészíteni, hogy ezeket vagy ne hasz­nálják ki, vagy rugalmasan alkalmazkodjanak az adott implementáció tulajdonságaihoz. Ez utóbbi megvalósítására a szabvány két lehetőséget kínál.

  • Fordítási időben megfelelő feltételes fordítási opciók és konstansok használatával. Ezt a C nyelvi interfészen a <unistd.h> állomány konstansai biztosítják.

  • Futási idejű lekérdezéssel a sysconf(), pathconf() illetve fpathconf() függvények használatával.

A hordozhatóság lehetősége azonnal felveti azt a kérdést, hogy hogyan, milyen formátumban lehetséges az adatok illetve programok átvitele az egyik környezetből a másikba. A POSIX.1 rendszerfelületet definiál ugyan, de mivel ezt a kérdést nem tudta megkerülni, egy külön fejezetben ajánlást tesz hordozható formátumokra és az ezeket előállító segédprogramokra is, amit később az 1003.2 (POSIX.2) jelű szabvánnyal foglalkozó bizottság dolgozott ki részletesen.

A POSIX.1 alapján egy alkalmazás új környezetbe való telepítéséhez a következő információkat kell egy hordozható csomagba becsomagolni:

  • Az alkalmazás programkódját

      1. Forráskódban. Ez a kódkészlet problémáját veti fel, mivel a POSIX az ISO 646 kódkészletre épül. Sajnos az ISO 646 kódkészletben számos jel nem definiált, amit viszont a legtöbb programozási nyelv, így a C is használ. Ezek helyettesítésére az ún. trigraph karaktereket használhatjuk. Ezek 3 karakteres karaktersorozatok, amelyek helyettesítik a megfelelő jelet.

      2. Bináris változatban. Bináris formában természetesen csak bizonyos korlátozásokkal lehetséges programokat egyik környezetből a másikba átvinni. A POSIX.1 utal arra, hogy létezik ún. ABI (Appli­cation Binary Interface), amivel részben lehetséges a probléma kezelése. A másik ígéretes próbálkozás az ANDF (Architecture Neutral Distribution Format), amely egy közbülső kódot definiál. Ez a közbülső kód minden lényeges információt tartalmaz a végleges kód generálásához. A kódgenerálás fázisa így eltolódhat az adott architektúrán történő installálás pillanatáig.

    • Az alkalmazás adatait, ami tartalmazza az on-line dokumentációt is. Ez az eltérő számábrázolási formátumok és pontosságok problémáját veti fel. Ezek elkerülhetők, ha az adatokat is szövegként visszük át, bár ahogyan a forráskód átvitelénél tárgyaltuk ez sem teljesen problémamentes.

    • Az installációs utasításokat és scripteket. Annak ellenére, hogy a PO­SIX.1 nem elvileg alapszik más szabványokon, ezen a téren a POSIX.2-re utal, mint ajánlásra, hiszen ez foglalkozik a shellekkel.

Egy hordozható alkalmazásnál különös figyelemmel kell lenni arra, hogy az alkalmazásban használt állományok nevei hordozhatók legyenek, és lehetőleg ne tartalmazzon abszolút útnévvel állományhivatkozást.

Az állományok csomagolására a cpio és a tar segédprogramokat javasolja a szabvány. Ezek kimeneti formátuma kellően egyszerű, így szinte minden operációs rendszerben lehetséges a kezelésük. Mindkét programnak vannak előnyös és hátrányos tulajdonságai a másikkal szemben, ami használatukat bizonyos körülmények között korlátozza. Így például a tar nem menti ki a device típusú állományokat. A cpio ezeket kimenti ugyan, de visszatöltéskor a tarral ellentétben nem tartja meg a linkeket.

5.44. ábra. ábra - Trigraph karakterek

Trigraph karakterek


5.5.4. Folyamatkezelés

A POSIX-környezetben a folyamatok konkurensen futnak, melyeket a UNIX-rendszereknél megismert módon a fork() rendszerhívással lehet létrehozni, és az ott megszokott jellemzőkkel rendelkeznek. A szabványba a folyamatkezelés terén alapvetően a System V rendszerekben alkalmazott megoldások kerültek be. BSD-örökség viszont a kiegészítő csoportazonosítók és a jelzés-maszk alkalmazása. A terminálhoz rendelt szekciók (session) ötlete a két irányvonal ötvözéseként jött létre, ami lehetőséget ad a BSD-rendszerekben támogatott ún. job-kontroll koncepció, és a System V rendszerekben alkalmazott folyamatcsoport használatára is. Ennek lényege az, hogy minden folyamat tagja egy szekciónak és egy folyamatcsoportnak is. Egy szekcióhoz egyszerre több folyamatcsoport is tartozhat, ami lényegében a BSD job koncepciójának felel meg.

A folyamatok legfontosabb tulajdonságai a következők:

  • Folyamatazonosító (PID). Egyedi azonosítószám.

  • Szülő folyamat azonosítója (PPID).

  • Folyamat csoportazonosító (PGID).

  • Login név.

  • Valós felhasználói azonosító (UID).

  • Effektív felhasználói azonosító. Ez az ún. set-uid bittel rendelkező programoknál eltérhet a valós UID-től. Ez lehetőséget ad arra, hogy átmenetileg, vagy véglegesen a folyamatot indító felhasználó azonosítójától eltérő azonosítóval illetve jogokkal rendelkezzen a folyamat.

  • Valós felhasználói csoportazonosító (GID).

  • Effektív felhasználói csoportazonosító. Ez az ún. set-uid bittel rendelkező programoknál eltérhet a valós GID-től. Ez lehetőséget ad arra, hogy átmenetileg, vagy véglegesen a folyamatot indító felhasználó csoportazonosítójától eltérő azonosítóval, illetve jogokkal rendelkezzen a processz.

  • Kiegészítő csoportazonosítók.

  • Munkakatalógus.

  • Állományok létrehozási maszkja (UMASK). Ez egy bitmaszk, mely minden új állomány létrehozásánál részt vesz a védelmi attribútumok kialakításában.

  • Jelzés maszk.

  • Terminál azonosító.

  • Szekció (session) azonosító.

  • Futási idők.

A szabvány a folyamatkezeléshez a következő, a UNIX-rendszerekből jól ismert függvényeket definiálja: abort(), exit(), execl(), execle(), exelp(), execv(), execve(), execvp(), wait(), waitpid().

5.5.5. Állománykezelés

A szabvány állománykezeléssel foglalkozó része azt a programfelületet definiálja, ahogyan az állományokat az egyes alkalmazások elérhetik. Az állományrendszer felépítésével a POSIX csak az 5.4.2. pontban ismertetett névtér absztrakció szintjén foglalkozik. Ez azt jelenti, hogy az állományokat, pontosabban azok neveit nem tisztán fa struktúrájú hierarchiában képzeljük el. Ebben a hierarchiában öt állománytípust különböztetünk meg:

  • normál állomány (regular file),

  • katalógus (directory),

  • FIFO,

  • blokkos elérésű eszköz,

  • karakteres elérésű eszköz.

A szabvány javasolja (should), hogy az állományok nevei ún. hordozható állománynevek legyenek. Ezek csak az angol abc kis- és nagybetűit, a pont (.), az aláhúzás (_) és a kötőjel (-) karaktereket tartalmazhatják. Ezen felül a nevek nem kezdődhetnek kötőjellel sem. Az útnevekben az egyes komponenseket per (/) karakterrel kell elválasztani. Egymás után több ilyen elválasztójel is lehet, kivéve az útnév legelején. Az egymás utáni / jelek egy / jelnek értelmeződnek.

Az állományok legfontosabb tulajdonságai a következők:

  • Állomány típusa.

  • Hozzáférés védelmi kódja (permission).

A POSIX a UNIX 3x3-as védelmét követeli meg, de implementáció által definiált módon megenged ezen felüli védelmet is. Ha ilyen létezik, akkor:

  • Annak állományonként engedélyezhetőnek illetve tilthatónak kell lennie.

  • Az alkalmazói program szempontjából ugyanúgy kell megjelennie, mint az eredeti 3x3-as védelem, tehát létezni kell a „tulajdonos”, „csoport” és „bárki” fogalmaknak.

  • Ha engedélyezve van, akkor az a korábbi védelmet felülírva jelenik meg a különböző rendszerhívásoknál (például stat, fstat).

  • Egyedi azonosítószám. Ez gyakorlatban az eszköz azonosítóját és az i-node számot jelenti, de ez már implementáció kérdése.

  • Hivatkozás (link) számláló.

  • Tulajdonos azonosítója és csoportazonosítója (UID, GID).

  • Állomány hossza byte-ban.

  • Utolsó módosítási idő.

  • Utolsó hozzáférési idő.

  • Utolsó státusmódosítási idő. A fenti adatokat tároló adatstruktúra módosítási ideje. Gyakorlatban az i-node módosítási ideje.

Az állományok kezelése az ANSI C alacsony és magas szintű függvényeivel történhet a POSIX rendszerekben. Ezek leírására nem térünk ki részletesen, csupán néhány fontos elemet ragadunk ki, amelyek némileg eltérnek mind a BSD, mind a System V megoldásaitól.

  • Nem használható az mknod() rendszerhívás. Helyette a speciális bejegyzések külön hívással állíthatók elő (például mkdir(), mkfifo()).

  • A katalógusok kezelésére külön interfész van (mkdir(), rmdir(), open­dir(), readdir(), rwinddir(), closedir()). A POSIX előírásai szerint a katalógusok nem olvashatók normál állományként, csak a fenti függvényeken keresztül, és csak szekvenciálisan érhetők el.

  • A szabvány bevezeti a rename() rendszerhívást, bár normál állományokra a link() és unlink() páros is használható, de katalógusokra csak a rename().

  • Az állományok megnyitása más UNIX-változatokhoz hasonlóan lehetséges O_APPEND illetve O_NONBLOCK módon is, de a POSIX nem támogatja az O_SYNC flag használatát, és sync() rendszerhívás sincs a szabványban. Ez utóbbi helyett egy fsync() rendszerhívást definiál, ami állományokra szelektíven adható ki. A megnyitott állomány különböző tulajdonságai az fcntl() függvény használatával lekérdezhetők illetve beállíthatók. Ezzel oldhatók meg a kölcsönös kizárást biztosító zárolások (lock) is.

5.5.6. Jelzéskezelés

A POSIX jelzéskezelés az egyik olyan terület, amely teljesen szakított a UNIX-hagyományokkal és a rendszerfelületet tekintve eltér a UNIX-irányvonaltól. Ennek oka, hogy mind az eredeti System V, mind a BSD-jelzés kezelése megoldhatatlan problémákat vet fel. A fő gondot az a dilemma jelenti, hogy mi történjen abban az esetben, ha egy jelzés érvényre jut, majd hatására elindul az alkalmazásban definiált kezelő rutin, és közben újból bejön ugyanaz a jelzés.

A System V filozófia szerint a jelzés érvényre jutásakor az adott jelzéshez tartozó kezelési mód kijelölése visszaáll az alapértékre. Ha az alkalmazásnak ez nem felel meg, akkor a kezelőrutinban újra át kell állítani, különben az alapértelmezés szerinti tevékenységet hajtja végre a rendszer, ami a legtöbb jelzés esetén programmegállást jelent. Azonban hiába veszi magához a program újból az adott jelzést a kezelőrutin elején, lehet hogy ott már késő, ugyanis van egy rövid idő, amikor a jelzés már alaphelyzetben van, és már érvényre juthat a következő jelzés.

A BSD filozófia szerint a jelzéshez rendelt kijelölés nem áll vissza alaphelyzetbe. Ez viszont feltételezi, hogy a kezelő rutin újrabelépő. Ami nem minden esetben teljesíthető.

A POSIX jelzéskezelése némi hardveres szemléletet tükröz. Ahogyan egy hardver megszakítás is maszkolható, úgy egy POSIX folyamathoz és a jelzést lekezelő rutinhoz is hozzárendelhető egy maszk. Ez az ún. jelzésmaszk előírja, hogy az adott pillanatban mely jelzések nem juthatnak érvényre, azaz blokkolódnak (5.45. ábra). Ez a megoldás a jelzéskezelés megszokott interfészét is érintette, hiszen egy adott szignáljelzéshez nemcsak a kezelőrutint kell hozzárendelni, hanem egy maszkot is. Így a jelzéskezelés beállításához nem a UNIX signal() rutinját, hanem a sigaction() POSIX-rutint kell alkalmazni, melynek egy struktúrában kell megadni a megfelelő paramétereket. A folyamat jelzésmaszkja a folyamat keletkezése során öröklődik, amit maga a folyamat is megváltoztathat a sigprocmask() rendszerhívással. Ha egy jelzés érvényre jut, akkor a kezelőrutin futása alatt egy új maszk jut érvényre, ami az eredeti maszk, a kezelő rutinhoz rendelt maszk, és az aktuális jelzés sorszámának uniójaként keletkezik. Ezzel és a maszkot manipuláló függvényekkel (sigemtyset(), sigfillset(), sigaddset(), sigdelset(), sigismember()) korrektül kezelhetők a fenti problémák.

5.45. ábra. ábra - POSIX jelzéskezelés

POSIX jelzéskezelés


A jelzés küldésére továbbra is megmaradt a UNIX-rendszerekben használatos kill() függvény. Hasonlóan használható a pause() függvény is, amely egy jelzést generáló eseményre való várakozásra használható. Ennek POSIX kiterjesztése a sigsuspend() függvény, amelynek paraméterként megadható a várakozás idejére érvényes jelzésmaszk is.

5.5.7. Terminálkezelés

A POSIX terminálkezelés új elemeket és régi UNIX-hagyományokat is tartalmaz. Alapjában a terminál kezelését befolyásoló paraméterek és működési módok lényegében változatlanul maradtak, de a kezelést végző függvények teljesen megváltoztak. Így megmaradtak az input és output kezelését befolyásoló speciális karakterek és az input két fő működési módja a kanonikus és nem kanonikus mód. Megszűnt viszont a korai UNIX-verziókban megjelenő ioctl() függvény, mivel nem illeszkedett sem a POSIX sem az ANSI C filozófiájához. Ezt teljesen kiváltották a tcgetattr() és a tcsetattr() POSIX függvények. Az ioctl() függvény leginkább azért nem felelt meg a szabvány követelményeinek, mert nagyon sok feladata volt, és ennek megfelelően különböző módon kellett paraméterezni. Jellemző a függvény összetettségére, hogy a harmadik argumentumának típusa és jelentése függött a második argumentumtól.

A terminálkezelés tekintetében azért is különösen fontos a strukturált, átlátható módszerek használata, mert ha egy program megváltoztatja a terminál kezelésének módját és paramétereit, az hatással van az összes azonos terminálról futó programra is. Ezért külön hangsúlyozza a szabvány, hogy a programok indulásakor el kell menteni az eredeti beállításokat, mielőtt megváltoztatnánk azokat, hogy az alkalmazás megállásakor az eredeti állapot visszaállítható legyen. Fontos továbbá, hogy a visszaállítás a különböző hi­ba­ágakon, illetve megszakítás hatására történő megálláskor is megtörténjen.