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 sigfunc. 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 :

 

  1. Štandardný vstup príkazu cmd1 je napojený na klávesnicu terminálu.
  2. Štandardný vstup príkazu cmd2 bude predávaný na štandartný vstup príkazu cmd2.
  3. Štandardný výstup z programu cmd2 je prepojený s obrazovkou terminálu.

 

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 popenpclose. 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 fwrite použiť systémové volania feadwrite.

 

 

 

 

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