<stdio.h> <iostram> input. FILE *stdin. std::cin output. std::cout error. FILE *stdout. FILE *stderr

Podobne dokumenty
Łącza nienazwane(potoki) Łącza nienazwane mogą być używane tylko pomiędzy procesami ze sobą powiązanymi.

Łącza nienazwane(potoki)

Biblioteka standardowa - operacje wejścia/wyjścia

4.2 Sposób korzystania z l acza

Laboratorium Systemów Operacyjnych. Ćwiczenie 4. Operacje na plikach

Obsługa plików. Systemy Operacyjne 2 laboratorium. Mateusz Hołenko. 25 września 2011

Kolejki FIFO (łącza nazwane)

Ćwiczenie 4. Obsługa plików. Laboratorium Podstaw Informatyki. Kierunek Elektrotechnika. Laboratorium Podstaw Informatyki Strona 1.

Funkcje zawarte w bibliotece < io.h >

Instrukcja do laboratorium Systemów Operacyjnych (semestr drugi)

Pliki. Funkcje tworzące pliki i operujące na nich opisane są w części 2 pomocy systemowej. Tworzenie i otwieranie plików:

Funkcje zawarte w bibliotece < io.h >

Komunikacja za pomocą potoków. Tomasz Borzyszkowski

Obsługa plików. Laboratorium Podstaw Informatyki. Kierunek Elektrotechnika. Laboratorium Podstaw Informatyki Strona 1. Kraków 2013

Instrukcja do laboratorium Systemów Operacyjnych. (semestr drugi)

Programowanie proceduralne INP001210WL rok akademicki 2018/19 semestr letni. Wykład 6. Karol Tarnowski A-1 p.

Obsługa plików Procesy

Procesy. Systemy Operacyjne 2 laboratorium. Mateusz Hołenko. 9 października 2011

Operacje na plikach. Informatyka. Standardowe strumienie wejścia i wyjścia

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

Wskaźniki do funkcji. Wykład 11. Podstawy programowania ( język C ) Wskaźniki do funkcji (1) Wskaźniki do funkcji (2)

Tablice, funkcje - wprowadzenie

Wstęp do programowania INP001213Wcl rok akademicki 2017/18 semestr zimowy. Wykład 12. Karol Tarnowski A-1 p.

Laboratorium z systemów operacyjnych. System plików - funkcje systemowe. Anna Wojak

Pliki. Informacje ogólne. Obsługa plików w języku C

Pliki. Informacje ogólne. Obsługa plików w języku C

Programowanie w językach wysokiego poziomu

Poniższe funkcje opisane są w 2 i 3 części pomocy systemowej.

Języki i metodyka programowania. Typy, operatory, wyrażenia. Wejście i wyjście.

INFORMATYKA Studia Niestacjonarne Elektrotechnika

Systemy Operacyjne 1 Laboratorium 3 Potoki i łącza nazwane w Linuksie (jeden tydzień) dr inż. Arkadiusz Chrobot

Strumienie i pliki. Programowanie Proceduralne 1

ISO/ANSI C dostęp do plików ISO/ANSI C. ISO/ANSI C dostęp do plików. ISO/ANSI C dostęp do plików. ISO/ANSI C dostęp do plików

Temat zajęć: Obsługa łączy komunikacyjnych

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

Ghost in the machine

ISO/ANSI C dostęp do plików ISO/ANSI C. ISO/ANSI C dostęp do plików. ISO/ANSI C dostęp do plików. ISO/ANSI C dostęp do plików

2 Przygotował: mgr inż. Maciej Lasota

Programowanie proceduralne INP001210WL rok akademicki 2015/16 semestr letni. Wykład 6. Karol Tarnowski A-1 p.

ISO/ANSI C - funkcje. Funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje

UŻYCIE I ZARZĄDZANIE WĄTKAMI

SYSTEMY OPERACYJNE I laboratorium 3 (Informatyka stacjonarne 2 rok, semestr zimowy)

Aplikacja Sieciowa wątki po stronie klienta

Pliki. Operacje na plikach w Pascalu

Pliki wykład 2. Dorota Pylak

OPERACJE WEJŚCIA / WYJŚCIA. wysyła sformatowane dane do standardowego strumienia wyjściowego (stdout)

UŻYCIE I ZARZĄDZANIE WĄTKAMI

1 Podstawy c++ w pigułce.

Stałe i zmienne znakowe. Stała znakowa: znak

Procesy i potoki. S. Samolej: Procesy

Program wykonujący operację na plikach powinien zachować schemat działania zapewniający poprawną pracę:

Powłoka I. Popularne implementacje. W stylu sh (powłoki zdefiniowanej w POSIX) W stylu csh. bash (najpopularniejsza) zsh ksh mksh.

Podstawy programowania w języku C++

7 Przygotował: mgr inż. Maciej Lasota

4. Komunikacja pomiędzy procesami przez łącza nienazwane i nazwane

Plik jest reprezentowany przez strumień znaków (bajtów) o zmiennej długości. Koniec strumienia identyfikowany jest znacznikiem końca pliku EOF.

Podstawy programowania skrót z wykładów:

Formatowane (tekstowe) wejście/wyjście. Binarne wejście/wyjście.

Temat: Dynamiczne przydzielanie i zwalnianie pamięci. Struktura listy operacje wstawiania, wyszukiwania oraz usuwania danych.

J. Ułasiewicz Łącza nienazwane, nazwane, select 1

Uruchamianie programów w systemie Linux, potoki, strumienie, procesy, alias

Podstawy programowania w języku C++

Pliki w C/C++ Przykłady na podstawie materiałów dr T. Jeleniewskiego

Struktury. Przykład W8_1

Argumenty wywołania programu, operacje na plikach

Podstawy programowania, Poniedziałek , 8-10 Projekt, część 1

Programowanie Proceduralne

Wskaźniki w C. Anna Gogolińska

Część 4 życie programu

Języki programowania. Przetwarzanie plików amorficznych Konwencja języka C. Część siódma. Autorzy Tomasz Xięski Roman Simiński

1. Pierwszy program. Kompilator ignoruje komentarze; zadaniem komentarza jest bowiem wyjaśnienie programu człowiekowi.

KOLEJKI KOMUNIKATÓW IPC

Narzędzia informatyczne w językoznawstwie

Podstawy Informatyki. Inżynieria Ciepła, I rok. Wykład 10 Kurs C++

۰ Elementem jednostkowym takiego pliku jest bajt. ۰ Format pliku binarnego: [bajty pliku][eof]

PROE wykład 3 klasa string, przeciążanie funkcji, operatory. dr inż. Jacek Naruniec

Laboratorium 3: Tablice, tablice znaków i funkcje operujące na ciągach znaków. dr inż. Arkadiusz Chrobot dr inż. Grzegorz Łukawski

Stałe, znaki, łańcuchy znaków, wejście i wyjście sformatowane

Programowanie w językach

#include <stdio.h> int main( ) { int x = 10; long y = 20; double s; s = x + y; printf ( %s obliczen %d + %ld = %f, Wynik, x, y, s ); }

Lekcja 10. Uprawnienia. Dołączanie plików przy pomocy funkcji include() Sprawdzanie, czy plik istnieje przy pmocy funkcji file_exists()

Temat zajęć: Filtry, strumienie standardowe oraz przetwarzanie potokowe. stderr

Podstawy programowania. Wykład Pętle. Tablice. Krzysztof Banaś Podstawy programowania 1

Temat zajęć: Obsługa systemu plików.

Pobieranie argumentów wiersza polecenia

#include <stdio.h> void main(void) { int x = 10; long y = 20; double s; s = x + y; printf ( %s obliczen %d + %ld = %f, Wynik, x, y, s ); }

Funkcja (podprogram) void

Instrukcja do laboratorium Systemów Operacyjnych. (semestr drugi)

1. Wprowadzanie danych z klawiatury funkcja scanf

I - Microsoft Visual Studio C++

Języki programowania. Karolina Mikulska-Rumińska Pokój 573, tel Konsultacje wtorek 9-10.

1 Podstawy c++ w pigułce.

Ćwiczenie nr 6. Poprawne deklaracje takich zmiennych tekstowych mogą wyglądać tak:

1. Wartość, jaką odczytuje się z obszaru przydzielonego obiektowi to: a) I - wartość b) definicja obiektu c) typ oboektu d) p - wartość

Linux Kernel III. Character devices

Wstęp do Informatyki i Programowania Laboratorium: Lista 0 Środowisko programowania

Uwagi dotyczące notacji kodu! Moduły. Struktura modułu. Procedury. Opcje modułu (niektóre)

Języki i metodyka programowania. Wprowadzenie do języka C

Programowanie Współbieżne. W Linuxie/Unixie

Wykład PASCAL - Pliki tekstowe

Transkrypt:

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Rodzina systemów POSIX zaopatrzona została w mechanizm tworzenie międzyprocesowych łączy komunikacyjnych pomiędzy procesami, zwanych potokami: nienazwanymi a więc istniejącymi wyłącznie w pamięci jądra obiektów tymczasowych, tworzonymi obok otwieranych w momencie inicjowania procesu strumieni standard input, output i error; <unistd.h> <stdio.h> <iostram> input STDIN_FILENO FILE *stdin std::cin output STDOUT_FILENO FILE *stdout std::cout error STDERR_FILENO FILE *stderr std::cerr std::clog nazwanymi, czyli posiadających dowiązanie w systemie plików, do których odwołania następując explicite przez nazwę a czas ich istnienia nie jest ograniczony czasem wykonania procesu. Każdemu z tych strumieni system operacyjny przypisuje deskryptor pliku, który stanowi unikalna liczbą całkowitą 0, 1, 2, 3, itd choć trzy pierwsze są przypisane standardowym strumieniom wejściowemu, wyjściowemu i błędu. Służą one procesowi identyfikacji i odwołaniom, przy czym procesy potomne dziedziczą te deskryptory po procesach macierzystych. Liczba możliwych do wykorzystania przez proces deskryptorów, a więc i ilość otwartych plików jest ograniczona. 1

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. W gruncie rzeczy można byłoby się zawahać na ile w przypadku problematyki współbieżności, czy rozpatrywanej tu wieloprocesowości zasadne są takie rozwiązania. W końcu, każdy proces potomny stanowi dokładną kopię procesu macierzystego, czy w takim razie nie łatwiej uzyskać komunikację między procesową poprzez zdefiniowane już w obrębie kodu zmienne (chociażby globalne). Okazuje się jednak że całość nie przedstawia się aż tak prosto, co pokazuje wprost kolejny przykład. Rozważmy hipotetyczną sytuację, że w procesie głównym z pewnego powodu zaistniała potrzeba obliczania wartość całek funkcji jednej zmiennej. W rozpatrywanym konkretnie przypadku jest to całka 1 I = sin 2 x e x dx 0 której wartość, obliczona na drodze analitycznej wynosi I =2 1 e 1 1 4 2 = 0.09811971024 Równocześnie może pojawić się sytuacja, że wyrażenie podcałkowe stanowić będą także i inna funkcja. W zawiązku z czym, logicznym rozwiązaniem byłoby wprowadzenie dodatkowej funkcji, która umożliwi obliczenie wartości całki drogą kwadratury numerycznej. 2

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Postać jej deklaracji nagłówkowej można by przyjąć następująco double quad( unsigned int n,double a,double b, double (*fun)(double) ); gdzie: unsigned int n ilość węzłów kwadratury double a, a b granice całkowania double fun() wskazanie do funkcji stanowiącej wyrażenie podcałkowe Istnieje wiele skutecznych metod obliczania kwadratur numerycznych, gdyby przyjąć tutaj przykładowo metodę trapezów, to b a gdzie: f x dx b a 2n f x 0 2 f x 1 2 f x k 2 f x n 1 f x n x 0 =a ; x n =b; x k =a b a k, k =0,1,2,,n n 3

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Samą funkcję kwadratury numerycznej możemy więc zdefiniować jak niżej. double quad( unsigned int n, double a, double b, double (*fun)(double) ) { unsigned int k; double xk, sum; sum = fun( a ) + fun( b ); for( k=1;k<n;k++ ) { xk = a+ (b-a)*k/n; sum += 2.0*fun( xk ); return ( (b-a)/(2.0*n)*sum ); Ponieważ proces macierzysty ma również i wiele innych zadań do wykonania, obliczenia wartości będą przekazane do utworzonego w tym celu procesu potomnego. 4

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Kod źródłowy może przedstawiać się następująco. #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <math.h> int main( void ) { int pid,status; double sine( double ); double quad( unsigned int,double,double,double (*)(double)); unsigned int n; double a,b,i; Inicjujemy wartości początkowe zmiennych a=0.0; b=1.0; I=0.0; i jeszcze ilość węzłów kwadratury n=3200; 5

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Wywołaniem funkcji fork() duplikujemy bieżący proces i różnicujemy kod. switch( (int)fork() ) { case -1: perror( "<!> błąd inicjacji potomka" ); exit( 1 ); break; case 0: Obliczamy całkę, ale już w potomku I = quad( n,a,b,sine ); Dla pewności wyprowadzamy informację o tym co wyliczyliśmy printf( "[%d] wartość całki\t%16.6f\n",(int)getpid(),i );... i kończymy działanie potomka exit( 0 ); Teraz kod dla procesu nadrzędnego default: Powiedzmy, że coś tutaj ważnego się dzieje printf( "[%d] wykonuje ważne rzeczy...\n",(int)getpid() ); Oczekiwanie na wynik z potomka pid = (int)wait( &status );... i mamy gotowy rezultat printf( "[%d] zakończył z kodem %d\n",pid,status ); printf( "[%d] otrzymał wartość\t%16.6f\n",(int)getpid(),i ); Na tym program kończy działanie return 0; 6

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Oczywiście w kodzie musimy uzupełnić jeszcze definicję funkcji podcałkowej przedstawia się ona bardzo elementarnie double sine( double x ){ return sin( 2*M_PI*x )*exp(-x); Kompilacja i konsolidacja $ gcc -Wall integra.c -o integra -lm zwróćmy uwagę na dyrektywę -lm nakazującą konsolidację z biblioteką matematyczną libm.so a efekt wykonania [12401] wartość całki 0.098120 [12400] wykonuje, ważne rzeczy... [12401] zakończył z kodem 0 [12400] otrzymał wartość 0.000000 a więc nieco zaskakujący. Mimo, że utworzony proces potomny wywiązał się z zadania dobrze uzyskując bardzo dobre przybliżenie 0.098120 wobec wartości dokładnej (wyliczonej na drodze analitycznej) 0.09811971024 jednak proces nadrzędny tej wartości nie otrzymał, w jego przypadku wartość zmiennej I, pozostała równa wartości inicjującej. Stało się tak, ponieważ każdy proces posiadał własną, prywatną, kopię zmiennej, stąd i ich wartości końcowe są różne. Natomiast sama komunikacja międzyprocesowa nie przedstawia się aż tak elementarnie a wymaga specjalnych mechanizmów. 7

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Wróćmy w takim razie do kwestii użycia łączy na początek nienazwanych które mogą stanowić użyteczne narzędzia rozwiązania problemu komunikacji międzyprocesowej. Umożliwiają one asynchroniczną wymianę danych między pokrewnymi procesami a więc mającymi wspólnego przodka, bądź między bezpośrednio miedzy przodkiem a potomkiem (i odwrotnie). Jak wspomniano to już na wstępie, są realizowane jako obiekty tymczasowe w obszarze pamięci jądra, w ten sposób że dodawane są nowe pozycje do tablicy deskryptorów otwartych plików. OBSZAR PAMIĘCI JĄDRA proces łącze proces W obrębie danego łącza przepływ odbywa się zawsze w jednym kierunku (half duplex), w konsekwencji proces piszący musi na wstępie zamknąć odczyt a czytający zapis. Jeżeli komunikacja międzyprocesowa wymaga także kierunku zwrotnego, to konieczne jest otwarcie dwóch łączy. Łącza nienazwane nie są objęte standardem ISO/ANSI C lecz POSIX. De facto łącza nienazwane pipe stanowią wczesną postać UNIX System IPC (omawianego na późniejszych zajęciach). 8

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Komunikacja międzyprocesowa za pośrednictwem potoków może być realizowana zarówno z poziomu kodu źródłowego jak i poziomu instrukcji systemowej, a obecne było w systemach UNIX'opodobnych praktycznie od momentu ich powstania. Realizowane jest za pośrednictwem operatora przetwarzania potokowego, który łączy standardowe wyjście jednego procesu ze standardowym wejściem innego. Przykładowo $ ls -l /usr/bin/ cat -n tail -n 5 2386 -rwxr-xr-x 1 root root 3652 lis 25 2006 znew 2387 lrwxrwxrwx 1 root root 8 kwi 6 2007 zsh -> /bin/zsh 2388 -rwxr-xr-x 1 root root 27920 kwi 18 2007 zsoelim 2389 -rwxr-xr-x 1 root root 10904 lis 27 2006 zvbi-chains 2390 -rwxr-xr-x 1 root root 396688 lip 4 2008 zypper Użyto tutaj trzech instrukcji systemowych ls -l /usr/bin listing katalogu /usr/bin w formacie long cat -n wyświetla zawartość wejście dodając numerację linii tail -n 5 wyświetla 5 ostatnich linii ze strumienia na wejściu ustanawiające między ich standardowymi wyjściami a wejściami dwa łącza nienazwane (symbol ' '). 9

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Korzystając z API systemowego POSIX otwarcie łącza następuje wywołaniem funkcji pipe() zaś zamknięcie close(). Synopsis #include <unistd.h> int pipe( int fd[2] ); int fd[0] zawiera numer deskryptora do odczytu int fd[1] zawiera numer deskryptora do zapisu int close( int fd ); int fd numer deskryptora do zamknięcia Return 0 operacja zakończona powodzeniem Errors -1 Odczyt/zapis z/do potoku odbywa się za pośrednictwem pary funkcji read() i write(). Synopsis #include <unistd.h> ssize_t read( int fd, void *buf, size_t count ); ssize_t write( int fd, const void *buf, size_t count ); void* buf obszar pamięcie do odczytu/zapisu size_t count ilość bajtów do odczytu/zapisu Return ilość bajtów przeczytana / zapisana Errors -1 10

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Jako elementarną ilustrację przygotujemy program, który utworzy proces potomny, wysyłający do macierzystego zwrotną wiadomość. #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <linux/limits.h> int main( void ) { int fd[2],n; PIPE_BUF jest predefiniowaną stała dla bufora systemowego pipe() char line[pipe_buf]; Próbujemy ustanowić połączenie (nienazwane) if( pipe( fd )< 0 ) { printf( "...błąd otwarcia łącza\n" ); exit( 1 ); Tworzymy proces potomny switch( fork() ) { Na wypadek ewentualnego niepowodzenia case -1: perror( "<!> błąd inicjacji potomka" ); exit( 1 ); break; 11

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Jeżeli fork() zakończył się sukcesem, to dla procesu potomnego case 0: Na początek zamykamy kanał od strony odczytu close( fd[0] ); bowiem będziemy do niego pisać write( fd[1],"\n\t[pozdrowienia od potomka]\n\n",29 ); W zasadzie to zamknięcie jest zbędne close( fd[1] ); ponieważ po wykonaniu exit() system i tak zamknie kanał od strony potomka exit( 0 ); Natomiast kod dla procesu nadrzędnego default: Tu natomiast zamykamy zapis close( fd[1] ); Czytamy teraz to co wcześniej wysłał potomek n = read( fd[0],(void*)line,pipe_buf ); //close( fd[0] ); Wypisujemy co otrzymaliśmy od potomka na STDOT write( STDOUT_FILENO,line,n ); return 0; 12

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Zauważmy, że celem wyprowadzenia informacji wykorzystano tutaj jeden ze standardowo predefinowanych deskryptorów #define STDOUT_FILENO 1 /* Standard output. */ #define STDERR_FILENO 2 /* Standard error output. */ które otwierane są dla każdego procesu, w momencie jego tworzenia, przez system. Definicje znajdują się w pliku unistd.h. Rozmiar bufora zdefiniowano za pomocą stałej PIPE_BUF zdefiniowanej /linux/limits.h Jej wartość określa maksymalny rozmiar dla przesyłanej porcji danych potokiem nienazwanym, tak aby operacja ta była atomowa a więc niepodzielna. Zgodnie z definicją #define PIPE_BUF 4096 Obok niej istnieje także definicja stałej POSIX (posix1_lih.h), o identycznym sensie #define _POSIX_PIPE_BUF 512 choć innej (znacznie mniejszej) wartości. Inaczej niż zwykle, w kodzie procesu macierzystego nie użyto funkcji wait(). Było to możliwe ponieważ wywołanie read() ma charakter blokujący. Domniemanie to można zmienić wykorzystując funkcję fcntl() i ustawiając znacznik O_NONBLOC dla uzyskanego wcześniej deskryptora. Wymagałoby to jednak użycia funkcji wait(). 13

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Wracając teraz do problemu przedstawionego na wstępie potomka który wykonywał na rzecz procesu nadrzędnego obliczenia kod programu powinien się przedstawiać jak w listingu. #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <math.h> int main( void ) { int pid,status; double sine( double ); double quad( unsigned int, double, double,double (*)(double) ); unsigned int n; double a,b,i; int fd[2]; a=0.0,b=1.0,i=0.0; n=3200; if(pipe(fd)<0){ printf("...błąd otwarcia łącza\n"); exit(1); 14

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Teraz dopiero tworzymy proces potomny. switch( fork() ) { case -1: perror( "<!> błąd inicjacji potomka" ); exit( 1 ); break; case 0: close( fd[0] ); I = quad( n,a,b,sine ); printf( "[%d] wartość całki %19.6f\n",(int)getpid(),I ); write( fd[1],(void*)&i,sizeof( double ) ); exit( 0 ); default: close( fd[1] ); printf( "[%d] wykonuje, ważne rzeczy...\n",(int)getpid() ); read( fd[0],(void*)&i,sizeof( double ) ); printf( "[%d] zakończył z kodem %d\n",pid,status ); printf( "[%d] otrzymał wartość %16.6f\n",(int)getpid(),I ); return 0; Oczywiście funkcje sine() i quad() nie wymagają żadnych zmian. 15

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Efekt wykonania będzie następujący $ integral [4225] wartość całki 0.098120 [4224] wykonuje, ważne rzeczy... [4225] zakończył z kodem 0 [4224] otrzymał wartość 0.098120 a więc komunikacja między procesami przebiegła bez zakłóceń a wynik otrzymany przez proces nadrzędny poprawny. 16

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Ponieważ proces może utworzyć wiele potoków w postaci łączy nienazwanych, a z reguły (w większości implementacji) jak zaznaczono to na wstępie, są one jednokierunkowe, bardzo użytecznymi mogą okazać się funkcje dup() i dup2(). Synopsis #include <unistd.h> int dup( int old ); int dup2( int old, int new ); int old istniejący, utworzony uprzednio wywołaniem pipe(),, deskryptor int new nowy deskryptor na który zostanie skopiowany (skojarzony) old Return nowy deskryptor Errors -1 Tak więc efektem wywołanie obu funkcji będzie utworzenie kopii deskryptora, z tą różnicą że w przypadku dup2() mamy możliwość wskazania w sposób jawny na co skopiować. Funkcja dup() zwraca natomiast najniższy, pierwszy wolny. 17

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. W kolejnym przykładzie z procesu macierzystego wyślemy kilka linii tekstu, które w procesie potomnym zostaną posortowane za pomocą programu sort (systemowy). Przy okazji użyjemy tu w relacji do łącza nienazwanego funkcji przeznaczonych do działania na strumieniu skojarzonym z plikiem. #include <stdio.h> #include <stdlib.h> #include <sys/wait.h> #include <unistd.h> int main () { int fd[2]; int pid,status; Ta zmienna zostanie w przyszłości skojarzona z potokiem FILE* stream; if( pipe( fd )< 0 ) { printf( "...błąd otwarcia łącza\n" ); exit( 1 ); 18

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. W takim razie tworzymy proces potomny switch( (pid=(int)fork()) ) { case -1: perror( "<!> błąd inicjacji potomka" ); exit( 1 ); break; Kod dla potomka case 0: Na początek powitanie printf( "<!>\tpotomek [%d] startuje\n",(int)getpid() ); Zamykamy fd[1] bo potomek nie będzie pisał do potoku close( fd[1] ); Kopjujemy potomkowi fd[0] potoku na jego stdin dup2( fd[0],stdin_fileno ); Zamykamy fd[0], bo już niepotrzebne skopiowaliśmy na stdin close(fd[0]); Teraz pozostaje już tylko wywołać program sort printf("------------------------------------------\n" ); execl( "/usr/bin/sort","sort","--reverse",(char*)null ); Zauważmy że program uruchomiony przez execl() odziedziczył wejście stdin po potomku, a więc przekierowane fd[0]. 19

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Teraz kod dla procesu macierzystego. default: Zamykamy od tej strony kanału odczyt, bo będziemy pisać. close( fd[0] ); Przypisanie strumienia (plikowego) istniejącemu deskryptorowi. stream = fdopen( fd[1], "w" ); Piszemy do kanału, na końcu którego jest potomek (właściwie to sort). fprintf( stream, "\taaaaa\n" ); fprintf( stream, "\tbbbbb\n" ); fprintf( stream, "\tccccc\n" ); fprintf( stream, "\tddddd\n" ); Na wszelki wypadek opróżniamy bufor plikowy fflush( stream ); Zamykamy deskryptor, ponieważ nie jest dłużej potrzebny close( fd[1] ); i czekamy na potomka, aż skończy wait( &status ); Po zakończeniu wyświetlamy komunikat pintf("------------------------------------------\n" ); printf("<!>\tpotomek [%d] zakończył działanie i zwrócił [%d]\n",pid,status ); return 0; 20

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Efekt wykonania programu przedstawia się następująco: $./duplicate <!> potomek [8269] startuje ----------------------------------------------------------- Ddddd Ccccc Bbbbb Aaaaa ----------------------------------------------------------- <!> potomek [8269] zakończył działanie i zwrócił [0] $ Użyto tutaj celem skojarzenia deskryptora ze strumieniem - dodatkowo funkcji fdopen() i fflush(). #include <stdio.h> int fflush( FILE *stream ); FILE *fdopen( int fildes, const char *mode ); Tryb mode może być określony jako r (odczyt), r+ w+ (odczyt lub zapis), a (rozszerzenie, czyli pisanie na końcu pliku), a+ (rozszerzenie lub czytanie). 21

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Mechanizm łączy nienazwanych można z powodzeniem wykorzystać do komunikacji dwukierunkowej, między procesem nadrzędnym a potomnym. Kolejny przykład pokazuje tego rodzaju wariant komunikacji międzyprocesowej. Złóżmy, że proces nadrzędny prześle do procesu potomnego pewną wartość x oczekując na wykonanie na niej pewnej operacji f(x)=y a proces potomny zwróci do procesu nadrzędnego wynik tej operacji, czyli y. #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main( void ) { int pid,status; int one[2],two[2]; double x=1.0,y=1.0; printf( "\t[%d] nadrzędny, start\n\n",(int)getpid() ); Oczywiście, w przypadku komunikacji dwukierunkowej, konieczne są dwa kanały if( pipe( one )< 0 pipe( two )<0 ) { printf( "<!> błąd otwarcia łączy\n" ); exit( 1 ); 22

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. switch( pid=(int)fork() ) { Gdyby coś poszło nie tak case -1: perror( "<!> błąd inicjacji potomka" ); exit( 1 ); break; Teraz kod dla potomka case 0: Powitanie printf( "\t[%d] potomek, start\n\n",(int)getpid() ); Zamykamy niepotrzebne deskryptory, odpowiednio do kierunku przesyłu close( one[1] ); close( two[0] ); Czytamy to co nadrzędny ma nam do powiedzenia read( one[0],(void*)&x,sizeof( double ) ); printf( "\t[%d] otrzymał x=%f\n",(int)getpid(),x ); Wykonujemy właściwe operacje, na rzecz nadrzędnego y = x*m_pi; printf( "\t[%d] wykonał f(x)=y, wysyła y=%f\n",(int)getpid(),y ); no i w końcu wysyłamy wynik końcowy do nadrzędnego write( two[1],(void*)&y,sizeof( double ) ); printf( "\t[%d] potomek, stop\n\n",(int)getpid() ); exit( 0 ); 23

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. A teraz kod dla procesu nadrzędnego default: Oczywiście zamykamy to co nie jest nam potrzebne close( one[0] ); close( two[1] ); Wysyłamy dane do potomka printf( "\t[%d] wysyła do potomka [%d] x=%f\n\n",(int)getpid(),pid,x ); write( one[1],(void*)&x,sizeof( double ) ); no i czekamy na wynik do potomka read( two[0],(void*)&y,sizeof( double ) ); wait( &status ); printf( "\t[%d] potomek, zwrócił sterowanie, kod powrotu [%d]\n",pid,status ); informacja diagnostyczna printf( "\t[%d] otrzymał y=%f\n",(int)getpid(),y ); printf( "\n\t[%d] nadrzędny, stop\n",(int)getpid() ); Proces nadrzędny kończy ostatecznie działanie return 0; 24

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Efekt wykonanie programu przedstawia się następująco $ bidir [7810] nadrzędny, start [7811] potomek, start [7810] wysyła do potomka [7811] x=1.000000 [7811] otrzymał x=1.000000 [7811] wykonał f(x)=y, wysyła y=3.141593 [7811] potomek, stop [7811] potomek, zwrócił sterowanie, kod powrotu [0] [7810] otrzymał y=3.141593 [7810] nadrzędny, stop [7810] nadrzędny, stop $ 25

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Prócz ewidentnych zalet i korzyści wynikających z użycia w komunikacji międzyprocesowej łączy nienazwanych, mimo wszystko nie zawsze warto je stosować. Zasadniczym utrudnieniem w przypadku łączy nienazwanych jest kwestia współużytkowania takiego łącza przez procesy niespokrewnione. W takiej sytuacji zwykle lepszym wyborem będą łącza w postaci potoku nazwanego (named pipe). W odróżnieniu od łącze nienazwane: jest identyfikowane przez nazwę i może z niego korzystać (o ile ma odpowiednie prawa dostępu) wiele procesów (nawet nie spokrewnionych); posiada organizację FIFO, czyli First In First Out, stąd i ich skrótowa nazwa; posiada dowiązanie w systemie plików (jako plik specjalny urządzenia) aż do momentu ich jawnego usunięcia; mimo iż zachowuje cechy pliku de facto jak wyjaśnia to dokumentacja systemowa LINUX: is a window into the kernel memory that "looks" like a file. Podobnie jak i łącza nienazwane mogą być tworzone w dwojaki sposób: z poziomu systemowego interface'u użytkownika albo wywołaniem funkcji API z procesu (czy wątku). 26

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Z poziomu interface systemowego łącza nazwane FIFO tworzone są komendą $ mkfifo -m mode name mode maska praw dostępu, czyli symbolicznie dla u (user), g (group), o (other), a (all) dodaje (+),, ujmuje (-) od istniejących lub ustawia (=) prawo r (read), w (zapis), x (execute) name nazwa pliku specjalnego FIFO, ewentualnie wraz ze ścieżką zaś jego usunięcie odbywa się w identyczny sposób jak każdego pliku dyskowego, a więc $ rm name Utwórzmy w takim razie przykładowe łącze FIFO $ mkfifo a=rw /tmp/km-fifo Jeżeli wykonamy teraz $ ls -l /tmp/km-* prw-r--r-- 1 kmirota users 0 maj 22 12:09 /tmp/km-fifo Zwróćmy uwagę na opis dowiązania, zgodnie ze specyfikacją dla ls - Regular file l Symbolic link b Block special file n Network file c Character special file p FIFO d Directory s Socket czyli powstało FIFO pipe. 27

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Takie cechy "pliku" km-fifo potwierdza także, w szczegółach, komenda stat $ stat /tmp/km-fifo File: `/tmp/km-fifo` Size: 0 Blocks: 0 IO Block: 4096 potok Device: 806h/2054d Inode: 591930 Links: 1 Access: (0644/prw-r--r--) Uid:(1000/kmirota) Gid:(100/users) Access: 2003-05-22 12:35:52.000000000 +0200 Modify: 2003-05-22 12:35:51.000000000 +0200 Change: 2003-05-22 12:35:51.000000000 +0200 Otwórzmy teraz dwie sesje terminala, w oknie pierwszego pierwszego wpisujemy $ cat < /tmp/km-fifo czyli przekierujemy zawartość km-fifo na wejście komendy systemowej cat. Ta, jak wiadomo, wyświetla na swoim wyjściu (czyli tutaj w oknie terminala pierwszego), to co otrzyma na wejściu. Teraz przełączamy się na drugie okno terminala, i wykonujemy $ cat > /tmp/km-fifo czyli w przeciwnym kierunku, zatem wejście drugiego terminala zostało przekierowane na plik km-fifo. Efekt będzie taki, że cokolwiek wprowadzimy w oknie terminala drugiego, natychmiast zobaczymy w oknie pierwszego. Zauważmy że plik tem będzie miał przez cały czas rozmiar zerowy. Całość można zakończyć przesyłając do cat kod EOF (End Of File), czyli z klawiatury <Ctrl>-d. 28

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Jeżeli na pierwszym terminalu ponownie wykonamy przekierowanie $ cat < /tmp/km-fifo zaś na drugim przekierujemy do pliku km-fifo wykonanie jakiejkolwiek komendy, przykładowo $ stat /tmp/km-fifo >/tmp/km-fifo to na pierwszym zobaczymy oczywiście File: `/tmp/km-fifo' Size: 0 Blocks: 0 IO Block: 4096 potok Device: 806h/2054d Inode: 591930 Links: 1 Access: (0644/prw-r r--) Uid: (1000/kmirota) Gid:(100/users) Access: 2003-05-22 13:53:13.000000000 +0200 Modify: 2003-05-22 13:53:13.000000000 +0200 Change: 2003-05-22 13:53:13.000000000 +0200 $ i w tym momencie potok zostanie automatycznie zamknięty, bowiem strumień przepływających danych uległ zakończeniu. Zauważmy że po zakończeniu sesji plik pozostaje w katalogu, przez cały czas posiadając rozmiar zerowy. Usuwamy go jednak jak każdy plik $ rm /tmp/km-fifo 29

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. W systemach rodziny POSIX łącza nazwane tworzone są wywołaniami funkcji mkfifo(). Synopsis #include <sys/types.h> #include <sys/stat.h> int mkfifo( const char *pathname, mode_t mode ); const char *pathname nazwa tworzonego pliku (tu: łącza) mode_t mode maska uprawnień odnośnie odczytu i zapisu Return 0 jeżeli zakończone sukcesem Errors -1 Usuwane natomiast odbywa się przy pomocy funkcji unlink(). Synopsis #include <unistd.h> int unlink( const char *pathname ); const char *pathname nazwa usuwanego pliku (tu: łącza) Return 0 jeżeli zakończone sukcesem Errors -1 30

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Maska uprawnień może być zadana z wykorzystaniem sumy bitowej predefiniowanych stałych w pliku nagłówkowym sys/stat.h: S_IRUSR S_IWUSR S_IXUSR dla właściciela S_IRGRP S_IWGRP S_IXGRP dla grupy, do której należy właściciel S_IROTH S_IWOTH S_IXOTH dla pozostałych lub też korzystając z stałej DEFFILEMODE, dla wszystkich łącznie #define DEFFILEMODE (S_IRUSR S_IWUSR S_IRGRP S_IWGRP S_IROTH S_IWOTH) ustawiając dla wszystkich uprawnienia do odczyty i zapisu (zwróćmy uwagę, że na efektywne uprawnienia mają wpływ stosowane ustalenia odnośnie katalogu nadrzędnego). Ponieważ łącze nazwane posiada dowiązanie do struktury plików operacje na nim przeprowadza się w zwyczajowy sposób, tak jak i w przypadku ogółu, a więc: utworzone łącze trzeba otworzyć, przed użyciem, uzyskując w ten sposób jego deskryptor albo wskazanie do obiektu typu FILE; wykonujemy odczyt lub zapis, odpowiednio do potrzeb; po wykorzystaniu łącza proces powinien zamknąć je, od swojej strony. 31

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Niskopoziomowe operacje otwarcia i zamknięcie wykonujemy przy pomocy open() i close(). Synopsis #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open( const char *pathname, int flags ); const char *pathname nazwa otwieranego pliku (tu: łącza) int flags maska określająca sposób korzystania: O_RDONLY O_WRONLY O_RDWR tylko odczyt, tylko zapis, odczyt lub zapis O_NONBLOCK otwarcie w trybie nieblokującym Return deskryptor pliku Errors -1 Synopsis #include <unistd.h> int close( int fd ); int fd deskryptor pliku (tu: łącza nazwanego) Return 0 zakończona sukcesem Errors -1 32

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Przydzielając deskryptor łączu za pośrednictwem funkcji open() trzeba pamiętać, że jej wywołanie mają zasadniczo charakter blokujący, tzn. wywołanie fd=open( pathname,o_rdonly ); będzie czekać aż jakikolwiek proces nie otworzy kolejki do zapisu fd=open( pathname,o_wronly ); będzie czekać aż jakikolwiek proces nie otworzy kolejki do odczytu Jeżeli jednak otwarcie nastąpi z użyciem maski O_NONBLOCK wówczas fd=open( pathname,o_rdonly O_NONBLOCK ); zwróci natychmiast sterowanie fd=open( pathname,o_wronly O_NONBLOCK ); także zwróci natychmiast sterowanie, jeżeli jednak nie będzie żadnego procesu, który otworzył tę kolejkę do odczytu to zamiast deskryptora otrzymamy -1 (oraz errno = ENXIO) Dysponując deskryptorem, do odczytu i zapisu, używamy read() i write(). Synopsis #include <unistd.h> ssize_t read( int fd,void *buffer,size_t bytes ); ssize_t write( int fd,const void *buffer,size_t bytes ); int fd deskryptor pliku Return void* buffer bufor do odczytu lub zapisu ilość bajtów przesłanych size_t bytes ilość bajtów do przesłania Errors -1 33

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Na łączu nazwanym możemy także operować w sposób bezpośredni za pomocą wysokopoziomowych funkcji ISO C standard I/O. W pierwszej kolejności należy oczywiście otworzyć łącze wywołaniem fopen(). Synopsis #include <stdio.h> FILE *fopen( const char *pathname,const char *mode ); const char *pathname nazwa otwieranego pliku (tu: łącza) char *mode tryb otwarcia pliku: r (r+ r+) odczyt (i i zapis), w (w+ w+) zapis (i i odczyt), a (a+ a+) rozszerzenie (i i odczyt) Return wskazanie do strumienia plikowego FILE Errors NULL W końcu zaś zamknąć przy pomocy fclose(). Synopsis #include <stdio.h> int fclose( FILE *stream ); FILE *stream wskazanie do strumienia, do zamknięcie Return 0 jeżeli sukces Errors EOF 34

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Celem odczytu i zapis z i do strumienia plikowego stosuje się głównie funkcje formatowanego wejścia/wyjścia fscanf() i fprintf(). Synopsis #include <stdio.h> int fscanf( FILE *stream,const char *format,... ); int fprintf( FILE *stream,const char *format,... ); FILE *stream wskazanie do strumienia otwartego do odczytu const char *format łańcuch formatujący zawierający wzorce konwersji, w szczególności: "%c" znak, "%s" łańcuch, "%d" liczba całkowita, "%f" liczba rzeczywista... lista zmiennych Return ilość zmiennych wczytanych skutecznie, wg listy argumentów Errors EOF Całość operacji plikowych z czego tu przedstawiono tylko drobny wycinek stanowi element normy ISO/IEC 9899:1999 "Programming languages C" 35

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Wejście i wyjście plikowe, z punktu widzenie programisty, przedstawia się zwykle dość korzystnie i atrakcyjnie, jednak może przysporzyć także problemów. Między innymi za sprawą stosowanych tu mechanizmów buforowania. Stąd mogą pojawić się niezgodności tego co zawarte jest w skojarzonym buforze plikowym a wartościami zmiennych, póki strumień nie zostanie zamknięty. Przykładem tego rodzaju sytuacji jest poniższy kod. #include <stdio.h> int main( void ) { printf( "1 sek " ); sleep( 1 ); printf( "2 sek " ); sleep( 1 ); printf( "3 sek " ); sleep( 1 ); printf( "4 sek " ); sleep( 1 ); printf( "5 sek " ); sleep( 1 ); printf( "i koniec\n" ); return 0; 36

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Nie zobaczymy wcale serii komunikatów (choć zależy to od użytego kompilatora), pojawiających się co 1 sekundę, ale wszystkie równocześnie w momencie kiedy napotkany będzie znak końca linii '\n'. 1 sek 2 sek 3 sek 4 sek 5 sek i koniec Aby osiągnąć zamierzony efekt, wyświetlanego komunikatu co sekundę należałoby użyć funkcji fflush() opróżniającej bufor plikowy, tutaj standardowego wyjścia stdout. Synopsis #include <stdio.h> int fflush( FILE *stream ); FILE *steram bufor plikowy do opróżnienia (uprzednio otwarty) Return 0 jeżeli sukces Errors EOF Zatem w przypadku rozpatrywanego kodu konieczne jest jego uzupełnienie o wywołania fflush( stdout ); w sposób ja na załączonym dalej listingu. 37

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. #include <stdio.h> int main( void ) { printf( "1. " ); fflush( stdout); sleep( 1 ); printf( "2. " ); fflush( stdout); sleep( 1 ); printf( "3. " ); fflush( stdout); sleep( 1 ); printf( "4. " ); fflush( stdout); sleep( 1 ); printf( "5. " ); fflush( stdout); sleep( 1 ); printf( "i koniec\n" ); return 0; O samej funkcji warto pamiętać ponieważ problem buforów plikowych w odniesieniu do łączy komunikacyjnych bywa nad wraz uciążliwy, stając się przyczyną wielu zaskakujących wyników i frustracji. Na koniec należy nadmienić że o ile użycie fflush( stdout ) jest zgodne z normą ISO, to w odniesieniu do strumienia wejściowego fflush( stdin ) już nie jest. 38

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Problem buforowania po stronie odczytu można także rozwiązać używając celem wczytywania funkcji "nie zaśmiecającej" tak bufor plikowy jak scanf(). Mogłaby to być w tym przypadku fgets(), która wczytuje łańcuch tekstowy, a dopiero z niego za pomocą sscanf() wczytujemy potrzebne wartości zmiennych. Synopsis #include <stdio.h> char *fgets(char *buffer, int size, FILE *stream); char *buffer bufor odczytu int size ilość znaków do wczytania, pomniejszona o 1,, na ostatniej pozycji dodawane jest '\0' (funkcja( kończy czytanie jeżeli napotka EOF lub '\n') FILE *stream otwarty do odczytu bufor plikowy Return char *buffer jeżeli sukces, to wskazanie do wyniku Errors NULL Deklaracja i użycie funkcji int sscanf( const char *buffer,const char *format,... ); jest właściwie identyczne z fscanf(), w tym że odczyt nie następuje ze strumienia plikowego ale łańcucha const char *buffer bufor w postaci łańcucha tekstowego, z którego zawartość podlega konwersji, zgodnie z łańcuchem format 39

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Przygotujemy teraz dwie aplikacje, które poprzez łącze nazwane pipe będą wzajemnie się komunikować. Pierwsza stanowić będzie serwer nasłuchujący danych napływających łączem, a w przypadku odebrania wyświetli komunikat. #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <time.h> #include <unistd.h> int main( void ) { time_t stamp; pid_t pid; int fd,run; char cmd; printf("\n[%d]*s*e*r*w*e*r*[%d]\n\n",(int)getpid(),(int)getpid()); Na początek próbujemy otworzyć łącze do odczytu if( (fd=open( "pipe",o_rdonly )) >0 ){ run=1; else{ printf( "!.!..nie znaleziono łącza..!.!\n\n" ); run=0; 40

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. W przypadku kiedy udało się uzyskać deskryptor while( run ) { Odczytujemy, od kogo pochodzi wiadomość read( fd,&pid,sizeof( pid_t ) ); następie czytamy komendę read( fd,&cmd,sizeof( char ) ); Wyświetlamy informację, od kogo, co i kiedy otrzymano stamp = time( NULL ); printf( "[%d]\t %c -> %s",(int)pid,cmd,ctime( &stamp ) ); Jeżeli odebrano komendę Q(uit), to kończymy if( cmd=='q' ){ run=0; close( fd ); return 0; Teraz kod klienta, który będzie komunikował się z naszym serwerem przez łącze pipe. 41

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <ctype.h> int main( void ) { int fd,run; pid_t pid; char cmd; char buffer[256]; Wyświetlamy komunikat diagnostyczny pid = getpid(); printf( "\n[%d]*k*l*i*e*n*t*[%d]\n\n",(int)pid,(int)pid ); Podobnie jak wcześniej sprawdzamy dostępność łącza if( (fd=open( "pipe",o_wronly )) >0 ){ run=1; else{ printf( "!.!..nie znaleziono łącza..!.!\n\n" ); run=0; 42

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Jeżeli udało się uzyskać deskryptor łącza while( run ) { Pobieramy komendę od użytkownika printf( "\t?...\t" ); fgets( buffer,256,stdin ); sscanf( buffer,"%c",&cmd ); cmd = toupper( cmd ); Piszemy do łącza write( fd,&pid,sizeof( pid_t ) ); write( fd,&cmd,sizeof( char ) ); Jeżeli użytkownik podał Q(uit), to kończymy proces klienta if( cmd=='q' ){ run=0; close( fd ); return 0; 43

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Zanim uruchomimy procesy serwer i klienckie, najpierw musimy utworzyć łącze. Zatem $ mkfifo pipe Sprawdźmy jeszcze wynik komendy $ ls -l razem 32 -rwxr-xr-x 1 kmirota users 11667 maj 23 10:36 klient -rw-r--r-- 1 kmirota users 697 maj 23 10:36 klient.c prw-r--r-- 1 kmirota users 0 maj 23 10:37 pipe -rwxr-xr-x 1 kmirota users 11576 maj 23 10:36 serwer -rw-r--r-- 1 kmirota users 682 maj 23 10:36 serwer.c a więc łącze pipe zostało utworzone. Otwieramy teraz dwa terminale w pierwszym uruchamiamy proces serwera a w drugim (i ewentualnie kolejnych) procesy klienckie. $./klient [8053]*K*L*I*E*N*T*[8053]?...??...??...??... q $./serwer [8052] * S * E * R * W * E * R * [8052] [8053]? -> Fri Oct 23 11:07:48 2003 [8053]? -> Fri Oct 23 11:07:50 2003 [8053]? -> Fri Oct 23 11:07:51 2003 [8053] Q -> Fri Oct 23 11:07:54 2003 44

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Ponownie wykorzystując mechanizm łącza nazwanego przygotujemy serwer, który pozostając na końcu łącza zamknie je i będzie odsyłał zwrotnie cokolwiek dostanie. Kod źródłowy serwera przedstawia się jak na załączonym listingu. #include <stdio.h> #include <limits.h> int main( void ) { FILE *stream; char buffer[line_max]; int run; if( (stream = fopen( "channel","r+" ) ) ){ run=1; else{ run=0; perror( "!.!..błąd otwarcia łącza..!.!" ); while( run ) { if( fgets( buffer,256,stream ) ) { fprintf( stream,"%s",buffer ); fflush( stream ); return 0; 45

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Zwróćmy uwagą, że celem wyświetlenia informacji o ewentualnym błędzie otwarcia kanału, użyliśmy funkcji perror(), gdyż tylko ona gwarantuje że nawet w przypadku procesu pracującego w tle zobaczymy komunikat o błędzie. Początkowy fragment kodu klienta niewiele tylko różni się od przedstawionego wcześniej #include <stdio.h> #include <limits.h> #include <string.h> #include <ctype.h> int main( void ) { FILE *stream; char buffer[line_max]; int run; int empty( char* ); if( (stream = fopen( "channel","r+" ) ) ){ run=1; else{ run=0; perror( "!.!..błąd otwarcia łącza..!.!" ); 46

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. W dalszym fragmencie umieszczamy pętle czytającą i piszącą, do momentu kiedy użytkownik wprowadzi linię pustą. while( run ) { bzero( (void*)buffer,line_max ); fgets( buffer,line_max,stdin ); if(!empty( buffer ) ) { fprintf( stream,"%s",buffer ); fflush( stream ); else{ fclose( stream ); break; fgets( buffer,line_max,stream ); if(!empty( buffer ) ) { fprintf( stdout,"%s",buffer ); fflush( stream ); return 0; Pozostaje jeszcze dodać funkcję która będzie sprawdzać czy nie pojawił się łańcuch pusty (w naszym rozumieniu łańcuch nie zawierający ani jednej litery czy też cyfry). int empty( char * string ) { while( *string ){ if( isalnum(*string++) ){ return 0; return 1; 47

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Oczywiście na początek należy utworzyć właściwe łącze nazwane $ mkfifo channel i sprawdźmy $ stat channel File: `channel' Size: 0 Blocks: 0 IO Block: 4096 potok Device: 807h/2055d Inode: 4162321 Links: 1 Access: (0644/prw-r r--) Uid:(1000/kmirota) Gid: (100/users) Access: 2006-12-23 06:29:21.000000000 +0200 Modify: 2006-12-23 06:29:21.000000000 +0200 Change: 2006-12-23 06:29:21.000000000 +0200 Następnie uruchamiamy z bieżącego terminala proces serwera, w tle (zwróćmy uwagę na znak '&' na końcu linii komendy, który wywołuje właśnie takie uaktywnienie procesu przez system). $./serwer & [1] 8603 gdyby teraz sprawdzić listę aktywnych procesów $ ps PID TTY TIME CMD 4311 pts/1 00:00:00 bash 8603 pts/1 00:00:00 server 8607 pts/1 00:00:00 ps to jak proces o identyfikatorze 8603 pracuje nasz serwer. Jego działanie możemy w dowolnej chwili zakończyć, komendą (o ile nie będzie już potrzebny) $ kill -SIGKILL 8603 48

z przedmiotu, prowadzonych na Wydziale BMiI, Akademii Techniczno-Humanistycznej w Bielsku-Białej. Następnie z bieżącego terminala uruchamiamy klienta. Ponieważ serwer pracuje zamyka pętlę zwrotną, czyli odsyła to co otrzymał, więc cokolwiek wprowadzimy zostanie nam natychmiast odesłane przez serwer. $./client pierwszy pierwszy drugi drugi i jeszcze nieco dłuższy łańcuch, 1234567890 i jeszcze nieco dłuższy łańcuch, 1234567890 $ a więc, po wprowadzeniu linii pustej, klient zakończył. Pozostaje nam jeszcze zamknąć serwer. Tak jak sugerowano to wcześniej wykorzystamy w tym celu sygnał SIGKILL, zatem z terminala wykonujemy $ kill -SIGKILL 8603 49