Pokročilé
IPC v Linuxu
Exec a roura | Exec a roura |
|
|
|
| Zhlédnutí: 2118 | ||||||||
| 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. ExecFunkce 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> 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); |} RouraTeď 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> 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.
Powered by !JoomlaComment 3.22
3.22 Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved." |
||||||||
| Další > |
|---|