1 używanie komend UNIXa z wnętrza programu napisanego w C#include
2 używanie komend UNIXa z wnętrza programu napisanego w Cexecl(char *path, char *arg0,...,char *argn, 0); path wskazuje na nazwę zbioru zawierającego komendę arg0 wskazuje na string który jest taki sam jak path (lub przynajmniej jego ostatni człon) Ostatni parametr musi być zero. main() { printf(”Zbiory: \n''); execl (”/bin/ls'', ”ls'', ”-l'', 0); } /* koniec funkcji main */
3 używanie komend UNIXa z wnętrza programu napisanego w Cint execv (const char *filename, char *const argv[]) Funkcja ta wykonuje plik o nazwie filename jako nowy proces (wykona fork); argument argv jest macierzą wskaźników; będzie ona argumentem argv w funkcji main tego nowego procesu. Ostatni element macierzy argv musi być wskaźnikiem zerowym (patrz execl). Pierwszy element macierzy argv musi być nazwą wykonywanego pliku ”bez kartoteki”.
4 używanie komend UNIXa z wnętrza programu napisanego w Cint execve (const char *filename, char *const argv[], char *const env[]) pozwala dodatkowo przesłać informację o otoczeniu procesu
5 używanie komend UNIXa z wnętrza programu napisanego w Cint execle (const char *filename, const char *arg0, char *const env[], ...) int execvp (const char *filename, char *const argv[]) int execlp (const char *filename, const char *arg0, ...)
6 fork #include
7 fork #include
8 #include
9 fork #include
10 wynik programu m=-1 n=0 status=134518208 getpid()=11430 n=0
11 wait int wait (int *status) – proces wołający jest zawieszony (”suspended”) dopóki proces potomny nie zakończy się. wait zwraca ID procesu-potomka lub –1 w przypadku błędu. ”exit status” potomka jest zwracany do miejsca pamięci wskazywanego przez wskaźnik status. uwaga: wait jest uproszczona wersją funkcji waitpid (-1, &status, 0)
12 waitpid #include
13 Inter Process Communication (IPC) - pipePodstawowym użyciem ”of pipes” czyli potoków jest przesyłanie lub otrzymywanie danych z programu uruchomionego jako proces potomny (”child process”). Można to zrobić albo wykorzystując ”Low Level piping”, czyli używając funkcji pipe (aby stworzyć pipe), fork (aby stworzyć proces potomny), dup2 (aby wymusić na procesie potomnym by używał dany pipe jako swój standard input lub standard output), exec (aby wykonać nowy program). Lub można skorzystać z ”High Level Piping” czyli funkcji popen oraz pclose
14 Inter Process Communication (IPC) - pipeFILE * popen (const char *command, const char *mode) ; int pclose (FILE *stream); popen uruchamia proces potomny. Nie czeka na zakończenie tego procesu, tworzy pipe do tego procesu i zwraca strumień odpowiadający temu pipe. Jeśli jako mode poda się ”r”, to można czytać ze standard output związanym z wykonującym się procesem potomnym. Analogicznie jeśli podać mode jako ”w”, to można pisać po standard input związanym z wykonującym się procesem potomnym. pclose zamyka to co popen otwarło
15 Inter Process Communication (IPC) - pipe#include
16 Inter Process Communication (IPC) - Low Level Pipeint pipe(int fd[2]) – tworzy pipe i zwraca dwa deskryptory do plików, fd[0], fd[1]. fd[0] jest otwarte dla czytania, fd[1] dla pisania. pipe() zwraca 0 w przypadku sukcesu, -1 w przypadku błędu Standardowy sposób użycia polega na tym, że zaraz po tym jak pipe jest utworzone, dwa (albo więcej) współdziałających procesów będzie stworzonych przez fork ; dane będą przesyłane używając read() oraz write() (można bez problemu używać także np.. fscanf, fgetc, fprintf, fputc, można przecież użyć fdopen dla uzyskania wskaźnika do struktury FILE) pipe otwarte przez pipe() można zamknąć przez close(int fd). Przykład: rodzic pisze do dziecka
17 Inter Process Communication (IPC) - Low Level Pipeint pdes[2]; pipe(pdes); if ( fork() == 0 ) { /* child */ close(pdes[1]); /* niepotrzebne */ read( pdes[0],......); /* czytaj od rodzica*/ } else { close(pdes[0]); /* niepotrzebne */ write( pdes[1].....); /* pisz do potomka */ ..... }
18 Low Level Pipe - przykład#include
19 Low Level Pipe - przykładvoid read_from_pipe (int file) /* czytaj znaki z "potoka" (czyli z pipe) i wypisuj je na stdout */ { int n=1; while( n>=0 ) { int n; char b; n=read(file,&b,1); if(n==0) {printf(" potomek robi break");break;} write(STDOUT_FILENO,&b,n); } printf("\n potomek zaraz konczy \n"); } /* koniec funkcji read_from_pipe*/
20 Low Level Pipe - przykładvoid write_to_pipe (int file) { char buf[30]=”Przesyłam pewien tekst, potem usnę, następnie zamknę"; write(file,buf,30); sleep(6); /* usypianie na 6 sekundy */ close (file); }
21 Low Level Pipe - przykładint main (void) { pid_t pid; int mypipe[2]; /* utwórz pipe */ if (pipe (mypipe)) fprintf (stderr, "Pipe zawiodlo!\n"); return EXIT_FAILURE; }
22 Low Level Pipe - przykład/* utworz proces pochodny */ pid = fork (); if (pid == (pid_t) 0) { /* to jest proces pochodny */ close (mypipe[1]); read_from_pipe (mypipe[0]); return EXIT_SUCCESS; }
23 Low Level Pipe - przykładelse if (pid < (pid_t) 0) /* nadal część dla procesu potomnego */ { /* fork zawiodl */ fprintf (stderr, "Fork zawiodl\n"); return EXIT_FAILURE; }
24 Low Level Pipe - przykładelse { /* to jest proces-rodzic czyli proces początkowy */ close (mypipe[0]); write_to_pipe (mypipe[1]); printf("\n Rodzic zaraz konczy\n"); return EXIT_SUCCESS; }
25 funkcja dup2() int dup (int old) Ta funkcja kopiuje deskryptor old na pierwszy wolny numer deskryptora (tzn. pierwszy nie otwarty) int dup2 (int old, int new) Ta funkcja kopiuje deskryptor old na deskryptor new. Zastosowanie: można zduplikować deskryptor pliku; zduplikowane deskryptory odwołują się do tego samego otwartego zbioru. Wskazują na tą samą pozycję wewnątrz zbioru. Zduplikowanie deskryptora umożliwia ”redirection of input or output”, czyli można zmienić zbiór czy potok (=”pipe”) do którego odnosi się dany deskryptor.
26 funkcja dup2() - przykład#include
27 funkcja dup2() - przykładindex=open("nic.dat",O_RDWR|O_TRUNC|O_CREAT,0664); /* do czytania i pisania; jeśli istnieje skróć plik do zera; jeśli plik nie istnieje to go utwórz */ k=write(index,buf,8); /* fp1=fdopen(index,"r+"); fprintf(fp1,"\nDodatkowa Linia\n"); */ fp1=fdopen(index,"w");
28 funkcja dup2() - przykładfprintf(fp1,"\nTo powinno zapisac sie w zbiorze nic.dat\n"); fflush(fp1); /* co jesli fflush() usuniete? */ printf("\n fileno(stdout)=%d\n za chwile dup2()\n", fileno(stdout) ); dup2(fileno(stdout), index); k=write(index,buf,8); fprintf(fp1,"\n To bedzie zapisane na stdout\n"); exit(0); } /* koniec main */
29 Inter Process Communication (IPC) - FIFOFIFO jest inaczej nazywane ”named pipe”. FIFO pojawia się jako specjalny zbiór w systemie zbiorów. Aby go stworzyć należy zawołać funkcje mkfifo(). Gdy plik FIFO jest utworzony, każdy proces może go otworzyć do czytania i pisania jak zwykły plik. Ale - musi być otwarty z obu końców. Otwarcie FIFO do czytania powoduje zawieszenie procesu, aż jakiś proces otworzy to samo FIFO do pisania, i odwrotnie. #include
30 Inter Process Communication (IPC) - FIFOFIFO (inaczej ”named pipe”) można utworzyć komendą systemu LINUX mkfifo ”nazwa zbioru” potem oczywiście można otworzyć taki potok z dwóch stron np. dwoma programami z których jeden czyta a drugi pisze przykład programu: users.uj.edu.pl/~ufrudy/forkop2.c ls –l nazwany_potok prwxr--r rudy users Jan 5 16:07 nazwany_potok
31 IPC - Sygnały Sygnały są programowo generowanymi przerwaniami przesyłanymi do procesu gdy zdarza się jakieś zdarzenie. W szczególności sygnał może być przesłany do procesu z innego procesu. Większość sygnałów powoduje zakończenie procesu do którego zostały przesłane, chyba że zostanie podjęta jakaś akcja w odpowiedzi na sygnał.Każdy sygnał ma jakąś określoną standardową (”default”) akcję, którą może być: przerwanie procesu, zastopowanie procesu itp..
32 IPC - Sygnały Sygnał przekazuje informację o zdarzeniu się czegoś wyjątkowego. Np.. że program dzieli przez zero lub że zażądał adresu pamięci spoza przydzielonego obszaru. Inne możliwości: żądanie użytkownika (procesu) dotyczące zatrzymania programu (procesu) lub przesłanie mu przerwania, tak by poinformowany proces mógł to przerwanie obsłużyć zakończenie procesu potomnego. Ogólnie, sygnały są ograniczoną ale bardzo użyteczną formą porozumiewania się procesów pomiędzy sobą (porozumiewanie typu ”look at me”)
33 IPC - Sygnały SIGHUP 1 /* hangup */ SIGSTOP 19/*zatrzymaj proces*/SIGINT 2 /* interrupt CTRL-c*/ SIGTERM 15 SIGQUIT 3 /* quit CTRL-\ */ SIGILL 4 /* illegal instruction */ SIGABRT 6 /* used by abort */ SIGKILL 9 /* hard kill */ SIGALRM 14 /* alarm clock */ SIGCONT 18 /* continue a stopped process */ SIGCHLD 20 /* to parent on child stop or exit */ Signals mogą mieć numery od 0 do 31
34 IPC - Sygnały Sygnały zdefiniowane w systemie należą do jednej z poniższych klas: sytuacje hardware’owe sytuacje software’owe kontrolowanie procesu (w tym WE/WY) kontrolowanie zasobów systemu
35 IPC – Sygnały; przykładowy program/* Przykład jak dwa procesy mogą rozmawiać */ /* używając kill() oraz signal() */ /* wykonamy fork(), rodzic wyśle kilka sygnałow do potomka */ #include
36 IPC – Sygnały; przykładowy programint main() { int pid; /* proces potomny */ if ((pid = fork()) < 0) { perror("fork"); exit(1); }
37 IPC – Sygnały; przykładowy programif (pid == 0) { /* potomek */ signal(11,sig11); signal(SIGHUP,sighup); signal(SIGINT,sigint); signal(SIGQUIT, sigquit); for(;;); /* wieczna pętla */ }
38 IPC – Sygnały; przykładowy programelse /* rodzic */ { /* pid zawiera ID potomka */ printf("\nPARENT: sending SIGHUP\n\n"); kill ( pid, SIGHUP); sleep(3); /* pause for 3 secs */ printf("\nPARENT: sending \n\n"); kill ( pid, 11); printf("\nPARENT: sending SIGINT\n\n"); kill ( pid, SIGINT); printf("\nPARENT: sending SIGQUIT\n\n"); kill (pid, SIGQUIT); sleep(3); }
39 IPC – Sygnały; przykładowy programvoid sig11() { printf("CHILD: I have received a SIGNAL=11\n"); } void sighup() { printf("CHILD: I have received a SIGHUP\n"); void sigint() { printf("CHILD: I have received a SIGINT\n"); void sigquit() { printf("Mój tata mnie zabił!!!\n"); exit(0); }
40 IPC – Sygnały Dostępne są dwie funkcje do wysyłania sygnałówint kill(int pid, int signal) wysłanie sygnału signal do procesu pid; jeśli pid jest większe niż zero, sygnał jest wysłany do procesu którego pid jest równe pid ; jeśli pid jest 0, sygnał zostanie wysłany do wszystkich procesów użytkownika; kill zwraca 0 w przypadku sukcesu, -1 w przypadku niepowodzenia.
41 IPC – Sygnały Dostępne są dwie funkcje do wysyłania sygnałówint raise (int sig) ; wysyła sygnał sig do wykonującego się procesu; raise() przy użyciu kill() wysyła sygnał komendą: kill( getpid(), sig); (istnieje komenda UNIX’a kill)
42 IPC – Sygnały Sygnały przyjmuje się przy użyciu funkcji signal() int (signal(int sig, void(*func) ( ) ) ) ( ) funkcja signal() w przypadku otrzymania sygnału sig zawoła funkcję func(); signal () zwraca wskaźnik do funkcji func w przypadku sukcesu oraz –1 w przypadku niepowodzenia func() może przyjmować trzy wartości: - SIG_DFL - SIG_IGN - napisana przez użytkownika funkcję
43 IPC – Sygnały Np. aby ignorować komendę CTRL/C można wywołać signal(SIGINT, SIG_IGN) (uwaga – nie można ignorować sygnału SIGKILL) aby SIGINT ponownie powodowało przerwanie programu/procesu należy wywołać signal(SIGINT, SIG_DFL)
44 IPC – Sygnały /* przykład programu z funkcjami obsługującymi sygnały */ #include
45 IPC – Sygnały main() { signal(SIGINT, sigproc); signal(SIGQUIT, quitproc); printf("ctrl-c nie działa użyj ctrl-\\ \n"); for(;;); /* pętla nieskończona */ }
46 IPC – Sygnały void sigproc() { signal(SIGINT, sigproc);/* Niektóre wersje UNIX’a znoszą obsługę sygnału po każdym zawołaniu. Dla pewności ponownie zgłaszana jest obsługa sygnału */ printf(”nacisnales ctrl-c\n"); }
47 IPC – Sygnały void quitproc(){ printf(”nacisnieto ctrl-\\ wychodze!! \n"); exit(0); /* normal exit */ }
48 IPC – Sygnały blokowanie/ignorowanieCzym się różni blokowanie sygnału od jego ignorowania? #include
49 IPC – Sygnały blokowanie/ignorowanieInne funkcje pracujące z sygnałami #include
50 IPC – Sygnały (zatrzymanie ,uruchomienie procesu potomnego)#include
51 IPC – Sygnały (zatrzymanie ,uruchomienie procesu potomnego)if (pid == 0) { /* potomek */ for(;;) /* wieczna pętla */ { printf("\n child: %d\n",getpid()); sleep(1); }
52 IPC – Sygnały (zatrzymanie ,uruchomienie procesu potomnego)else /* parent */ { /* pid zawiera ID potomka */ sleep(5); printf("\nPARENT: sending SIGSTOP\n\n"); kill(pid,SIGSTOP); /* zatrzymanie */ sleep(5); printf("\nPARENT: sending SIGCONT\n\n"); kill(pid,SIGCONT); /* uruchomienie */ sleep(5); printf("\nPARENT: sending SIGQUIT\n\n"); kill(pid,SIGQUIT); /* sygnal quit */ } }/* koniec main */
53 IPC – Sygnały (floating point exceptions)#include
54 IPC – Sygnały (floating point exceptions)//n=feenableexcept(FE_ALL_EXCEPT); n=feenableexcept(FE_DIVBYZERO); /* feenableexcept aktywuje przerwanie */ //n=fedisableexcept(FE_ALL_EXCEPT); //n=feenableexcept(FE_UNDERFLOW); printf("n=%d\n",n); printf(" %d\n",FE_ALL_EXCEPT); f1=1.0; f2=0.0; f1=f1/f2;
55 IPC – Sygnały (floating point exceptions)printf("\n f1=%f\n",f1); exit(0); }/* koniec funkcji main */ void sigfpe() /* obsługuje sygnał */ { printf("\n dzielenie przez zero!\n"); }
56 IPC – status zakończenia procesu (potomnego)Jeśli wartość zwracana przez proces potomny jest zero, to także wartość uzyskana poprzez waitpid() czy wait() jest równa zeru. Można otrzymać inne informacje zakodowane w uzyskanej wartości używając poniżej przedstawionych makr. Makra te są zdefiniowane w pliku sys/wait.h, który należy dołączyć poprzez #include < sys/wait.h> int WIFEXITED (int status) To makro zwraca wartość różną od zera jeśli proces potomny zakończy się normalnie wywołaniem exit() lub _exit() . int WEXITSTATUS (int status) Jeśli WIFEXITED(status) jest TRUE, makro to zwraca 8 bitów wartości zwracanej przez exit() w procesie potomnym int WIFSIGNALED (int status) Makro to zwraca wartość niezerową jeśli proces potomny zakończył się, gdyż otrzymał sygnał który nie został obsłużony (nie było odpowiedniego ”signal handling”)
57 IPC – status zakończenia procesu (potomnego)int WTERMSIG (int status) Jeśli WIFSIGNALED(status) jest TRUE, makro to zwraca numer sygnału który zakończył proces potomny. int WCOREDUMP (int status) To makro zwraca wartość niezerową, jeśli proces potomny zakończył się i wyprodukował ”core dump”. int WIFSTOPPED (int status) Powyższe makro zwraca wartość niezerową jeśli proces potomny jest zastopowany. int WSTOPSIG (int status) Jeśli WIFSTOPPED(status) jest TRUE, to makro zwraca numer sygnału który spowodował zastopowanie procesu potomnego.
58 IPC – status zakończenia procesu (potomnego)/* przykład zawołania WEXITSTATUS() */ #include
59 IPC – status zakończenia procesu (potomnego)/* przykład zawołania WEXITSTATUS() */ int main () { int n; n=fork(); if(n==0) { /* to potomek */ exit(5); }
60 IPC – status zakończenia procesu (potomnego)/* przykład zawołania WEXITSTATUS() */ else { /* to proces nadrzędny == rodzic */ int status; wait(&status); printf("\n potomek zwrocil status=%d\n",status); printf("\n czyli WEXITSTATUS=%d\n",WEXITSTATUS(status)); /* 5!! */ printf("\n program nadrzedny zaraz konczy prace...\n"); exit(0); } }/* koniec funkcji main */
61 IPC – funkcja pause() Jeśli chcemy, by program zatrzymał się, dopóki nie nadejdzie do niego jakis sygnał (który zdecyduje o jego dalszym losie), zazwyczaj używa się funkcji pause() #include
62 IPC – funkcja pause() /* Przyklad jak dwa procesy moga rozmawiac *//* uzywajac kill() oraz signal() */ /* wykonamy fork() */ #include
63 /* wykonanie fork() */ IPC – funkcja pause()if ((pid = fork()) < 0) { perror("fork"); exit(1); }
64 for(n=1;;++n) /* wieczna pętla / { signal(SIGCONT, sigcont); IPC – funkcja pause() if (pid == 0) { /* potomek */ for(n=1;;++n) /* wieczna pętla / { signal(SIGCONT, sigcont); printf("\n child: %d\n",getpid()); sleep(1); if(n==2) pause(); /* proces zatrzyma się, będzie czekał */ }
65 else /* parent czyli proces nadrzedny czyli rodzic */IPC – funkcja pause() else /* parent czyli proces nadrzedny czyli rodzic */ { /* pid zawiera ID potomka */ sleep(7); /* symulacja jakiegoś przetwarzania.... */ printf("\nPARENT: sending SIGCONT\n\n"); kill(pid,SIGCONT); /* uruchomienie */ sleep(7); } }/* koniec main */
66 IPC – funkcja pause() void sigcont() { printf("\n potomek: byl sygnal SIGCONT\n"); }/* koniec sigcont */ /* proces może zatrzymać czasowo jakiś inny proces, wysyłając do niego najpierw sygnał SIGSTOP, a po pewnym czasie może go obudzić wysyłając sygnał SIGCONT */
67 Zaawansowana obsługa sygnałów#include
68 Tworzenie zbioru sygnałów ”signal set”Wszystkie blokujące sygnały funkcje używają struktury danych nazwanej signal set w celu wyspecyfikowania o które sygnały chodzi. Używanie tych funkcji jest dwustopniowe: najpierw tworzy się ”signal set”, następnie przesyła się go jako argument do odpowiedniej funkcji (bibliotecznej). #include
69 ”signal set” Są dwa sposoby zainicjalizowania zbioru sygnałów. Można rozpocząć od sigemptyset(), to określa pusty zbiór sygnałów, a następnie dodawać poszczególne sygnały przez sigaddset() . Inny sposób to zainicjowanie zbioru sygnałów pełnym zbiorem istniejących możliwych sygnałów przez sigfillset() a następnie usuwanie poszczególnych sygnałow przez sigdelset() . Wysoce zalecane jest wybranie jednej z tych dwóch możliwości!
70 ”signal set” int sigemptyset (sigset_t *set)Incjalizuje zbiór sygnałów (pusty). Zawsze zwraca zero. int sigfillset (sigset_t *set) Inicjalizuje zbiór sygnałów (pełny). Zawsze zwraca zero. int sigaddset (sigset_t *set, int signum) Dodaje sygnał signum do zbioru sygnałów. Zwraca zero w przypadku sukcesu oraz –1 w przypadku niepowodzenia. int sigdelset (sigset_t *set, int signum) Usuwa sygnał signum ze zbioru sygnałów. Zwraca zero w przypadku sukcesu oraz –1 w przypadku niepowodzenia. int sigismember (const sigset_t *set, int signum) Sprawdza czy sygnał signum jest zawarty w zbiorze sygnałów. Zwraca 1 jeżęli sygnał jest w zbiorze sygnałow, 0 jeśli go nie ma, -1 jeśli był błąd.
71 ”signal set” int sigprocmask (int how, sigset_t * set, sigset_t * oldset) funkcja ta służy do zmiany maski sygnałów w danym procesie lub uzyskania informacji jaka jast ta maska. Argument how wybiera, jak funkcja działa. Możliwe wartości how : SIG_BLOCK Blokuj sygnały które są w set -- dodaj je do istniejącej maski. In other words, the new mask is the union of the existing mask and set. SIG_UNBLOCK Odblokuj sygnały które są w set – usuń je z istniejącej maski. SIG_SETMASK Sygnały z set tworzą nową maskę; ignore the previous value of the mask. (jeśli set jest NULL, maska procesu nie zostanie zmieniona, jedynie zostanie załadowany oldset informacją o bieżącej masce procesu.