łącza nazwane

Download Report

Transcript łącza nazwane

Systemy operacyjne
(wiosna 2014)
Laboratorium 11 – tutorial
Komunikacja procesów
dr inż. Wojciech Bieniecki
Instytut Nauk Ekonomicznych
i Informatyki
http://wbieniec.kis.p.lodz.pl/pwsz
1
Metody komunikacji międzyprocesowej
Komunikacja międzyprocesowa (ang. Inter-Process Communication — IPC) – sposoby
komunikacji pomiędzy procesami systemu operacyjnego.
Pojęcie IPC może odnosić się do wymiany informacji w systemach rozproszonych (klastrów,
systemów odległych połączonych siecią).
Mechanizmy IPC opierają się na budowaniu w pamięci lub na dysku dynamicznych struktur,
używanych do transmisji komunikatów pomiędzy procesami
Lista metod IPC ta obejmuje:
pliki i blokady – najprostsza i najstarsza forma IPC
sygnały (ang. signals) – czasami znane jako przerwania programowe
semafory (ang. semaphores)
łącza nienazwane (ang. pipes) – znane też jako łącza komunikacyjne
łącza nazwane (ang. named pipes) – znane też jako nazwane łącza komunikacyjne
kolejki komunikatów (ang. message queues)
pamięć dzielona (ang. shared memory)
gniazda Uniksa (ang. Unix domain sockets)
gniazda (ang. sockets)
RPC (ang. Remote Procedure Call) – zdalne wywoływanie procedur.
2
Łącza komunikacyjne
Łącza w UNIX są plikami specjalnymi.
Są podobne do plików zwykłych – posiadają swój i-węzeł, posiadają bloki z danymi, na
otwartych łączach można wykonywać operacje zapisu i odczytu.
Czym różnią się od plików zwykłych
• ograniczona liczba bloków – łącza mają rozmiar 4KB – 8KB w zależności od
konkretnego systemu,
• dostęp sekwencyjny – na łączach można wykonywać tylko operacje zapisu i odczytu,
ale nie można wykonywać funkcji lseek)
• sposób wykonywania operacji zapisu i odczytu – dane odczytywane z łącza są
zarazem usuwane (nie można ich odczytać ponownie)
• proces jest blokowany w funkcji read na pustym łączu i w funkcji write, jeśli w łączu
nie ma wystarczającej ilości wolnego miejsca, żeby zmieścić zapisywany blok
Łącza komunikacyjne – cechy wspólne i różnice
Łącze nazwane (tzw kolejki FIFO).
Łącze nienazwane (tzw. potok)
Posiada dowiązanie w systemie plików
(istnieje jako plik w jakimś katalogu)
nie ma dowiązania w systemie plików –
istnieje tylko tak długo, jak jest otwarte
Może być identyfikowane przez nazwę
Jest identyfikowane tylko przez deskryptory
Po zamknięciu pozostaje przydzielony iwęzeł, ale wszystkie jego bloki na dysku są
zwalniane.
Po zamknięciu wszystkich jego
deskryptorów przestaje istnieć (zwalniany
jest jego i-węzeł i wszystkie bloki)
Procesy, które chcą komunikować się za pomocą łącza nienazwanego, muszą znać jego
deskryptor.
Sposobem przekazania deskryptora łącza innemu procesowi jest utworzenie go jako
proces potomny – tablica otwartych plików jest dziedziczona.
Można w procesie macierzystym otworzyć łącze a następnie utworzyć dwa procesy
potomne, które będą komunikować się ze sobą.
Operacje na łączach w UNIX
Łącza nazwane
mkfifo - funkcja systemowa tworząca łącze nazwane (działa podobnie jak creat dla plików
zwykłych, ale w przeciwieństwie do creat nie otwiera łącza)
int mkfifo (char* path, mode_t mode)
Argumenty funkcji:
path - nazwa ścieżkowa pliku specjalnego będącego kolejką FIFO
mode - prawa dostępu do łącza
Wartości zwracane:
poprawne wykonanie funkcji: 0
zakończenie błędne: -1
W przypadku błędnego zakończenie funkcji, kod błędu możemy odczytać w errno.
5
Operacje na łączach w UNIX
Łącza nazwane
open – otwiera łącze podobnie jak plik. Uwaga – aby łącze działało musi być otwarte do
odczytu i zapisu (np. jeden proces pisze drugi czyta)
int open (char* path, int flags)
Wartości zwracane:
poprawne wykonanie funkcji: deskryptor kolejki FIFO
zakończenie błędne: -1
Argumenty funkcji:
path - nazwa ścieżkowa pliku specjalnego będącego kolejką fifo
mode - prawa dostępu do łącza
flags - określenie trybu w jakim jest otwierana kolejka:
O_RDONLY - tryb tylko do odczytu
O_WRONLY- tryb tylko do zapis
6
Przykład użycia łącza nazwanego
Utworzone metodą mkfifo łącze musi zostać otwarte przez użycie funkcji open.
Funkcja ta musi zostać wywołana przynajmniej przez dwa procesy w sposób
komplementarny, tzn. jeden z nich musi otworzyć łącze do zapisu, a drugi do odczytu.
Odczyt i zapis danych z łącza nazwanego odbywa się za pomocą funkcji read i write
mkfifo("kolejka",0666);
desc = open("kolejka" , O_RDONLY);
desc = open("kolejka" , O_WRONLY);
read(desc);
write(desc);
close(desc);
close(desc);
unlink("kolejka");
7
Przykład użycia łącza nazwanego
#include <fcntl.h>
main(){
int fd;
if (mkfifo("/tmp/fifo", 0600) == -1)
exit(1); //Błąd tworzenia kolejki
switch(fork()){
case -1: // blad w tworzeniu procesu
exit(1);
case 0: //dotyczy procesu potomnego
fd = open("/tmp/fifo", O_WRONLY);
if (fd == -1)
exit(1); //Błąd otwarcia kolejki do zapisu
if (write(fd, "Witaj!", 7) == -1)
exit(1); //Błąd zapisu do kolejki
exit(0); //normalne zakończenie procesu potomnego
default: // dotyczy procesu macierzystego
char buf[10];
fd = open("/tmp/fifo", O_RDONLY);
if (fd == -1)
exit(1);
if (read(fd, buf, 10) == -1)
exit(1);
printf("Odczytano z potoku: %s\n", buf);
}
}
8
Użycie łączy nienazwanych
9
Funkcja pipe
int pipe(int fd[2])
Funkcja tworzy parę sprzężonych deskryptorów pliku, wskazujących na inode potoku i
umieszcza je w tablicy fd.
fd[0] – deskryptor potoku do odczytu
fd[1] – deskryptor potoku do zapisu
Proces, który utworzył potok może się przez niego komunikować tylko ze swoimi potomkami
lub przekazać im deskryptory, umożliwiając w ten sposób wzajemną komunikację.
Wartości zwracane:
poprawne wykonanie funkcji: 0
zakończenie błędne: -1
10
Przykład komunikacji przodek-potomek
main() {
int fd[2];
if (pipe(fd) == -1)
exit(1); // błąd tworzenia potoku
switch(fork()){ // rozwidlamy procesy
case -1: // blad w tworzeniu procesu
exit(1);
case 0: // proces potomny
if (write(fd[1], "Witaj!", 7) == -1)
exit(1); // błąd zapisu do potoku
exit(0); // kończymy proces potomny
default: // proces macierzysty
char buf[10];
if (read(fd[0], buf, 10) == -1)
exit(1); // błąd odczytu z potoku
printf("Odczytano z potoku: %s\n", buf);
}
}
11
Przykład użycia potoku
Działanie funkcji read w przypadku pustego potoku
main() {
int fd[2];
pipe(fd);
if (fork() == 0){ // proces potomny
write(fd[1], "witaj!", 7);
exit(0);
}
else { // proces macierzysty
char buf[10];
read(fd[0], buf, 10);
read(fd[0], buf, 10); //Uwaga tutaj!!!
printf("Odczytano z potoku: %s\n", buf);
}
}
Drugi odczyt spowoduje zawieszenie procesu, gdyż potok jest pusty, a proces
potomny ma otwarty deskryptor do zapisu.
12
Przykład użycia potoku
Niskopoziomowa realizacja ls|tr a-z A-Z z użyciem łączy nienazwanych
#define MAX 512 //będziemy przetwarzać po 512 znaków
main(int argc, char* argv[]) {
int fd[2];
pipe(fd) == -1); //Tworzenie potoku
if(fork() == 0)
{// proces potomny
dup2(fd[1], 1); // tworzymy drugie dowiązanie do stdout
execvp("ls", argv); // Uruchomienie programu ls
}
else
{ // proces macierzysty
char buf[MAX];
int lb, i;
close(fd[1]); // zamykamy możliwość zapisu do łącza
while ((lb=read(fd[0], buf, MAX)) > 0){ //czytamy
for(i=0; i<lb; i++)
buf[i] = toupper(buf[i]);
//konwertujemy znaki w pętli
write(1, buf,lb);
// zapisujemy znaki na wyjście
}
}
}
13
Przykład użycia potoku
wysokopoziomowa realizacja ls|tr a-z A-Z z użyciem łączy nienazwanych
main(int argc, char* argv[])
{
int fd[2];
pipe(fd);
if(fork() == 0)
{// proces potomny
dup2(fd[1], 1);
execvp("ls", argv);
}
else
{ // proces macierzysty
close(fd[1]);
dup2(fd[0], 0);
execlp("tr", "tr", "a-z", "A-Z", 0); //Uruchomienie tr
}
}
W przykładzie tym i poprzednim pominięto obsługę błędów.
14
Zadanie producenta-konsumenta
Jeden proces-producent generuje produkuje dane (zapisuje do łącza) a drugi proceskonsument pobiera je (czyta z potoku).
Potok jest wspólną zmienną która jest ograniczonym buforem. Może pojawić się problem
przepełnienia łącza lub gdy łącze jest puste.
15