POSIX folyamatkezelés

Lőrentey Károly

Folyamatok

A futó programok absztrakciói

Tulajdonságok:

Önálló vezérlési szál

Saját memóriaterület

Nyitott fájlok, hálózati kapcsolatok

Egyéb erőforrások

Általában egynél több folyamat fut egyszerre a rendszerben

Folyamat-azonosító (process id, pid)

Az óra céljai

Folyamatkezelés alapjainak elsajátítása

Aktuális pid lekérdezése

Folyamat megkettőzése a fork hívással

Várakozás a gyermekfolyamat befejeződésére (wait függvények)

Folyamat megszakítása (kill)

Másik program indítása (exec függvénycsalád)

Feladatok:

6 kis program megírása

Az utolsó programot emailben be kell adni

Prog. körny. emlékeztető

A GNU/Linux rendszeren a ps parancs szolgál a futó folyamatok kilistázására

Ha minden folyamatra kíváncsiak vagyunk, használjuk az -A opciót

A lorentey nevű felhasználó összes folyamatát a
ps -u lorentey parancs adja meg.

Próbáljuk ki!

$ ps

PID TTY TIME CMD

2334 pts/2 00:00:00 bash

2411 pts/2 00:00:00 ps

Prog. körny. emlékeztető II.

„Elszabadult” folyamatok kilövése: kill parancs

Argumentuma: a kilövendő folyamat pidje

Próbáljuk ki!

$ sleep 60 &

[1] 2410

$ ps

PID TTY TIME CMD

2334 pts/2 00:00:00 bash

2410 pts/2 00:00:00 sleep

2411 pts/2 00:00:00 ps

$ kill 2410

[1]+ Terminated sleep 60

1. prg: aktuális pid lekérdezése

Programjainkban a getpid függvénnyel kérdezhetjük le az aktuális folyamatazonosítót:

#include <sys/types.h>
#include <unistd.h>
pid_t getpid (void)

Írjunk programot, ami kiírja a saját folyamatazonosítóját!

Folyamat-megkettőzés (fork)

Hogyan lehet új folyamatokat létrehozni?

A fork rendszerhívás segítségével az aktuális folyamatról másolat készül, és a program két példányban fut tovább:

Az új folyamatban (gyerek) a fork() értéke nulla

Az eredeti folyamatban (szülő) a fork() értéke az új folyamat azonosítója

Tipikus használat:

pid = fork();
if (pid < 0) { hiba }
else if (pid == 0) { gyermekfolyamat }
else { szülőfolyamat }

2. prg: A fork használata

2. program (2.fork.c):
Írjunk programot, amely futáskor megkettőzi önmagát, és az eredményül kapott folyamatokban más-más szöveget ír ki, majd fél-fél percet várakozik!

Egy másik terminálról figyeljük meg, amint programunk két példányban fut.

Figyelem: a forkkal bánjunk csínján; véletlenül se hívjuk végtelen ciklusban!

Mi történt?

A fork hívás hatására a rendszer létrehozott egy új folyamatot, melynek állapota a szülőfolyamat állapotának pontos másolata

A gyerek megörökli

a memória tartalmát, így a teljes veremállapotot

a végrehajtás állapotát (regiszterek)

a szülő megnyitott fájlait (ezért működött a printf a gyerekben)

a környezeti változókat, a program paramétereit

...mindent!

A gyerek és a szülő mindössze a pidjükben és a fork visszatérési értékében térnek el egymástól

Várakozás befejeződésre

A gyermekfolyamatunk befejeződésének kivárására használhatjuk a waitpid rendszerhívást:
pid_t waitpid (pid_t pid, int *stats,
int options)

pid: A gyermekfolyamat azonosítója
(-1: bármelyik gyerek)

stats: Futási statisztikák (lehet NULL)

options: Várakozási opciók (lehet 0)

Visszatérési érték: a megállt folyamat pidje

Ha az options paraméternek WNOHANG értéket adunk, a függvény mindig azonnal visszatér

Ilyenkor ha a gyerek még fut, a visszatérési érték nulla

3–4. prg: Várakozás

3. program (3.waitpid.c):
Az előző programunkat írjuk át úgy, hogy a szülőfolyamat ne lépjen ki addig, míg a gyerekfolyamat be nem fejeződik. A gyerek várakozzon a program első paraméterében megadott számú másodpercig, csak azután lépjen ki.

4. program (4.waitpid.c):
A szülőfolyamat másodpercenként kérdezze le a gyerek státuszát, és írja ki, hogy fut-e még. Ha a gyerek leállt, lépjen ő is ki.

Gyerekfolyamat leállítása

Ha idő előtt le szeretnénk állítani gyerekfolyamatunkat, használjuk a kill függvényt:

#include <signal.h>

int kill (pid_t pid, int signal);

Az első paraméter a kilövendő folyamat azonosítója, a második a küldendő megszakítás fajtája

Esetünkben ez legyen SIGTERM.

Később más megszakításokat is használni fogunk.

5. prg: Türelmetlen szülő

5. program (5.kill.c):
Módosítsuk a 4. programot úgy, hogy ha a gyerekfolyamat öt másodperc elteltével még mindig nem lépett ki, a szülő veszítse el a türelmét, és állítsa le a gyermekét.

A szignálkezelés fejezetben még találkozni fogunk a kill függvénnyel.

A fenti feladatot a SIGCHLD megszakítás segítségével elegánsabban (ismételt lekérdezgetés nélkül) is meg lehet oldani. (Lásd később!)

Más program indítása

Mi a helyzet, ha saját magunk megkettőzése helyett egy másik programot szeretnénk gyerekfolyamatként elindítani?

Megoldás: az exec függvénycsalád

Az alábbi függvényhívás a folyamat programját lecseréli a /bin/ls programra, a megadott paraméterekkel:

execl("/bin/ls", "/bin/ls", "-l", "/usr", NULL)

Miért kellett megduplázni a program nevét?

Az execlp ugyanez, de figyelembe veszi a $PATH-et

execve, execv, execle, execvp: lásd dokumentáció

Más program indítása II.

Az exec hívások csak hiba esetén térnek vissza

Ha nincs hiba, az eredeti program memóriatartalma elvész, az új program kerül a helyébe

A megnyitott fájlok viszont megmaradnak!

Ha folytatni szeretnénk a programunkat, forkoljunk.

Az exec hívást a gyerekfolyamat csinálja

A szülő waitpid-del várja ki a program befejeződését

Ennyi információból már meg lehet írni egy egyszerű parancsértelmező környezetet!

6. prg: Szegényember shellje

6. program (6.exec.c): (BEADANDÓ)
Írjunk interaktív programot, mely a bemenetéről beolvasott parancsokat sorban végrehajtja, mindig kivárva, amíg a legutóbb elindított parancs befejeződik.

A teljes pontszámért paramétereket is el kell fogadniuk a parancsoknak!

$ ./5.exec

> ls

5.exec	5.exec.c

> ps

PID TTY TIME CMD

2334 pts/2 00:00:00 bash

2410 pts/2 00:00:00 5.exec

2411 pts/2 00:00:00 ps

> bölömbika

bölömbika: No such file or directory

> <Control-D>

$

Problémák

A parancsértelmező és az általa elindított programok ugyanazokat a fájlokat (a terminált) használják ki- és bemenetre.

Miért nem vesznek össze a bemeneten?
(A kipróbáláshoz indítsuk el a cat programot.)

Vajon ez akkor is működik, ha a bemenet nem egy terminál? Miért? Próbáljuk ki!

Hogyan lehetne a fork() után információt cserélni a gyerek- és a szülőfolyamat között?

Károly Lőrentey

2004-01-26T01:37:48

Károly Lőrentey