Exec a roura PDF Tisk E-mail
Zhlédnutí: 2118
Hodnocení čtenářů: / 0
SlabéVynikající 
Napsal filbar   
Úterý, 11 září 2007
Aktualizováno ( Pátek, 31 srpen 2007 )

Jak jsem minule uvedl dneska budeme pokračovat kombinací funkcí fork() a exec() a mezi procesy a vlákny si pošleme první data pomocí roury.

Exec

Funkce exec slouží k nahrazení aktuálního procesu určitou funkcí, která je zadaná v parametrech. Exec má 5 variant, které lze rozdělit do dvou kategorií. První kategorie začíná na execl a parametry jsou zde uvedeny normálně. Druhá kategorie execv má parametry uložené v poli. Jejich prototypy jsou následující:

#include <unistd.h>

extern char **environ;

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg , ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *filename, char *const argv [], char *const envp[]);

První parametr obsahuje buď plnou cestu k programu(execl(), execle(), execv()), nebo jenom jeho jméno, kdy se pro nalezení proměnné použije proměnná PATH(ostatní funkce). Do druhého parametru u execl[p,e] přijde opět jméno programu. Třetí parametr jsou parametry volaného příkazu a jako poslední je nutné uvést znak 0. Funkce execv[p,e] jsou jednodušší, protože jejich parametry ukládámě do pole. Pouze první parametr zapisujeme přímo. U fukcí končících na e ještě lze použít poslední parametr, kde modifikujeme proměnné prostředí. Opět je nutno použít ukončovací nulu. Příklad si ukážeme na příkazu who -l -i -b, který vypíše přihlašovací procesy, nečínné procesy a čas, kdy se systém nastartoval. Jako první uvedu funkci execl:

 
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void) {
printf("Tady bezi puvodni program.\n");
printf("Soustim exec!!!\n");
execl("/usr/bin/who","who","-l","-i", "-b",0);
sleep (20);
printf("Tento text se nezobrazi.\n");
exit(0);
}

Jak funguje execv* si ukážeme na funkci execvp:

 
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void) {
char *const args[]={"who","-l","-i","-b",0};
printf("Tady bezi puvodni program.\n");
printf("Soustim exec!!!\n");
execvp("who",args);
printf("Tento text se nezobrazi.\n");
exit(0);
}

Jako výsledek obdržíme něco takového:

 
Tady bezi puvodni program.
Soustim exec!!!
who: Warning: -i will be removed in a future release;   use -u instead
system boot  Jul 18 11:30
LOGIN    tty1         Jul 18 11:30              3835 id=1
LOGIN    tty2         Jul 18 11:30              3836 id=2
LOGIN    tty3         Jul 18 11:30              3837 id=3
LOGIN    tty4         Jul 18 11:30              3838 id=4
bartmann pts/0        Jul 19 12:36   .         10213 (:0.0)
bartmann pts/4        Jul 18 15:07 00:53        5933 (:0.0)

Dobře si všimněte, že se ani v jednom případě nezobrazil text "Tento text se nikdy nezobrazí". Je to tak proto, že původní proces je úplně nahrazen procesem zadaným do funkce exec, takže ke konstrukcím za exec se program nikdy nedostane. Paramer envp u exexle() a execve() může vypadat podobné jako v následujícím příkladu:

 
char * const pp[]= {"PATH=/usr/bin:/bin/:/home/bartmann/moje/programy", "LANG=cs_CZ.utf8", "LC_ALL=cs_CZ.utf8",0}

Jak vidíme ani zde nesmíme zapomenout na ukončovací 0.

V následujícím příkladu si ukážeme jak zkombinovat fork() a exec(), aby se v jednom procesu prováděl původní program a ve druhém program nový. Program vlastně vypíše podrobný seznam programů v aktuálním adresáři. Výpis se provádí v procesu potomka, takže v procesu rodiče můžeme provádět další konstrukce:

 

 
|#include <stdio.h>
|#include <stdlib.h>
|#include <sys/types.h>
|#include <unistd.h>
|       
|int main(void) {
|       int fpid, ret;
|       printf("Ahoj, tady je puvodni proces\n");
|       char * const prog[]={"ls","-al",0};
|       char * const pp[]={"PATH=/usr/bin/:/usr/sbin/:/sbin:/bin",0};
|       char  cdir[1000];
|       fpid=fork();
|       if (fpid != -1) {
|               if (fpid ==0) { // Potomek
|                       char *retv=getcwd(cdir,1000);
|                       if (retv == NULL) {
|                               printf("Nepodarilo se ziskat aktualni adresar\n");
|                       }
|                       printf("Vypisuji obsah aktualniho adresare %s\n",cdir);
|                       ret=execve("/bin/ls",prog,pp);
|                       if (ret == -1 ) {
|                               printf("Nepodarilo se spustit prikaz\n");
|                               exit (1);
|                       }
|               } else { // Rodic
|                       printf("Tady bezi rodic.\n");
|               }
|       }
|       exit(0);
|}

Roura

Teď když jsme se naučili i funkce exec můžeme již přistoupit k posílání prvních dat a to pomocí roury. Její prototyp vypadá:

#include <unistd.h>
int pipe(int filedes[2]);

Jak vidíme obsahuje jeden parametr a to pole o dvou prvcích, což jsou deskriptory souborů. První prvek pole slouží pro čtení a do druhého se zapisuje. Později si ještě ukážeme jak lze rouru použít místo standardního vstupu a výstupu. V případě chyby je navrácena hodnota -1:

 
|#include <stdio.h>
|#include <stdlib.h>
|#include <unistd.h>
|#include <sys>
|#include <sys>
|       
|int main (void) {
|       int fpid, ret;
|       int roura[2];
|       int roura2[2];
|       int * chstat;
|       ret=pipe(roura);
|       if (ret == -1) {
|               printf("Nepodarilo se vytvorit rouru\n");
|               exit(1);
|       }
|       ret=pipe(roura2);
|       if (ret == -1) {
|               printf("Nepodarilo se vytvorit rouru\n");
|               exit(1);
|       }
|
|       fpid=fork();
|       if (fpid != -1 ) {
|               if (fpid == 0) { //potomek
|                       int chpid=getpid();
|                       int ppid1;
|                       write (roura[1],&chpid,sizeof(chpid));
|                       read (roura2[0],&ppid1,sizeof(ppid1));
|                       printf("PPID: %d\n",ppid1);
|                       execlp("pstree","pstree",0);
|               } else { // Rodic
|                       int chpid1;
|                       read (roura[0],&chpid1,sizeof(chpid1));
|                       printf("CHPID: %d\n",chpid1);
|                       int ppid=getpid();
|                       write (roura2[1],&ppid,sizeof(ppid));
|                       waitpid(chpid1,chstat,0);
|               }
|       } else {
|               printf("Nepodarilo se vytvorit potomka\n");
|               exit(1);
|       }
|       exit(0);
|}

V příkladu nejprve vytváříme dvě roury a pak je otvíráme. V případě, že se nepodařilo rouru správně otevřít, navracíme chybovou zprávu. Jinak pokračujeme rozdvojením do dvou procesů. Z rodiče posíláme do potomka jeho číslo pid a totéž se dějě i naopak mezi potomkem a rodičem. Čísla PID vytiskneme. Následně také zobrazíme seznam procesů pstree, abychom se přesvědčili, že jak fork(), tak exec() funguje. Dostaneme výpis podobný tomuto(pro přehlednost jsem jej zkrátil):

 
CHPID: 10879
PPID: 10878
|         -+= 10878 bartmann ./pipe 
|           --- 10879 bartmann pstree

Pokud chceme použít pro rouru standartní vstup a výstup můžeme využít jedné příjemné vlastnosti práce s deskriptory souboru a to, že se vžducky použije do nejnižší možné číslo. Takže když uzavřeme standartní vstup 0 a následně použijeme systémové volání dup(), které duplikuje desktriptor, máme jistotu, že se použije deskriptor 0. Totéž platí i u standardního výstupu 1 a standartního chybového výstupu 2. Následující příklad je modifikací toho předchozího, kdy se výstup z pstree posílá do roury. V rodiči ho přečteme a pak teprve vytiskneme na obrazovku. Abyste se o tom přesvědčili, zkuste si zakomentovat řádek printf("%s\n",pst); a žádný výpis se nezobrazí:

 
|#include <stdio.h>
|#include <stdlib.h>
|#include <unistd.h>
|#include <sys/types.h>
|#include <sys/wait.h>
|#include <ctype.h>
|int main (void) {
|       int fpid, ret;
|       int roura[2];
|       int roura2[2];
|       int * chstat;
|       ret=pipe(roura);
|       if (ret == -1) {
|               printf("Nepodarilo se vytvorit rouru\n");
|               exit(1);
|       }
|       ret=pipe(roura2);
|       if (ret == -1) {
|               printf("Nepodarilo se vytvorit rouru\n");
|               exit(1);
|       }
|
|       fpid=fork();
|       if (fpid != -1 ) {
|               if (fpid == 0) { //potomek
|                       int chpid=getpid();
|                       int ppid1;
|                       write (roura[1],&chpid,sizeof(chpid));
|                       read (roura2[0],&ppid1,sizeof(ppid1));
|                       printf("PPID: %d\n",ppid1);
|                       close (1);
|                       dup(roura[1]);
|                       execlp("pstree","pstree",0);
|               } else { // Rodic
|                       int chpid1;
|                       read (roura[0],&chpid1,sizeof(chpid1));
|                       printf("CHPID: %d\n",chpid1);
|                       int ppid=getpid();
|                       write (roura2[1],&ppid,sizeof(ppid));
|                       close (0);
|                       dup(roura[0]);
|                       char pst[10000];
|                       read(0,&pst,sizeof(pst));
|                       printf("%s\n",pst);
|
|                       ret=waitpid(chpid1,chstat,0);
|                       if (ret==-1) {
|                               printf("Chyba pi ukoncovani potomka\n");
|                       }
|               }
|       } else {
|               printf("Nepodarilo se vytvorit potomka\n");
|               exit(1);
|       }
|       exit(0);
|}

Komunikaci pomocí roury lze samozřejmě použít také u vláken, ovšm zde si musíme dát pozor na jednu věc. Vlákna totiž sdílí stejné deskriptory souborů, takže pokud bychom chtěli v jednom vláknu zavřít standardní výstup jako v příkladu výše, pak z jiného vlákna kýžený výstup nikdy nedostaneme. Na důkaz, že zavření vstupu/výstupu funguje je v následujícím příkladu uzavřen standartní vstup a místo toho je použit vstup z roury. Dokud standartní výstup znova neotevřeme, tak stále bude očekávat vstup jenom a pouze z přiřazé roury. Rovněž pokud ve vláknech použijeme exec, tak po spojení vláken je i původní vlákno nahrazeno execem(zkuste si pohrát se zakomentovanými řádky):

 
|#include <stdio.h>
|#include <stdlib.h>
|#include <unistd.h>
|#include <sys/types.h>
|#include <sys/wait.h>
|#include <ctype.h>
|#include <pthread.h>
|       
|int roura[2];
|void * child (void *par) {
|       printf("Tady bezi nove vlakno procesu %d\n",getpid());
|       char text[]="Zprava od noveho vlakna do roury";
|//     close(1);
|//     dup(roura[1]);
|       write(roura[1],text,sizeof(text));
|//     write(1,text,sizeof(text));
|//     execlp("ls","ls",0);
|       pthread_exit(0);
|}
|int main (void) {
|       int  ret;
|       char ztext[100];
|       pthread_t vlakno;
|       ret=pipe(roura);
|       if (ret == -1) {
|               printf("Nepodarilo se vytvorit rouru\n");
|               exit(1);
|       }
|       printf("Tady bezi puvodni vlakno procesu %d\n",getpid());
|       ret=pthread_create(&vlakno,NULL,child,NULL);
|       pthread_join(vlakno,0);
|       close (0);
|       dup(roura[0]);
|       char pst[10000];
|       read(0,&pst,sizeof(pst));
|       printf("%s\n",pst);
|       printf("Zadej nejaky text:\n");
|       fgets(ztext,100,stdin);
|       write(1,&ztext,sizeof(ztext));
|       exit(0);
|}

To by bylo pro dnešek všechno(všechny použité příklady najdete jako obvykle v sekci Ke stažení. Příště se podíváme na vyspělejší variantu rour a to na frontu zpráv.

Komentářů
Přidat Nový Hledat RSS
Přidat komentář
Jméno:
Email:
 
Website:
Název:
UBBKód:
[b] [i] [u] [url] [quote] [code] [img] 
 
 
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch:
:(:shock::X:side::):P:unsure::woohoo::huh::whistle:;):s
:!::?::idea::arrow:
 
Please input the anti-spam code that you can read in the image.

3.22 Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved."

 
Další >

Zapomenuté heslo
Nemáte účet? Vytvořte jej!
Skype: My status bartfil
Jabber: bartmann@rupyhost.cz

Doporučujeme

Příběhy psů z útulků