A folyamatokat kezelo rendszerhívások a következok: fork(), exec(), exit(), wait(), getpid(), getppid(), setpgrp() és nice(). A fork() rendszerhívással lehet egy új folyamatot létrehozni. A létrehozott gyermek-folyamat a szülo-folyamat pontos másolata lesz, vagyis a gyermek-folyamat a szülo-folyamat majdnem minden jellemzojét örökli (a pid-et például nem!). A gyermek- és a szülo-folyamat egymással párhuzamosan fognak futni. A fork() rendszerhívás C nyelvben egy int típusú értékkel tér vissza, és ez az, ami alapján meg lehet különböztetni, hogy melyik a szülo- ill. melyik a gyermek-folyamat. A gyermek-folyamatban a visszatérési érték: 0, míg a szülo-folyamatban a visszatérési érték egyenlo a gyermek-folyamat pid-jével. Negatív visszatérési érték a rendszerhívás sikertelenségét jelzi (a hiba oka lehet például az, hogy nem volt elég memória a gyermek-folyamat létrehozásához). A fork() a következo formában fordul elo a leggyakrabban:
valtozo=fork(); /* Megszuljuk a gyermeket ... */ if (valtozo < 0) { /* Hibauzenet kiirasa, a sikertelen rendszerhivasrol! */ } else { if (valtozo == 0) { ... /* A gyermek ezt csinalja ... */ } else { ... /* A szulo pedig ezt ... */ } }
Mi az, amit a gyermek-folyamat fork után a szülotol örököl?
Mi az, ami a fork után eltér a szülo és a gyermek között?
A leggyakrabban a gyermek-folyamatnak a szülo feladatától teljesen eltéro dolgot kell csinálnia, például egy másik fájlban tárolt programot kell végrehajtania. Erre való az exec rendszerhívás, amely a UNIX kernelnek talán a legbonyolultabb rendszerhívása. Az exec több különbözo formában érheto el, itt az execle() hívás lesz bemutatva. Amikor egy C nyelvu programot az operációs rendszer shelljébol elindítunk, akkor a shell-parancsként beadott programnév után írt egyéb programparaméterek hogyan lesznek a programból elérhetok. A C program fo-eljárását a következo módon kell deklarálni:
main(argc, argv, envp) int argc; char **argv, **envp;
A program ezután a híváskor megadott paraméterek darabszámát az argc paraméteren keresztül tudja megkapni, maguk a paraméterek pedig az argv (karakteres tömb) változón keresztül érhetok el. Az envp karakter tömb a shell-változókat tartalmazza. Az mindig igaz, hogy argc > 0, és az argv[0] nem üres, mert a nulladik parancs-paraméter az maga a beadott parancsnév szokott lenni. Példa az execle() hívásra:
r=execle("/bin/ls","ls","-l","alma.c",(char *)0, envp);
Az elso paraméter a végrehajtandó bináris program fájlnevét tartalmazza. A következo paraméter magának a parancsnak a nevét tartalmazza (ez nem kell, de így szokás), a harmadik és a negyedik paraméter a végrehajtandó parancs egyéb paramétereit tartalmazza. A (char *)0 a programnak átadandó paraméter-felsorolás végét jelzi. Az utolsó ( envp) paraméter az új programnak átadandó shell-változókra mutat.
Az exec() rendszerhívások végrehajtásukkor ellenorzik, hogy a végrehajtandó fájl rwx-bitjei közül az x-bit be van-e állítva, az új folyamat befér-e még a memóriába. Ha a fenti feltételek nem teljesülnek, akkor az exec rendszerhívás sikertelen lesz (ezért kell figyelni az exec rendszerhívás visszatérési értékét). (Az exec rendszerhívás nem zárja le automatikusan a korábban használt fájlokat! A megnyitott fájlokat az újabb, exec-elt program tovább használhatja.)
Ha egy szülo-folyamat megszül egy gyermek-folyamatot, majd a gyermek elvégzi a feladatát, akkor a gyermeknek meg kell hívnia az exit() rendszerhívást. Az exit rendszerhívásnak egyetlen paramétere van, egy 0 és 255 közé eso egész szám, az ún. exit-státusz. A szülo folyamat lekérdezheti a gyermek-folyamat exit-státuszát, és ebbol például arra következtethet, hogy a gyermek-folyamat milyen eredménnyel tudta a feladatát ellátni. A szülo-folyamat a gyermek- folyamat befejezodésére a wait() rendszerhívás segítségével várakozhat. A wait() rendszerhívás egyetlen paramétere egy egész típusú változóra mutat, és a rendszerhívás végrehajtása után a befejezodött gyermek-folyamat exit-státusza kerül a megadott változó magasabb helyiértéku bytejába. Ha a gyermek-folyamat végrehajtotta az exit() rendszerhívást, a szülo pedig még nem adta ki a wait rendszerhívást, akkor a gyermek-folyamat általában ún. zombie-processz lesz, vagyis az általa lefoglalt memória felszabadul, de a processz-táblában még foglalja a helyet. (Ha egy szülo-folyamat nem hajt végre egyetlen wait()-et sem, akkor elobb- utóbb betelhet a processz-tábla, és a rendszer nem lesz képes újabb folyamatokat elindítani.)
A fenti rendszerhívásokat például a következoképpen használhatjuk:
main(argc, argv, envp) int argc; char **argv, **envp; { int eredm; /* . . . */ if (fork() == 0) { execle("/bin/ls","ls","-l",(char *)0,envp); } else { wait(&eredm); } }
A fenti program elindít egy gyermek-folyamatot, amely végrehajtja a UNIX ls parancsát, és a szülo megvárja, amíg a gyermek-folyamat befejezodik. Lényegében a UNIX shelljei is így muködnek (ezt késobb egy részletesebb példán is megnézhetjük).
A getpid() és a getppid() rendszerhívások közül az elobbi az ot végrehajtó folyamat pid-jét, az utóbbi pedig az ot végrehajtó folyamat szülo-folyamatának a pid-jét adja vissza (mindkét függvény egész típusú értéket ad vissza).
A setpgrp() rendszerhívás az ot végrehajtó folyamat folyamat-csoport azonosítóját a folyamat azonosítójára (pid-jére) állítja, és az új folyamat-csoport azonosítót adja vissza. A folyamat-csoport azonosító azért hasznos, mert a kill() rendszerhívással signal-t lehet küldeni egy folyamat-csoport minden egyes tagjának (ld. késobb).
A nice() rendszerhívással lehet egy folyamat ütemezési prioritását módosítani. Az alapértelmezés szerinti prioritási érték 20; ezt az értéket növelve csökken a folyamat prioritása. A nice() paraméterében megadott értéket az operációs rendszer hozzáadja a folyamat prioritásához. Negativ paramétert csak szuperfelhasználó jogú folyamatoktól fogad el az operációs rendszer.