Komunikacja międzyprocesowa - rury
Potok to medium komunikacyjne między dwoma lub większą liczbą powiązanych lub wzajemnie powiązanych procesów. Może to być w ramach jednego procesu lub komunikacji między dzieckiem a procesem nadrzędnym. Komunikacja może być również wielopoziomowa, np. Komunikacja między rodzicem, dzieckiem i wnukiem, itp. Komunikację uzyskuje się za pomocą jednego procesu wpisywania do potoku i innego odczytu z potoku. Aby wykonać wywołanie systemu potoków, utwórz dwa pliki, jeden do zapisu do pliku, a drugi do odczytu z pliku.
Mechanizm rurowy można oglądać w czasie rzeczywistym, na przykład wlewając wodę rurką do jakiegoś pojemnika, powiedzmy do wiadra, i ktoś ją odzyskuje, powiedzmy za pomocą kubka. Proces napełniania to nic innego jak zapisywanie w potoku, a proces odczytu to nic innego jak pobieranie z potoku. Oznacza to, że jedno wyjście (woda) jest wkładem dla drugiego (wiadro).
#include<unistd.h>
int pipe(int pipedes[2]);
To wywołanie systemowe utworzy potok do komunikacji jednokierunkowej, tj. Tworzy dwa deskryptory, pierwszy jest podłączony do odczytu z potoku, a drugi do zapisu w potoku.
Descriptor pipedes [0] służy do czytania, a pipedes [1] do pisania. Cokolwiek jest zapisane w potokach [1], można odczytać z potoków [0].
To wywołanie zwróci zero w przypadku sukcesu i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
Mimo że podstawowe operacje na pliku to odczyt i zapis, ważne jest, aby otworzyć plik przed wykonaniem operacji i zamknąć plik po zakończeniu wymaganych operacji. Zwykle domyślnie dla każdego procesu otwierane są 3 deskryptory, które są używane jako dane wejściowe (standardowe wejście - stdin), wyjście (standardowe wyjście - stdout) i błąd (standardowy błąd - stderr) mające odpowiednio deskryptory plików 0, 1 i 2.
To wywołanie systemowe zwróci deskryptor pliku używany do dalszych operacji na plikach odczytu / zapisu / wyszukiwania (lseek). Zazwyczaj deskryptory plików zaczynają się od 3 i zwiększają o jedną liczbę wraz z liczbą otwieranych plików.
Argumenty przekazywane do wywołania systemowego open to ścieżka (względna lub bezwzględna), flagi wskazujące na cel otwarcia pliku (powiedzmy, otwarcie do odczytu, O_RDONLY, do zapisu, O_WRONLY, do odczytu i zapisu, O_RDWR, do dołączenia do istniejącego pliku O_APPEND, aby utworzyć plik, jeśli nie istnieje z O_CREAT i tak dalej) i wymagany tryb zapewniający uprawnienia do odczytu / zapisu / wykonania dla użytkownika lub właściciela / grupy / innych. Tryb można wspomnieć za pomocą symboli.
Odczyt - 4, zapis - 2 i wykonanie - 1.
Na przykład: wartość ósemkowa (zaczyna się od 0), 0764 oznacza, że właściciel ma uprawnienia do odczytu, zapisu i wykonywania, grupa ma uprawnienia do odczytu i zapisu, a druga ma uprawnienia do odczytu. Można to również przedstawić jako S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH, co implikuje operację 0700 | 0040 | 0020 | 0004 → 0764.
To wywołanie systemowe, po pomyślnym zakończeniu, zwraca identyfikator nowego deskryptora pliku i -1 w przypadku błędu. Przyczynę błędu można zidentyfikować za pomocą zmiennej errno lub funkcji perror ().
#include<unistd.h>
int close(int fd)
Powyższe wywołanie systemowe zamykające już otwarty deskryptor pliku. Oznacza to, że plik nie jest już używany, a skojarzone z nim zasoby można ponownie wykorzystać w dowolnym innym procesie. To wywołanie systemowe zwraca zero w przypadku sukcesu i -1 w przypadku błędu. Przyczynę błędu można zidentyfikować za pomocą zmiennej errno lub funkcji perror ().
#include<unistd.h>
ssize_t read(int fd, void *buf, size_t count)
Powyższe wywołanie systemowe ma na celu odczytanie z podanego pliku argumentów deskryptora pliku fd, odpowiedniego bufora z przydzieloną pamięcią (statyczną lub dynamiczną) i wielkości bufora.
Identyfikator deskryptora pliku służy do identyfikacji odpowiedniego pliku, który jest zwracany po wywołaniu funkcji systemowej open () lub pipe (). Plik należy otworzyć przed wczytaniem z pliku. Otwiera się automatycznie w przypadku wywołania funkcji systemowej pipe ().
To wywołanie zwróci liczbę odczytanych bajtów (lub zero w przypadku napotkania końca pliku) po pomyślnym zakończeniu i -1 w przypadku niepowodzenia. Zwracane bajty mogą być mniejsze niż liczba żądanych bajtów, na wypadek gdyby żadne dane nie były dostępne lub plik został zamknięty. W przypadku awarii ustawiany jest prawidłowy numer błędu.
Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
#include<unistd.h>
ssize_t write(int fd, void *buf, size_t count)
Powyższe wywołanie systemowe polega na zapisaniu do podanego pliku argumentów deskryptora pliku fd, odpowiedniego bufora z przydzieloną pamięcią (statyczną lub dynamiczną) oraz wielkości bufora.
Identyfikator deskryptora pliku służy do identyfikacji odpowiedniego pliku, który jest zwracany po wywołaniu funkcji systemowej open () lub pipe ().
Plik należy otworzyć przed zapisaniem do pliku. Otwiera się automatycznie w przypadku wywołania funkcji systemowej pipe ().
To wywołanie zwróci liczbę zapisanych bajtów (lub zero, jeśli nic nie zostanie zapisane) po pomyślnym zakończeniu i -1 w przypadku niepowodzenia. W przypadku awarii ustawiany jest prawidłowy numer błędu.
Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
Przykładowe programy
Oto kilka przykładowych programów.
Example program 1 - Program do pisania i czytania dwóch wiadomości za pomocą potoku.
Algorytm
Step 1 - Stwórz rurę.
Step 2 - Wyślij wiadomość do rury.
Step 3 - Pobierz wiadomość z potoku i zapisz ją na standardowe wyjście.
Step 4 - Wyślij kolejną wiadomość do rury.
Step 5 - Pobierz wiadomość z potoku i zapisz ją na standardowe wyjście.
Note - Pobieranie wiadomości można również wykonać po wysłaniu wszystkich wiadomości.
Source Code: simplepipe.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds[2];
int returnstatus;
char writemessages[2][20]={"Hi", "Hello"};
char readmessage[20];
returnstatus = pipe(pipefds);
if (returnstatus == -1) {
printf("Unable to create pipe\n");
return 1;
}
printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Reading from pipe – Message 1 is %s\n", readmessage);
printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Reading from pipe – Message 2 is %s\n", readmessage);
return 0;
}
Note- W idealnym przypadku status zwrotu należy sprawdzać przy każdym wywołaniu systemowym. Aby uprościć proces, kontrole nie są przeprowadzane dla wszystkich połączeń.
Kroki wykonania
Kompilacja
gcc -o simplepipe simplepipe.c
Wykonanie / wyjście
Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell
Example program 2 - Program do pisania i odczytywania dwóch wiadomości przez potok przy użyciu procesu rodzica i procesu potomnego.
Algorytm
Step 1 - Stwórz rurę.
Step 2 - Utwórz proces potomny.
Step 3 - Proces nadrzędny zapisuje w potoku.
Step 4 - Proces potomny pobiera komunikat z potoku i zapisuje go na standardowe wyjście.
Step 5 - Powtórz krok 3 i krok 4 jeszcze raz.
Source Code: pipewithprocesses.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds[2];
int returnstatus;
int pid;
char writemessages[2][20]={"Hi", "Hello"};
char readmessage[20];
returnstatus = pipe(pipefds);
if (returnstatus == -1) {
printf("Unable to create pipe\n");
return 1;
}
pid = fork();
// Child process
if (pid == 0) {
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
} else { //Parent process
printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
}
return 0;
}
Kroki wykonania
Compilation
gcc pipewithprocesses.c –o pipewithprocesses
Execution
Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello
Dwukierunkowa komunikacja za pomocą potoków
Komunikacja potokowa jest postrzegana jako komunikacja jednokierunkowa, tj. Albo zapisuje proces nadrzędny, a proces potomny, lub odwrotnie, ale nie oba. A co jeśli rodzic i dziecko muszą jednocześnie pisać i czytać z potoków, rozwiązaniem jest dwukierunkowa komunikacja za pomocą potoków. Do nawiązania komunikacji dwukierunkowej potrzebne są dwie rury.
Oto kroki prowadzące do dwukierunkowej komunikacji -
Step 1- Utwórz dwie rury. Pierwsza jest dla rodzica do pisania, a dziecka do czytania, powiedzmy jako pipe1. Drugi jest przeznaczony dla dziecka do pisania, a rodzica do czytania, powiedzmy jako pipe2.
Step 2 - Utwórz proces potomny.
Step 3 - Zamknij niechciane końce, ponieważ dla każdej komunikacji potrzebny jest tylko jeden koniec.
Step 4 - Zamknij niechciane końce procesu macierzystego, przeczytaj koniec potoku1 i zapisz koniec potoku2.
Step 5 - Zamknij niechciane końce procesu potomnego, zapisz koniec potoku1 i przeczytaj koniec potoku2.
Step 6 - Przeprowadź komunikację zgodnie z wymaganiami.
Przykładowe programy
Sample program 1 - Osiągnięcie dwukierunkowej komunikacji za pomocą rur.
Algorytm
Step 1 - Utwórz potok1 dla procesu nadrzędnego do zapisu i dla procesu potomnego do odczytu.
Step 2 - Utwórz potok2 dla procesu potomnego do zapisu i dla procesu nadrzędnego do odczytu.
Step 3 - Zamknij niechciane końce rury od strony rodzica i dziecka.
Step 4 - Proces nadrzędny polegający na napisaniu wiadomości i proces potomny do odczytania i wyświetlenia na ekranie.
Step 5 - Proces podrzędny służący do pisania wiadomości i proces nadrzędny do czytania i wyświetlania na ekranie.
Source Code: twowayspipe.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds1[2], pipefds2[2];
int returnstatus1, returnstatus2;
int pid;
char pipe1writemessage[20] = "Hi";
char pipe2writemessage[20] = "Hello";
char readmessage[20];
returnstatus1 = pipe(pipefds1);
if (returnstatus1 == -1) {
printf("Unable to create pipe 1 \n");
return 1;
}
returnstatus2 = pipe(pipefds2);
if (returnstatus2 == -1) {
printf("Unable to create pipe 2 \n");
return 1;
}
pid = fork();
if (pid != 0) // Parent process {
close(pipefds1[0]); // Close the unwanted pipe1 read side
close(pipefds2[1]); // Close the unwanted pipe2 write side
printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
read(pipefds2[0], readmessage, sizeof(readmessage));
printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
} else { //child process
close(pipefds1[1]); // Close the unwanted pipe1 write side
close(pipefds2[0]); // Close the unwanted pipe2 read side
read(pipefds1[0], readmessage, sizeof(readmessage));
printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
}
return 0;
}
Kroki wykonania
Kompilacja
gcc twowayspipe.c –o twowayspipe
Wykonanie
In Parent: Writing to pipe 1 – Message is Hi
In Child: Reading from pipe 1 – Message is Hi
In Child: Writing to pipe 2 – Message is Hello
In Parent: Reading from pipe 2 – Message is Hello