Ciele
- Zistiť, čo sú to procesy.
- Zombie procesy
Postup
Krok 1: Procesy
Príprava
Pre prácu na cvičení si stiahnite súbor k cvičeniu.
Obsah súboru:
.
└── 05
├──u1_1.c
├──u2_1.c
├──u3_1.c
├──u3_2.c
├──Makefile
└──tests
└──u1_1_test.sh
Poznámka
Programy je možné kompilovať pomocou make nazov_suboru.c
Napríklad:
make u1_1
Úvod
Proces je prostredie v ktorom sa vykonávajú programy. Každý proces má svoj vlastný adresný priestor. Okrem toho sú mu aj pridelené systémové zdroje. Každý proces získa pri vzniku PID (process identifier), ktoré je v systéme unikátne.
Poznámka
Pre získanie PID aktívnych procesov slúži príkaz ps
.
Časť výpisu môže vyzerať takto:
PID TTY TIME CMD
5557 pts/2 00:00:00 sh
5689 pts/2 00:00:00 ps
Viacero procesov môže vykonávať jeden program. Štruktúra procesov je hierarchická pretože každý proces bol vytvorený iným procesom. Týmto vzniká medzi procesmi vzťah „rodič – potomok“.
Služba jadra getpid()
Slúži na získanie ID procesu.
#include <unistd.h>
#include <sys/types.h>
int getpid();
Návratová hodnota |
---|
úspešne vykonanie funkcie vráti ID procesu |
Ak sa táto služba použije samostatne v programe, tak to nemá veľký význam. Využíva sa predovšetkým, keď aktuálny proces vytvorí svojho potomka a je potrebné sa medzi nimi zorientovať.
Poznámka
Viac si o službe getpid() môžete naštudovať v manuáli: man 2 getpid
Príklad použitia:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(){
printf("ID procesu je %d\n", getpid());
}
Služba jadra getppid()
Služba jadra getppid() vráti ID rodiča volajúceho procesu.
#include <unistd.h>
#include <sys/types.h>
int getppid();
Návratová hodnota |
---|
úspešne vykonanie funkcie vráti ID rodiča volajúceho procesu |
Podobná funkcionalita ako getpid().
Poznámka
Viac si o službe getppid() môžete naštudovať v manuáli: man 2 getppid
Príklad použitia:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(){
printf("ID procesu je %d\n", getpid());
printf("ID jeho rodiča je %d\n", getppid());
}
Služba jadra fork()
Vytvára takmer identický proces (klon). Jedná sa o proces potomka. Rodičovský proces, ktorý proces potomka vytvoril, nečaká na ukončenie svojho potomka. Ich vykonávanie prebieha súčasne.
#include <unistd.h>
int fork();
Návratová hodnota |
---|
v rodičovskom procese je to ID jeho potomka |
v potomkovi je to 0 |
v prípade chyby je to -1 |

Úloha 1.1
Preštudujte si v manuáli, ktoré vlastnosti zdedí potomok od rodiča: man 2 fork
Príklad použitia:
#include <stdio.h>
#include <unistd.h>
int main(){
int pid; /*Premenná definovaná pre uloženie návratovej hodnoty fork()*/
pid = fork();
if(pid == 0){ /*pid == 0 vtedy ak sa jedná o potomka*/
printf("Toto je proces potomka.\n");
}
else{ /*(pid == ID potomka && pid!=0) ak sa jedná o rodiča*/
printf("Toto je proces rodica.\n");
}
}
Aplikovanie poznatkov k službe fork()
Úloha 1.2
Vytvorte program, ktorý vytvorí potomka procesu a na základe návratovej hodnoty služby fork() vypíše na štandardný výstup, čo je vykonávané potomkom printf(„Potomok\n“);
, a čo rodičom printf(„Rodic\n“);
. V prípade chyby vypíšte chybové hlásenie printf(„Chyba\n“);
.
Upozornenie
Nezabudnite, že v prípade rodičovského procesu sa bude návratová hodnota služby fork() stále meniť, takže nie je možné ho v podmienke jednoznačne identifikovať. Skúste preskúmať možnosti riešenia.
Úloha 1.3
Rozšírte predchádzajúci program o výpis id procesov. Očakávaný výstup je nasledovný:
Rodic [ID = 4897]
Potomok [ID = 4898]
Upozornenie
Jednotlivé id procesov sú pri každom spustení programu iné.
Poznámka
Zdrojové súbory k úlohe sa nachádzajú v priečinku 05 pod názvom u1_1.c. Pred testovaním je potrebné skompilovať program u1_1.c. Úlohu je možné testovať zadaním následovného príkazu do terminálu:
./tests/u1_1_test.sh
Služba jadra execve()
Služba execve() priradí nový program procesu.
#include <unistd.h>
int execve (const char *path, char *const argv[], char *const envp[]);
Parametere | |
---|---|
const char *path | názov spustiteľného programu, ktorý má proces vykonávať. Uvádza sa bez prípony „.c“ |
char *const argv[] | pole argumentov, ktoré chceme danému spúšťanému programu odovzdávať |
char *const envp[] | vlastnosti prostredia spúšťaného programu |
Návratová hodnota |
---|
Pri úspešnom vykonaní nevracia návratovú hodnotu. |
Pri chybe -1. |
Príklad použitia:
Program child.c
#include <stdio.h>
#include <unistd.h>
int main(){
printf("Proces potomka [id = %d] sa vykonava.\n",getpid());
sleep(1);
/*funkcia sleep() slúži na pozastavenie vykonávania programu
na počet sekúnd uvedených v parametri. V tomto prípade sa jedná o 1 sekundu.*/
printf("Proces potomka [id = %d] sa ukoncuje.\n",getpid());
}
Program execve.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
if(fork()==0){
/*Ak je návratová hodnota fork == 0, tak sa jedná o potomka.*/
/*Argumenty execve() nesmú byť NULL. V tomto prípade stačia
prázdne polia na konci ktorých musí byť NULL. Spustí sa program child.c*/
execve("child", (char *[]){NULL}, (char *[]){NULL});
//Tu sa program nikdy nedostane, ak execve() nezlyhá.
exit(0);
}
printf("Proces rodica [id = %d] sa vykonava.\n",getpid());
sleep(2);
printf("Proces rodica [id = %d] sa ukoncuje.\n",getpid());
}
Opis kódu:
Pri zavolaní služby execve() sa pôvodný kód procesu (program execve.c) nahradí kódom programu child.c. Tiež sa nahradia aj údaje pôvodného programu (premenné, konštanty, alokovaná pamäť).
Ukážkový výpis z programu vyzerá nasledovne:
Proces rodica [id = 14646] sa vykonava.
Proces potomka [id = 14647] sa vykonava.
Proces potomka [id = 14647] sa ukoncuje.
Proces rodica [id = 14646] sa ukoncuje.
Jednotlivé id procesov sú pri každom spustení programu iné. Najprv sa začal vykonávať proces rodiča. Po vytvorení potomka pomocou služby fork() sa začal vykonávať proces potomka. Keďže proces potomka bol pozastavený len na 1 sekundu, zatiaľ čo proces rodiča až na 2, tak sa najprv ukončí proces potomka.
Úloha 1.4
Syntax služby execve() je nasledovná:
int execve(const char *filename, char *const argv[], char *const envp[]);
Vyhľadajte v manuáli, čo musia polia argv a envp obsahovať na konci:
Služby podobné execve()
Názvy služieb |
---|
int execl(const char path, const char arg0, ..., (char *)0); |
int execlp(const char file, const char arg0, ..., (char *)0); |
int execle(const char path, const char arg0, ..., (char )0, char const envp[]); |
int execv(const char path, char const argv[]); |
int execvp(const char file, char const argv[]); |
Ak sa v službe nachádza písmeno: |
---|
l – argumenty sú main-u predané ako zoznam stringov const char arg0, const char arg1,.... |
v – argumenty sú main-u predané ako pole stringov char *const argv[]. |
p – služby využívajú premennú prostredia PATH na vyhľadanie spustiteľného programu. Ak sa spustiteľný program nenachádza v žiadnom adresári uvedenom v premennej prostredia PATH, je nutné použiť ako argument meno programu s absolútnou, resp. relatívnou cestou k danému programu. |
e – prostredie môže byť špecifikované volajúcim procesom. Špecifikuje sa pomocou char *const evnp[]. |
Príklad použitia:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/*Nasledujúci program spustí nový program v pôvodnom pomocou služby execlp.*/
int main(){
printf("Spustenie ps pomocou execlp:\n");
execlp("ps", "ps", "-ax", (char *){NULL});
printf("Koniec.\n");
exit(0);
}
Spustenie ps pomocou execlp:
PID TTY STAT TIME COMMAND
3856 ? Ss 0:00 /usr/lib/openssh/sftp-server
4001 ? Ss 0:00 /usr/lib/openssh/sftp-server
16842 pts/1 Ss 0:00 -sh
16870 pts/1 R+ 0:00 ps -ax
Opis kódu:
Program vypísal prvú správu „Spustenie ps pomocou execlp:“, následne zavolal službu execlp(). Tým sa spustilo vykonávanie nového kódu z nového vykonateľného súboru špecifikovaného vo volaní execlp(). Po skončení programu sa nevykonal návrat do pôvodného programu, takže sa nevypísala správa „Koniec.“.
Služba jadra wait()
Služba wait() pozastaví vykonávanie volajúceho procesu po dobu, kým sa neukončí jeho proces – potomok.
#include <sys/wait.h>
pid_t wait(int *wstatus);
Parametre | |
---|---|
int *wstatusd | smerník na stavový buffer (celočíselná hodnota). Hodnota môže byť celočíselná alebo NULL. Ak sa použije celočíselná hodnota, tak služba uloží stavovú informáciu do stavového buffra, na ktorý odkazuje táto hodnota (je to smerník). NULL sa používa, ak s touto stavovou informáciou nepotrebujeme neskôr pracovať. |
Návratová hodnota |
---|
v prípade chyby -1 |
v prípade úspešného volania vráti ID ukončeného procesu |
Príklad použitia:
Poznámka
Program child.c je použitý ten istý, ako v príklade použitia služby execve().
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(){
if(fork()==0){
/*Ak je návratová hodnota fork == 0, tak sa jedná o potomka.*/
/*Argumenty execve() nesmú byť NULL. V tomto prípade stačia prázdne
polia na konci ktorých musí byť NULL. Spustí sa program child.c*/
execve("child", (char *[]){NULL}, (char *[]){NULL});
//Tu sa program nikdy nedostane, ak execve() nezlyhá.
exit(0);
}
printf("Proces rodica [id = %d] sa vykonava.\n",getpid());
/*Služba wait() pozastavila vykonávanie rodiča po dobu, kým sa neukončí proces
jeho potomka. V tomto prípade nepotrebujeme pracovať so stavovou informáciou,
takže sa do parametra dáva NULL.*/
if(wait(NULL) > 0){
printf("Proces rodica [id = %d] zaznamenal ukoncenie potomka\n", getpid());
}
printf("Proces rodica [id = %d] sa ukoncuje.\n",getpid());
}
Opis kódu:
V procese potomka sa prostredníctvom služby execve() priradil procesu nový program. Proces rodiča čaká na ukončenie potomka pomocou služby wait(). Až po ukončení procesu potomka sa môže ukončiť proces rodiča.
Ukážkový výpis programu:
Proces rodica [id = 25824] sa vykonava.
Proces potomka [id = 25825] sa vykonava.
Proces potomka [id = 25825] sa ukoncuje.
Proces rodica [id = 25824]: rodic zaznamenal ukoncenie potomka
Proces rodica [id = 25824] sa ukoncuje.
Úloha 1.5
Upravte vyššie uvedený program tak, aby k výpisu:
„Proces [id = 25824]: rodic zaznamenal ukoncenie potomka“
doplnil aj pid ukončeného potomka. Pracujte so službou wait(). Upravený výpis bude vyzerať nasledovne:
Proces rodica [id = 25824] sa vykonava.
Proces potomka [id = 25825] sa vykonava.
Proces potomka [id = 25825] sa ukoncuje.
Proces rodica [id = 25824]: rodic zaznamenal ukoncenie potomka [id = 25825]
Proces rodica [id = 25824] sa ukoncuje.
Služba jadra waitpid()
Služba waitpid() pozastaví vykonávanie volajúceho procesu po dobu, kým sa neukončí jeho proces – potomok.
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
Parametere | ||
---|---|---|
pid_t pid | môže nadobúdať hodnoty: | -1 – čaká na ukončenie ľubovoľného potomka. V tomto stave je ekvivalentný k službe wait() |
>0 – čaká na ukončenie potomka presne daným PID | ||
==0 – čaká na ukončenie ľubovoľného potomka, ktorého skupinové ID je rovnaké s ID skupiny volajúceho procesu | ||
<-1 – čaká na ukončenie potomka, ktorého skupinové ID je rovnaké s absolútnou hodnotou pid | ||
int *wstatus | smerník na stavový buffer (celočíselná hodnota). Hodnota môže byť: | celočíselná - služba uloží stavovú informáciu do stavového buffra, na ktorý odkazuje táto hodnota (je to smerník) |
NULL - so stavovou informáciou nepotrebujeme ďalej pracovať | ||
int options | môže nadobúdať hodnoty: | 0, WNOHANG, WUNTRACED, WCONTINUED |
Návratová hodnota |
---|
v prípade chyby -1 |
v prípade úspešného volania vráti ID ukončeného procesu |
Úloha 1.6
Preštudujte si služby wait() a waitpid() v manuáli. man 2 wait
Služba jadra system()
Služba system() spustí nový proces, v ňom shell a ten vykoná príkaz zadaný ako parameter tejto funkcie. Spája činností služieb fork(), execve() a waitpid().
#include <stdlib.h>
int system (const char *command)
Parametere | |
---|---|
const char *command | shell príkaz, ktorý služby vykoná |
Návratová hodnota |
---|
Ak je *command NULL, tak vracia nenulovú hodnotu, ak je shell dostupný, alebo 0 ak nie je dostupný. |
Ak nemôže byť proces potomka vytvorený alebo jeho status nemôže byť získaný, tak je návratovou hodnotou -1. |
Ak shell nemôže byť vykonaný na procese potomka, tak je návratová hodnota taká, akoby bol shell potomka ukončený volaním –exit(2) so statusom 127. |
Ak prebehli všetky systémové volania v poriadku, tak je návratovou hodnotou status ukončenia shellu potomka, ktorý bol použitý na vykonanie programu. (Status ukončenia shellu je status ukončenia posledného vykonaného príkazu. |
Príklad použitia:
#include <stdio.h>
#include <stdlib.h>
/*Nasledujúci program spustí program v shelli pomocou parametru.*/
int main(){
printf("Spustenie ps s parametrami pomocou sluzby system():\n");
system("ps ax");
printf("Hotovo.\n");
exit(0);
}
Úloha 1.7
Vytvorte jednoduchý interpretátor shell príkazov. Jeho úlohou bude spúšťať príkazy bez parametrov a to tak, že pomocou scanf načítate jednoduchý shell príkaz.
Poznámka
Zdrojové súbory k úlohe sa nachádzajú v priečinku 05 pod názvom u2_1.c. Pred testovaním je potrebné skompilovať program u2_1.c. Úlohu je možné testovať prostredníctvom terminálu a to tak, že po spustení programu zadáte jednoduchý shell príkaz akoby ste ho písali do terminálu.
Ukážkový výpis programu môže vyzerať nasledovne:
ls
system
system.c
Krok 2: Zombie procesy
Opis zombie procesu
Závažný problém, ktorý vedie k vyčerpávaniu systémových zdrojov. Jedná sa o procesy, ktoré nie sú aktívne, no naďalej zostávajú v systéme ako "zombie" procesy. Dôvod vzniku: Ak proces vracia svojmu rodičovi návratovú hodnotu prostredníctvom služby exit() (bude vysvetlená neskôr), tak v systémovej tabuľke ostáva záznam o tejto hodnote. Tento záznam pretrváva dovtedy, pokiaľ rodič alebo nejaký iný program nepreberie návratovú hodnotu (prostredníctvom zavolania služby wait() alebo waitpid()). Ak však rodič alebo nejaký iný program túto hodnotu neprevezme, potomok nemôže ukončiť svoju činnosť. Rodič nikdy nečaká na ukončenie potomka, pretože ich vykonávanie prebieha naraz (ako 2 nezávislé procesy). Zombie proces vzniká vtedy, keď proces rodiča ukončí svoju činnosť skôr ako proces potomka. Takýto proces zostane v tabuľke procesov pokiaľ sa o jeho odstránenie nepostará proces init.
Služba jadra exit()
Služba exit() spôsobí okamžité ukončenie vykonávaného procesu.
#include <sys/wait.h>
void exit(int status);
Parametere | |
---|---|
int status | návratová hodnota, ktorá je predaná rodičovskému procesu |
Návratová hodnota |
---|
Nie je žiadna |
Príklad použitia:
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
int status;
switch(fork()){
case 0://Úspešné vytvorenie potomka.
printf("Pid potomka je: %d\n", getpid());
printf("Pid rodica je: %d\n", getppid());
sleep(3);
exit(51);//Hodnota návratového kódu bola zvolená náhodne.
break;
case -1:
perror("Nepodarilo sa vytvorit potomka.");
exit(1);
break;
default:
//Tu sa jedná o rodičovský proces
printf("Pid potomka ziskane v rodicovskom procese = %d\n", wait(&status));
printf("Status = %x\n", status);
if(WIFEXITED(status)){
printf("Status (cez makro) = %d\n", WEXITSTATUS(status));
}
exit(0);
break;
}
}
Opis kódu:
Program vytvoril potomka, ktorého činnosť na 3 sekundy pozastavil. Proces rodiča čaká na ukončenie potomka prostredníctvom služby wait(). Pomocou tejto služby je získané aj PID potomka v rodičovskom procese. Použitím makrá WEXITSTATUS je možné sa dostať k návratovej hodnote potomka "51".
Ukážkový výstup môže vyzerať nasledovne:
Pid potomka je: 1077
Pid rodica je: 1076
Pid potomka ziskane v rodicovskom procese = 1077
Status = 3300
Status (cez makro) = 51
Úloha 2.1
Preskúmajte si význam jednotlivých makier v manuáli k službe wait(): man 2 wait
Záverečná úloha k cvičeniu
Úloha 2.2
V programe u3_1.c načítajte zo štandardného vstupu reťazec znakov rôznej dĺžky. Parametrom funkcie createProcess(char* command) bude práve spomínaný reťazec. Vo funkcií vytvorte proces a priraďte mu program u3_2.c. Tomúto programu predajte reťazec command ako argument. V programe u3_2.c zistíte dĺžku reťazca a tu nastavte ako návratový kód tohto programu. Po ukončení procesu potomka vypíšte túto dĺžku zistenú z návratového kódu na štandardný výstup.