Komunikácia medzi procesmi I.
SIGNÁLY
Signál je udalosť, ktorú generuje systém UNIX ako odpoveď na nejakú podmienku
a po jej prijatí môže proces previesť nejakú akciu. Signály generujú niektoré chybové podmienky. Názvy signálov sú definované v hlavičkovom súbore signal.h . Všetky začínajú skratkou SIG . tu je výpis tých najdôležitejších :
SIGABORT – prerušenie
procesu
SIGALARM –
budík
SIGFPE – výnimka plávajúcej
čiarky
SIGHUP -
zavesenie
SIGGILL - neplatná
inštrukcia
SIGGINT – prerušenie
terminálu
SIGKILL –
Zabitie
SIGPIPE – zápis do rúry
z ktorej nikto nečíta
SIGQUIT – opustenie
terminálu
SIGSEGV – neplatný prístup
k segmentu pamäti
SIGTERM –
ukončenie
SIGUSR1 – signál definovaný
užívateľom č.1
Keď proces obdrží jeden z týchto signálov bez toho aby predtým zariadil jeho odchytenie
Bude okamžite prerušený. U signálov označených hviezdičkou môžu byť vykonané tiež rôznej akcie, ktoré sú závislé na implementácii. Obvykle je vytvorený súbor s výpisom pamäti. Tento súbor, ktorý sa volá core a je umiestnený v aktuálnom adresári, je obrazom procesu. Môže sa to hodiť pri ladení.
K ďalším signálom patria:
SIGCHILD
– Dcérin proces sa zastavil alebo skončil.
SIGCONT
– Pokračovať vo vykonávaní, bol proces zastavený.
SIGSTOP
– Zastavenie vykonávania. (Nedá sa odchytiť ani
ignorovať).
SIGTSTP
– Signál stop terminálu.
SIGTTIN
– Proces na pozadí sa pokúsi čítať.
SIGTTOU
– Proces na pozadí sa pokúša zapisovať.
Signál SIGHILD sa môže hodiť pri riadení dcérinho procesu. Implicitne je ignorovaný. Ostatné signály spôsobia zastavenie procesu, ktorý ich dostane(s výnimkou signálu SIGCONT, ktorý zaistí pokračovanie procesu). Programy shellu ich používajú k riadeniu úloh, ale užívateľské programy ich využívajú len zriedka.
Keď sú shell a ovládač terminálu nastavená normálne, spôsobí znak prerušenie (často Ctrl-C) zapísaný na klávesnici signál SIGINT, ktorý je poslaný procesu na popredí, tj. práve bežiacemu programu. To vyvolá ukončenie programu, pokiaľ sa mu však nepodarí tento signál odchytiť.
Pokiaľ chceme poslať signál inému procesu ako úlohe na popredí, použijeme príkaz kill. Tento príkaz prijíma voliteľné číslo signálu a PID procesu (ktoré obvykle zistíme príkazom ps), ktorému sa má daný signál poslať. Pokiaľ by sme napríklad chceli poslať shellu, ktorý beží na inom terminály pod iď 512 signál „hangup“, použili by sme nasledujúci príkaz.
Kill –HUP 512
Užitočným variantom príkazu kill je príkaz killall, ktorý umožňuje poslať určitý signál všetkým procesom vykonávajúcim špecifický príkaz. Tento príkaz však nepodporuje všetky verzie systému Unix, aj keď v Linuxe podporovaný nie je. Príkaz killall sa hodí, keď nepoznáme PID alebo keď chcete poslať signál niektorým rozumným procesom, ktoré vykonávajú rovnaký príkaz. Bežne sa používa napríklad v spojení s programom inetd, keď chceme, aby znovu načítal konfiguráciu nastavenia. K tomu môžeme použiť nasledujúci príkaz:
Killall
– HUP inetd
Programy môžu obsluhovať signály pomocou knižnice funkcie signal.
#include
<signal.h>
void (*signal (int sig, void
(*func)(int)))(int);
Táto aj keď komplikovaná deklarácia hovorí, že signal je funkcia, ktorá preberá dva parametre sig a func. Prvý parameter udáva signál, ktorý má byť odchytený alebo ignorovaný. Druhý parameter špecifikuje funkciu, ktorú má program po dostatí daného signálu zavolať. Táto funkcia musí byť z tých, ktoré preberajú jediný argument typu int (prijatý signál) a musí byť typu void. Vlastná funkcia signal vracia funkciu toho istého typu, čo je predchádzajúca hodnota funkcie nastavené pre obsluhu tohto signálu alebo jedna z týchto dvoch špeciálnych hodnôt:
SIG_IGN
– Ignoruje signál.
SIG_DFL
– obnov implicitné chovanie.
OBSLUHA
SIGNÁLOV
Najdôležitejším prostredníkom k signálom
je funkcia signal
void
(*signal (int sig, void (*func)(int)))(int);
prvý parameter - udáva signál, ktorý ma byť odchytený alebo ignorovaný.
druhy parameter - špecifikuje funkciu ktorú ma program po obdržaní daného signálu volať.
Táto funkcia musí byť z tých ktoré
preberajú jediný argument typu int
(prijatý signál) a musí byť typu void.
Príklad:
Funkcia ouch reaguje na signál, ktorý je predaný v parametri sig. Táto funkcia bude zavolaná keď sa objaví nejaký signál. Vypíše správu a potom znovu nastaví obsluhu signálu SIGINT (ktorý implicitne generuje kombinácia kláves Ctrl – C 0na pôvodne správanie.
#include<signal.h>
#include<stdio.h>
#include<unistd.h>
void ouch(int sig)
{
printf(“OUCH! – I got signal
%d\n “,sig);
(void)
signal(SIGINT,SIG_DFL);
}
Funkcia main musí zachytiť signál SIGINT
generovaný po stlačení kláves Ctrl – C. Zvyšok času len čaká v nekonečnom
cykle a každú sekundu vypíše
správu.
int
main()
{
(void)
signal(SIGINT,ouch);
while(1)
{
printf(“Hello world ! \n”);
sleep(1);
}
}
Program zareaguje na prvé stlačenie kombinácie
kláves Ctrl – C a potom pokračuje. Keď stlačíme klávesy Ctrl -C znovu, program
skončí pretože sme nastavili pôvodnú reakciu programu na signál SIG_INT ,ktorý
zaistí jeho ukončenie.
POSIELANIE SIGNÁLOV
Proces môže pomocou funkcie kill poslať signál inému procesu,
vrátane seba samého. Ak nemá program oprávnenie poslať signál potom volanie
funkcie zlyhá , obvykle preto, že cieľový proces vlastní iný užívateľ.
Funkcia
kill
int
kill(pid_t pid,int sig) ;
Funkcia kill pošle špeciálny signál sig procesu, ktorého identifikátor je
predaný v parametru pid. Aby mohol proces signál poslať, musí k tomu mať
oprávnenie. Normálne to znamená že obidve procesy musia mať rovnaké ID, tj
signál môžete poslať len vlastným
procesom.
Ak signál predaný funkcií kill nieje platný (premenná errno
nastavená na hodnotu EINVAL )
, nemá príslušné oprávnenia (EPERM) alebo
špecifikovaný proces neexistuje (ESRCH) funkcia zlyhá ,vráti hodnotu -1 a
nastaví premennú errno jedným z vyššie uvedených spôsobov.
Signály slúžia ako užitočné výstražné
zariadení. Proces môže pomocou funkcie alarm naplánovať odosielanie signálov
SIGALARM niekedy v budúcnosti.
#include
<unistd.d>
unsigned
int alarm (unsigned int second);
Funkcia alarm naplánuje doručenie signálu SIGALARM po určitom počte sekúnd. Varovanie bude v skutočnosti, vďaka oneskoreniu pri spracovaní a nepredvídateľnom plánovaní, doručeného krátko potom. Nulová hodnota zruší ľubovoľné nevyriešené varovanie. Pokiaľ zavoláme funkciu alarm ešte pred doručením signálu, dôjde k jeho preplánovaniu. Každý proces môže mať iba jedno nevyriadené varovanie. Funkcia alarm vráti počet sekúnd, ktoré ostávajú do odoslania ľubovoľného nevyriadeného varovania, prípadne -1, keď zlyhá.
Komunikácia medzi procesmi II.
Vzájomná komunikácia medzi procesmi:
Rúry
Pomocou signálov sme vytvárali oznamovacie
udalosti, ktoré bolo možne použiť
k vyvolaniu odpovedi. Prenesení informácie však boli obmedzené oba
na číslo signálu. Pomocou rúr(pipes) si procesy môžu vymieňať užitočné
dáta.
Čo je to
rúra?
Termín rúra používame tam kde prepojujeme tok dát medzi dvoma procesmi. Väčšinou pripojujeme výstup jedného procesu na vstup iného.
Väčšina užívateľov Unixu bude poznať podstatu príkazu Shellu, kedy je vstup jedného programu privedený priamo na vstup druhého. V príkaze shell sa používa nasledujúca syntax.
cmd1 | cmd2
Shell usporiada štandardný vstup a výstup týchto dvoch príkazov tak, že :
Shell v skutočnosti prepojil prúdy štandardného vstupu a výstupu, takže dáta plynu z klávesnice cez dva príkazy a potom sú zobrazené na obrazovke.
Procesove rúry.
Asi najjednoduchší spôsob predávania dát medzi programami ponúka funkcia popen a pclose. Toto sú ich prototypy:
#include
<stdio.h>
FILE *popen (const char *command, const char
*open_mode);
int pclose (FILE
*stream_to_close);
Popen
Funkcia popen umožňuje programu spustiť iný program ako nový proces a buď mu dáta predáva alebo z neho dáta číta. Reťazec command je názov programu, ktorý sa má spustiť, spoločne s prípadnými parametrami. Parameter open_mode musí byť buď „r“ alebo „w“.
Keď má parameter open_mode hodnotu „r“, bude výstup vyvolaného programu sprístupnený volajúcemu programu a možno ho pomocou obvyklej funkcie knižnice stdio (napr. fread) čítať zo súborového prúdu FILE *, ktorý vráti funkcie popen. Keď má však parameter open_mode hodnotu „w“ , môže program potom tieto dáta čítať zo štandardného vstupu. Normálne vyvolaný program nebude vedieť, že číta dáta z iného procesu bude prosto iba čítať štandardný vstupný prúd a ten spracovávať. Každé volanie funkcie popen musí špecifikovať jednu z hodnôt „r“ alebo „w“. V štandardnej implementácií tejto funkcie nie sú žiadne ďalšie voľby podporovania. To znamená, že nemôžme vyvolať iný program a z neho prostredníctvom rúry čítať aj do neho zapisovať. Pri chybe vráti funkcia popen nulový ukazovateľ. Ak požadujete obojsmernú komunikáciu, rieši sa to normálne pomocou dvoch rúr, kedy každá z nich obstaráva prenos dát v jednom smere.
Pclose
Keď proces spúšťaný cez funkciu popen skončí, môžeme pomocou funkcie pclose uzatvoriť súborový prúd s nim združený. Funkcia pclose vráti riadenie programu až po skončení procesu spusteného prostredníctvom funkcie popen. Keď funkciu zavoláte, proces pobeží ďalej a funkcia pclose bude čakať, až skončí.
Funkcia pclose normálne vracia návratový kód procesu, ktorého súborový prúd zatvára. Keď vyvolávaní proces vykonal pred volaním funkcie pclose príkaz wait, bude návratová hodnota stratená a funkcia pclose vráti hodnotu -1 a nastaví premennú errno na ECHILD.
Volanie funkcie pipe
Hore máme opis vysoko úrovňovej funkcie popen, teraz sa pozriem na nízko úrovňovú funkciu pipe. Táto funkcia umožňuje predávať dáta medzi programami bez nutnosti spúšťať shell, ktorý by vykonal požadovaný príkaz. Taktiež poskytuje lepšiu kontrolu nad čítaním a zapisovaním dát.
Funkcia pipe má nasledujúci prototyp:
#include
<unistd.h>
int pipe(int
file_descriptor[2]);
Funkcií pipe je predávané pole dvoch celočíselných deskriptorov súborov. Toto pole vyplní dvoma novými deskriptormi a vráti nulu. Pri zlyhaní vráti hodnotu -1 a do premennej errno uloží konštantu udávajúcu dôvod zlyhania. Manuálové stránky systému LINUX definujú nasledujúce chyby:
EMFILE Proces používa príliš veľa daskriptorov súborov.
ENFILE Systémová tabuľka súborov je plná.
EFAULT Deskriptor súboru je neplatný.
Dva vrátené deskriptory súborov sú zvláštnym spôsobom prepojené. Ľubovoľné dáta zapísané do deskriptora file_descriptor[1] môžeme načítať opäť z deskriptora file_descriptor[0]. Dáta sú spracované metódou prvý dnu, prvý von, pre nich sa často používa skratka FIFO. To znamená, že do deskriptora file_descriptor[1] zapíše bajty 1,2,3, získate pri čítaní z deskriptora file_descriptor[0] hodnoty 1,2,3 v rovnakom poradí. Týmto sa líši metóda od zásobníku, ktorý funguje na princípe posledný dnu, prvý von, skrátené LIFO.
Je dôležite si uvedomiť, že tieto deskriptory súborov nie sú súborovými prúdmi, takže pri prístupe k dátam musíme miesto funkcie fread a fwrite použiť systémové volania fead a write.
Príklady
1.Vytvorte obslužný program pre niektorý zo signálov, tak aby program
reagoval na “príchod” signálu (napr. SIGUSR1).
#include
<signal.h>
void
signal_handler()
{
printf("Prisiel signal SIGUSR1
...\n");
}
void
signal_handler2()
{
printf("Prisiel signal SIGUSR2 ...
\n");
}
void
main()
{
int count;
pid_t id;
//vrati id tohoto
procesu
id=getpid();
//instalujem obsluhu na signal SIGUSR1 a
SIGUSR2
signal(SIGUSR1,signal_handler);
signal(SIGUSR2,signal_handler2);
//pocitadlo nastavim na nulu a v
cykle
// ak bude pocitadlo delitelne 5 ->
vysle signal
count=0;
while(1){
printf("bezi
cyklus...\n");
sleep(1);
count++;
if(count%5==0)kill(id,SIGUSR1);
if(count%3==0)kill(id,SIGUSR2);
}
}
2.Vytvorte program, ktorý vo
svojom tele vytvorí pipe a sám bude doň zapisovať i čítať z
neho
void ReadFromPipe(int
filedes)
{
int length;
char
buff[100];
read(filedes,&length,sizeof(int));
read(filedes,buff,length);
buff[length]=0; //treba pridat 0 na
konci retazca (jeho ukoncenie)
printf("From pipe:
%s\n",buff);
}
void WriteToPipe(int
filedes,char *buff)
{
int length;
length=strlen(buff);
write(filedes,&length,sizeof(int));
write(filedes,buff,length);
printf("To pipe:
%s\n",buff);
}
void
main()
{
//file descriptor pre
pipe
int
filedes[2];
pipe(filedes);
WriteToPipe(filedes[1],"toto bolo
poslane na pipe");
ReadFromPipe(filedes[0]);
}
3.Vytvorte program, ktorý
bude pozostávať aspoň z dvoch procesov a komunikácia medzi týmito procesmi bude
prebiehať pomocou pipe-ov. (Napr. rodičovský proces bude načítavať čísla z
terminálu a proces-potomok ich bude vypisovať)
void ChildProcess(int
*filedes)
{
char str[100];
int length;
while(1){
length=0;
read(filedes[0],&length,sizeof(int));
if(length>0){
read(filedes[0],str,length);
str[length]=0;
printf("Child
process: %s\n",str);
}
sleep(1);
}
}
void ParentProcess(int
*filedes)
{
char str[100];
int length;
do{
printf("zadaj retazec (0 pre
koniec):\n");
scanf("%s",str);
if(strlen(str)==0)continue;
length=strlen(str);
write(filedes[1],&length,sizeof(int));
write(filedes[1],str,length);
}while(strcmp(str,"0"));
}
void
main()
{
//file descriptory pre
pipe
int
filedes[2];
//vytvorim
pipe
pipe(filedes);
switch(fork()){
case -1:
printf("chyba: nepodarilo sa
vytvorit detsky proces");
break;
case 0:
//obsluha detskeho
procesu
ChildProcess(filedes);
break;
default:
//obsluha povodneho
procesu
ParentProcess(filedes);
}
}
Technická univerzita Košice
Fakulta elektrotechniky
a informatiky
Komunikácia medzi procesmi
(Pipes, signály )
Vypracovali: Vladimír Kríž
3.ročník
Ľubomír Kováč
VTI
Ján
Denci
22.10.2003