Manual Lab5 by Skynet Komunikacja międzyprocesowa - cz. 2 Zakres przygotowania do zajęć: • •
UNIX IPC: kolejki komunikatów i pamięć dzielona. Polecenia systemowe: ipcs, ipcrm Funkcje: ftok, msgget, msgctl, msgsnd, msgrcv, shmget, shmctl, shmat, shmdt
Pozdro dla Fronia :D Mechanizmy IPC Podobnie jak łącza, mechanizmy IPC (Inter Process Communication) jest grupą mechanizmów komunikacji i synchronizacji procesów działających w ramach tego samego systemu operacyjnego. Mechanizmy IPC obejmują: • •
kolejki komunikatów — umożliwiają przekazywanie określonych porcji danych, pamięć współdzieloną — umożliwiają współdzielenie kilku procesom tego samego fragmentu wirtualnej przestrzeni adresowej, • semafory — umożliwiają synchronizacje procesów w dostępie do współdzielonych zasobów (np. do pamięci współdzielonej) Do tworzenia obiektów IPC i manipulowania ich danymi służy zbiór funkcji przedstawiony w poniższej tabelce:
działanie
kolejka komunikatów
pamięć współdzielona
semafor
Rezerwowanie obiektu IPC, uzyskiwanie do niego dostępu
msgget
shmget
semget
Sterowanie obiektem IPC, uzyskiwanie informacji o stanie modyfikowanych obiektów IPC, usuwanie obiektów IPC
msgctl
shmctl
semctl
shmat, shmdt
semop
Operacje na obiektach IPC: wysyłanie i odbieranie komunikatów, operacje na semaforach, rezerwowanie msgsnd, msgrcv i zwalnianie segmentów pamięci wspólnej
Wywołania systemowe get (msgget, shmget, semget) są stosowane do tworzenia nowych obiektów IPC, lub do uzyskania dostępu do obiektów istniejących. Drugim wywołaniem systemowym wspólnym dla mechanizmów IPC jest ctl (msgctl, shmctl, semctl), używane w celu przeprowadzenia operacji kontrolnych na obiektach IPC. Funkcje get zwracają wartości całkowitoliczbowe, nazywane identyfikatorami IPC, które identyfikują obiekty IPC. Od strony systemu operacyjnego identyfikator IPC jest indeksem w systemowej tablicy zawierającej struktury z danymi dotyczącymi uprawnień do obiektów IPC. Struktura IPC jest zdefiniowana w pliku sys/ipc.h. Każda z funkcji get wymaga określenia argumentu typu key_t (będącego ekwiwalentem typu long), nazywanego kluczem i umożliwiającego generowanie identyfikatorów IPC. Procesy, poprzez podanie tej samej wartości klucza uzyskują dostęp do konkretnego mechanizmu IPC. Wartość klucza można określić podając samodzielnie konkretną wartość. Polu temu można także przypisać stałą IPC_PRIVATE, która spowoduje utworzenie obiektu IPC o niepowtarzalnej wartości identyfikatora. Jednak łącze identyfikowane przez klucz wygenerowany w oparciu o stałą IPC_PRIVATE pozwala na komunikację jedynie pomiędzy procesami spokrewnionymi (ponieważ procesy potomne dziedziczą wartość klucza od swoich przodków)
Drugim parametrem wspólnym dla wszystkich wywołań z rodziny get jest znacznik komunikatu, który określa prawa dostępu do tworzonego obiektu IPC. Prawa te mogą być połączone operacją logiczną OR (oznaczaną w języku programowania C przez |) z flagami IPC_CREAT lub IPC_EXCL. Flaga IPC_CREAT nakazuje funkcjom get utworzenie nowego obiektu IPC, jeśli on jeszcze nie istnieje. Jeśli natomiast obiekt IPC już istnieje i jego klucz nie został wygenerowany przy użyciu stałej IPC_PRIVATE, to funkcje get zwrócą identyfikator tego obiektu. Natomiast użycie flag IPC_CREAT|IPC_EXCL spowoduje, że gdy obiekt IPC dla danej wartości klucza już istnieje, wywołanie funkcji get zakończy się błędem. Dzięki połączeniu tych dwóch flag użytkownik posiada gwarancję, że jest on twórcą danego obiektu IPC. Wywołania funkcji ctl posiadają dwa argumenty wspólne: identyfikator obiektu IPC (otrzymany w wyniku wywołania odpowiedniej funkcji get), oraz następujące stałe: IPC_STAT, IPC_SET i IPC_RMID, zdefiniowane w pliku sys/ipc.h: • • •
IPC_STAT — zwraca informację o stanie danego obiektu IPC IPC_SET — zmienia właściciela, grupę i tryb obiektu IPC IPC_RMID — usuwa obiekt IPC z systemu
Obsługa mechanizmów IPC w konsoli systemu Na poziomie systemowym dane znajdujące się w obiekcie IPC pobiera się za pomocą polecenia ipcs. Informacje na temat konkretnych obiektów: kolejek komunikatów, pamięci współdzielonej i semaforów otrzymamy stosując odpowiednio przełączniki -q , -m, -s. Czyli, by uzyskać informację nt. kolejki komunikatów o identyfikatorze msgid należy wydać polecenie: ipcs -q msgid Żeby uzyskać informację nt. segmentu pamięci współdzielonej o identyfikatorze shmid należy wydać polecenie: ipcs -m shmid Informację nt. zestawu semaforów o identyfikatorze semid zwraca polecenie: ipcs -s semid Dodatkowo przełącznik -b pozwala uzyskać informację nt. maksymalnego rozmiaru obiektów IPC, czyli ilości bajtów w kolejkach, rozmiarów segmentów pamięci współdzielonej i ilości semaforów w zestawach. Usunięcie obiektu IPC można natomiast wykonując polecenie systemowe ipcrm. Usunięcie kolejki komunikatów o identyfikatorze msgid realizuje polecenie: ipcrm -q msgid By usunąć segment pamięci współdzielonej o identyfikatorze shmid należy wpisać: ipcrm -m shmid Oraz usunięcie zestawu semaforów o identyfikatorze semid jest realizowane następująco: ipcrm -s semid Kolejki komunikatów, funkcje systemowe obsługujące kolejki komunikatów i ich argumenty Kolejki komunikatów umożliwiają przesyłanie pakietów danych, nazywanych komunikatami, pomiędzy różnymi procesami. Sam komunikat jest zbudowany jako struktura msgbuf, jego definicja znajduje się w pliku sys/msg.h: struct msgbuf{ long mtype; char mtext[1]; }
//typ komunikatu (>0) //treść komunikatu
Każdy komunikat ma określony typ i długość. Typ komunikatu, pozwalający określić rodzaj komunikatu, nadaje proces inicjujący komunikat. Komunikaty są umieszczane w kolejce w kolejności ich ich wysyłania. Nadawca
może wysyłać komunikaty, nawet wówczas gdy żaden z potencjalnych odbiorców nie jest gotów do ich odbioru. Komunikaty są w takich przypadkach buforowane w kolejce oczekiwania na odebranie. Przy odbiorze komunikatu, odbiorca może oczekiwać na pierwszy przybyły komunikat lub na pierwszy komunikat określonego typu. Komunikaty w kolejce są przechowywane nawet po zakończeniu procesu nadawcy, tak długo, aż nie zostaną odebrane lub kolejka nie zostanie zlikwidowana. Podczas tworzenia kolejki komunikatów tworzona jest systemowa struktura danych o nazwie msgid_ds. Definicję tej obsługiwanej przez system struktury można znaleźć w pliku nagłówkowym sys/msg.h. Funkcje umożliwiające komunikację za pomocą kolejek komunikatów zdefiniowane są w plikach sys/types.h, sys/ipc.h oraz sys/msg.h. # int msgget( key_t key, int msgflg ) Wartości zwracane: •
poprawne wykonanie funkcji: identyfikator kolejki komunikatów
•
zakończenie błędne: -1
Możliwe kody błędów (errno) w przypadku błędnego zakończenie funkcji: •
EACCES - Kolejka skojarzona z key, istnieje, ale proces wywołujący funkcję nie ma wystarczających praw dostępu do tej kolejki.
•
EEXIST - Kolejka skojarzona z wartością key istnieje a msgflg zawiera jednocześnie oba znaczniki IPC_CREAT i IPC_EXCL.
•
EIDRM - Kolejka została przeznaczona do usunięcia.
•
ENOENT - Kolejka skojarzona z wartością key nie istnieje, zaś msgflg nie zawiera flagi IPC_CREAT.
•
ENOMEM - Kolejka komunikatów powinna zostać utworzona, ale w systemie brak jest pamięci na utworzenie nowej struktury danych.
•
ENOSPC - Kolejka komunikatów powinna zostać utworzona, ale przekroczone zostałoby systemowe ograniczenie (MSGMNI) na ilość istniejących kolejek komunikatów.
Argumenty funkcji: •
key - liczba, która identyfikuje kolejkę
•
msgflg - sumą bitowa stałej IPC_CREAT i dziewięciu bitów określających prawa dostępu do kolejki.
UWAGI: IPC_PRIVATE nie jest flagą tylko szczególną wartością key_t. Jeśli wartość ta zostanie użyta jako wartość klucza, to system uwzględni jedynie bity uprawnień parametru msgflg i zawsze będzie próbować utworzyć nową kolejkę. Istnienie flag IPC_CREAT i IPC_EXCL w parametrze msgflg znaczy tyle samo w przypadku kolejki komunikatów, co istnienie flag O_CREAT i O_EXCL w argumencie mode wywołania open , tzn. funkcja msgget nie wykona się prawidłowo jeśli msgflg będzie zawierać flagi IPC_CREAT i IPC_EXCL, zaś kolejka komunikatów skojarzona z kluczem key już będzie istnieć. # int msgsnd( int msgid, struct msgbuf *msgp, int msgs, int msgflg ) Wartości zwracane: •
poprawne wykonanie funkcji: 0
•
zakończenie błędne: -1
Możliwe kody błędów (errno) w przypadku błędnego zakończenie funkcji: •
EACCES - kolejka skojarzona z wartością key, istnieje, ale proces wołający funkcję nie ma wystarczających praw dostępu do kolejki
•
EAGAIN - kolejka jest pełna, a flaga IPC_NOWAIT była ustawiona
•
EFAULT - niepoprawny wskaźnik msgq
•
EIDRM - kolejka została przeznaczona do usunięcia
•
EINTR - otrzymano sygnał podczas oczekiwania na operację zapisu
•
EINVAL - niepoprawny identyfikator kolejki, lub ujemny typ wiadomości, lub nieprawidłowy rozmiar wiadomości
Argumenty funkcji: •
msgid - identyfikator kolejki komunikatów
•
msgp - wskaźnik na komunikat do wysłania
•
msgs - rozmiar właściwej treści komunikatu (w bajtach)
•
msgflg - flagi specyfikujące zachowanie się funkcji w warunkach nietypowych. Wartość ta może być ustawiona na 0 lub IPC_NOWAIT
UWAGI: Jeśli w momencie wysyłania komunikatu system osiągnął już limit długości kolejki, to w zależności of wartości flagi msgflg funkcja nie wyśle komunikatu i powróci z funkcji msgsnd (dla msgflg = IPC_NOWAIT), lub zablokuje proces wywołujący aż do chwili, gdy w kolejce będzie wystarczająco dużo wolnego miejsca, by żądany komunikat mógł być wysłany (przy msgflg=0). Treść wysyłanego komunikatu w rzeczywistości może mieć dowolną strukturę. Struktura ta, zanim zostanie wysłana, musi być wcześniej zarezerwowane miejsce w pamięci. Pole mtype określa typ komunikatu, dzięki czemu możliwe jest przy odbiorze wybieranie z kolejki komunikatów określonego rodzaju. Typ komunikatu musi być wartością większą od 0. #int msgrcv ( int msgid, struct msgbuf *msgp, int msgs, long msgtyp, int msgflg ) Wartości zwracane: •
poprawne wykonanie funkcji: ilość odebranych bajtów
•
zakończenie błędne: -1
Możliwe kody błędów (errno) w przypadku błędnego zakończenie funkcji - analogicznie do funkcji msgsnd Argumenty funkcji: •
msgid - identyfikator kolejki komunikatów
•
msgp - wskaźnik do obszaru pamięci w którym ma zostać umieszczony pobrany komunikat
•
msgs - rozmiar właściwej treści komunikatu
•
msgtyp - określa typ komunikatu który ma być odebrany z kolejki. Możliwe są następujące wartości zmiennej msgtyp: • msgtyp > 0 - pobierany jest pierwszy komunikat typu msgtyp • msgtyp < 0 - pobierany jest pierwszy komunikat którego wartość typu jest mniejsza lub równa wartości msgtyp • msgtyp = 0 - typ komunikatu nie jest brany pod uwagę, tzn. funkcja pobiera pierwszy komunikat dowolnego typu
•
msgflg - flaga specyfikujące zachowanie się funkcji w warunkach nietypowych. Wartość ta może być ustawiona na 0, IPC_NOWAIT lub MSG_NOERROR
UWAGI: Odebranie komunikatu oznacza pobranie go z kolejki, czyli raz odebrany komunikat nie może zostać odebrany ponownie. Argument msgflg określa czynność, która jest wykonywana, gdy żądanego komunikatu nie ma w kolejce, lub miejsce przygotowane do odebrania komunikatu jest niewystarczające. Gdy wartością msgflg
jest IPC_NOWAIT, funkcja przy żądaniu odbioru komunikatu, którego nie ma w kolejce nie będzie blokowała wywołującego ją procesu. Z kolei flaga MSG_NOERROR powoduje odpowiednie obcinanie rozmiaru komunikatu za dużego, by go odebrać. W przypadku, gdy flaga MSG_NOERROR nie jest ustawiona i otrzymany jest za długi komunikat, to funkcja zakończy się błędem. Gdy nie ma znaczenia fakt, czy komunikaty mają być obcinane czy nie, flagę msgflg należy ustawić na 0. #int msgctl( int msgid, int cmd, struct msgid_ds. *buf ) Wartości zwracane: •
poprawne wykonanie funkcji: 0
•
zakończenie błędne: -1
Możliwe kody błędów (errno) w przypadku błędnego zakończenie funkcji: •
EACCES - nie ma praw do odczytu oraz cmd jest ustawiona na IPC_STAT
•
EFAULT - adres wskazywany przez buf jest nieprawidłowy
•
EIDRM - kolejka została usunięta
•
EINVAL - msgqid nieprawidłowe lub msgsz mniejsze od 0
•
EPERM - komendy IPC_SET lub IPC_RMID zostały wydane podczas gdy proces nie ma praw dostępu do zapisu
Argumenty funkcji: •
msgid - identyfikator kolejki
•
cmd - stała specyfikująca rodzaj operacji • cmd = IPC_STAT - pozwala uzyskać informację o stanie kolejki komunikatów • cmd = IPC_SET - pozwala zmienić związane z kolejką ograniczenia • cmd = IPC_RMID - pozwala usunąć kolejkę z systemu
•
buf - wskaźnik na zmienną strukturalną przez którą przekazywane są parametry operacji
UWAGI: Funkcja służy do zarządzania kolejką (np. usuwania kolejki, zmiany praw dostępu itp.) Przykładowe programy obsługujące kolejki komunikatów Listingi 1 i 2 przedstawiają rozwiązanie problemu producenta i konsumenta (odpowiednio program producenta i program konsumenta) na kolejce komunikatów. W rozwiązaniu zakłada się ograniczone buforowanie, tzn. nie może być więcej nieskonsumowanych elementów, niż pewna założona ilość (pojemność bufora). Rozwiązanie dopuszcza możliwość istnienia wielu producentów i wielu konsumentów.
3
#include #include #include #include
#define MAX 10 6
struct buf_elem { long mtype; 9 int mvalue; }; #define PUSTY 1 12 #define PELNY 2 15
main(){ int msgid, i;
struct buf_elem elem; 18
msgid = msgget(45281, IPC_CREAT|IPC_EXCL|0600); if (msgid == -1){ msgid = msgget(45281, IPC_CREAT|0600); if (msgid == -1){ perror("Utworzenie kolejki komunikatow"); exit(1); } } else{ elem.mtype = PUSTY; for (i=0; i
21 24 27 30 33
for (i=0; i<10000; i++){ if (msgrcv(msgid,&elem,sizeof(elem.mvalue),PUSTY, 0)== -1){ perror("Odebranie pustego komunikatu"); exit(1); } elem.mvalue = i; elem.mtype = PELNY; if (msgsnd(msgid, &elem, sizeof(elem.mvalue), 0) == -1){ perror("Wyslanie elementu"); exit(1); } }
36 39 42 45 }
Listing 1: Impelmentacja zapisu ograniczonego bufora za pomocą kolejki komunikatów Opis programu: W linii 18 jest próba utworzenia kolejki komunikatów. Jeśli kolejka już istnieje, funkcja msgget zwróci wartość -1 i nastąpi pobranie identyfikatora już istniejącej kolejki (linia 20). Jeśli kolejka nie istnieje, zostanie ona utworzona w linii 18 i nastąpi wykonanie fragmentu programu w liniach 27-32, w wyniku czego w kolejce zostanie umieszczonych MAX komunikatów typu PUSTY. Umieszczenie elementu w buforze, reprezentowanym przez kolejkę komunikatów, polega na zastąpieniu komunikatu typu PUSTY komunikatem typu PELNY. Pusty komunikat jest pobierany w linii 36. Brak pustych komunikatów oznacza całkowite zapełnienie bufora i powoduje zablokowanie procesu w funkcji msgrcv. Po odebraniu pustego komunikatu przygotowywany jest komunikat pełny (linie 40-41) i umieszczany jest w buforze, czyli wysyłany do kolejki (linia 42).
3
#include #include #include #include
#define MAX 10 6 9 12 15
struct buf_elem { long mtype; int mvalue; }; #define PUSTY 1 #define PELNY 2 main(){ int msgid, i; struct buf_elem elem;
18
msgid = msgget(45281, IPC_CREAT|IPC_EXCL|0600); if (msgid == -1){ msgid = msgget(45281, IPC_CREAT|0600); if (msgid == -1){ perror("Utworzenie kolejki komunikatów"); exit(1); } } else{ elem.mtype = PUSTY; for (i=0; i
21 24 27 30 33 36 39 42 45 }
for (i=0; i<10000; i++){ if (msgrcv(msgid,&elem,sizeof(elem.mvalue),PELNY,0) == -1){ perror("Odebranie elementu"); exit(1); } printf("Numer: %5d Wartosc: %5d\n", elem.mvalue); elem.mtype = PUSTY; if (msgsnd(msgid, &elem, sizeof(elem.mvalue), 0) == -1){ perror("Wyslanie pustego komunikatu"); exit(1); } }
Listing 2: Impelmentacja zapisu ograniczonego bufora za pomocą kolejki komunikatów Opis programu: Analogicznie do poprzedniego przykładu, z tym, że umieszczenie elementu w buforze, reprezentowanym przez kolejkę komunikatów, polega na zastąpieniu komunikatu typu PELNY komunikatem typu PUSTY. Pełny komunikat jest pobierany w linii 36. Mechanizmy IPC: pamięć współdzielona. Pamięć współdzielona. Pamięć współdzielona jest specjalnie utworzonym segmentem wirtualnej przestrzeni adresowej, do którego dostęp może mieć wiele procesów. Jest to najszybszy sposób komunikacji pomiędzy procesami. Podstawowy schemat korzystania z pamięci współdzielonej wygląda następująco: jeden z procesów tworzy segment pamięci współdzielonej, dowiązuje go powodując jego odwzorowanie w bieżący obszar danych procesu, opcjonalnie zapisuje w stworzonym segmencie dane. Następnie, w zależności od praw dostępu inne procesy mogą odczytywać i/lub zapisywać wartości w pamięci współdzielonej. Każdy proces uzyskuje dostęp do pamięci współdzielonej względem miejsca wyznaczonego przez jego adres dowiązania, stąd każdy proces korzystając z tych samych danych używa innego adresu dowiązania. W przypadku współbieżnie działających procesów konieczne jest najczęściej synchronizowanie dostępu np. za pomocą semaforów. Kończąc korzystanie z segmentu pamięci proces może ten segment odwiązać, czyli usunąć jego dowiązanie. Kiedy wszystkie procesy zakończą korzystanie z segmentu pamięci współdzielonej, za jego usunięcie najczęściej odpowiedzialny jest proces, który segment utworzył. Podczas tworzenia segmentu pamięci współdzielonej tworzona jest systemowa struktura danych o nazwie shmid_ds. Definicję tej obsługiwanej przez system struktury można znaleźć w pliku nagłówkowym < sys/shm.h>. Funkcje operujące na pamięci współdzielonej zdefiniowane są w plikach: < sys/ipc.h> i < sys/shm.h>.
Funkcje systemowe obsługujące pamięć współdzieloną i ich argumenty.
int shmget ( key_t key, size_t size, int shmflags ) Wartości zwracane: • •
poprawne wykonanie funkcji: identyfikator segmentu pamięci współdzielonej zakończenie błędne: -1
Możliwe kody błędów (errno) w przypadku błędnego zakończenie funkcji: • • • • • •
EACCES - brak brak praw dostępu ENOENT - segment pamięci nie istnieje EIDRM - segment pamięci został usunięty EINVAL - nieprawidłowy rozmiar segmentu pamięci ENOMEM - nie ma wystarczająco dużo miejsca by stworzyć segment pamięci współdzielonej EEXIST - segment pamięci współdzielonej istnieje
Argumenty funkcji: •
key - wartość klucza, który identyfikuje segment pamięci współdzielonej (podobnie jak w przypadku kolejek komunikatów może to być dowolna liczba lub stała IPC_PRIVATE) • size - wielkość segmentu pamięci współdzielonej (w bajtach) • shmflags - prawa dostępu do pamięci współdzielonej (z prawami dostępu mogą zostać użyte znaczniki IPC_CREAT, IPC_EXCL, ich działanie jest analogiczne jak w przypadku funkcji msgget) UWAGI: Funkcja shmget służy do tworzenia segmentu pamięci współdzielonej i do uzyskiwania dostępu do już istniejących segmentów pamięci. W drugim przypadku wartością parametru size może być 0, ponieważ rozmiar segmentu został już wcześniej zadeklarowany przez proces, który go utworzył. int shmctl ( int shmid, int cmd, struct shmid_ds *buf ) Wartości zwracane: • •
poprawne wykonanie funkcji: 0 zakończenie błędne: -1
Argumenty funkcji: • •
shmid - identyfikator pamięci współdzielonej cmd - stała specyfikująca rodzaj operacji cmd = IPC_STAT - pozwala uzyskać informację o stanie pamięci współdzielonej cmd = IPC_SET - pozwala zmienić parametry segmentu pamięci cmd = IPC_RMID - pozwala usunąć segment pamięci współdzielonej z systemu
•
buf - wskaźnik na zmienną strukturalną przez którą przekazywane są parametry operacji
UWAGI: Funkcja odpowiada funkcji msgctl. Przy próbie usunięcia segmentu odwzorowanego na przestrzeń adresową procesu system odpowiada komunikatem o błędzie. Jeśli w wywołaniu funkcji użyje się stałej IPC_RMID, to wartość argumentu buf należy wyzerować, rzutując 0 na typ (shmid_ds *). char* shmat ( int shmid, char* shmaddr, int shmflg ) Wartości zwracane:
•
poprawne wykonanie funkcji: wskaźnik do segmentu danych, do którego jest dowiązana pamięć współdzielona. • zakończenie błędne: -1 Argumenty funkcji: • •
shmid - identyfikator pamięci współdzielonej zwracany przez funkcję shmget shmaddr - adres dla tworzonego segmentu pamięci współdzielonej lub wartość NULL, która powoduje, że segment dołączany jest w miejscu wybranym przez system (użytkownik nie musi znać rozmieszczenia programu w pamięci) • shmflg - określa uprawnienia do segmentu pamięci współdzielonej i specjalne warunki dowiązania UWAGI: Ponieważ pamięć jest alokowana przy wywołaniu funkcji shmat nie ma potrzeby używania funkcji malloc przy umieszczaniu danych w segmencie. Domyślnie dowiązane segmenty są dostępne w trybie do zapisu i odczytu. W przypadku gdy segment ma segmentem tylko do odczytu, argument shmflg można połączyć operatorem OR ze znacznikiem SHM_RDONLY. Natomiast gdy dla shmflg jest ustawiony znacznik SHM_RND, to przy wywołaniu funkcji adres shmaddr jest zaokrąglany w dół do granicy strony w pamięci, a w przeciwnym razie pobierana jest wartość podana jako argument wejściowy. char* shmdt ( char* shmaddr ) Wartości zwracane: • •
poprawne wykonanie funkcji: 0 zakończenie błędne: -1
Argumenty funkcji: •
shmaddr - adres stworzonego segmentu pamięci współdzielonej
UWAGI: Odłączenie segmentu pamięci współdzielonej. Odłączenie to powinno nastąpić po zakończeniu pracy z danym segmentem. Po wywołaniu funkcji shmdt licznik dołączeń do segmentu jest zmniejszany o 1. Przykład usunięcia segmentu pamięci: struct shmid_ds shm_desc; shmctl(shm_id, IPC_RMID, shm_desc) Przykładowe programy zapisujące/odczytujące dane z segmentów pamięci współdzielonej. Listing 1 przedstawia program, w którym następuje cykliczny zapis bufora umieszczonego we współdzielonym obszarze pamięci. Listing przedstawia program, w którym jest analogiczny odczyt bufora cyklicznego.
3
#include #include #include #include
#define MAX 10 6 9 12
main(){ int shmid, i; int *buf; shmid = shmget(45281, MAX*sizeof(int), IPC_CREAT|0600); if (shmid == -1){ perror("Utworzenie segmentu pamieci wspoldzielonej");
exit(1); 15
}
18 21 24
}
buf = (int*)shmat(shmid, NULL, 0); if (buf == NULL){ perror("Przylaczenie segmentu pamieci wspoldzielonej"); exit(1); } for (i=0; i<10000; i++) buf[i%MAX] = i;
Listing 1: Zapis bufora cyklicznego Opis programu: W linii 11 tworzony jest segment współdzielonej pamięci o kluczu 45281, o rozmiarze MAX*sizeof(int) i prawach do zapisu i odczytu przez właściciela. Jeśli obszar o takim kluczu już istnieje, zwracany jest jego identyfikator, czyli nie jest tworzony nowy obszar i tym samym rozmiar podany w drugim parametrze oraz prawa dostępu są ignorowane. W linii 17 utworzony segment włączony zostaje do segmentów danego procesu i zwracany jest adres tego segmentu. Zwrócony adres podstawiany jest pod zmienną buf. Buf jest zatem adresem tablicy o rozmiarze MAX i typie składowym int. Pętla w liniach 23-24 oznacza cykliczny zapis tego bufora, tzn. indeks pozycji, na której zapisujemy jest równy i%MAX, czyli zmienia się cyklicznie od 0 do MAX-1.
3 6 9
#include #include #include #include
#define MAX 10 main(){ int shmid, i; int *buf; shmid = shmget(45281, MAX*sizeof(int), IPC_CREAT|0600);
12
if (shmid == -1){ perror("Utworzenie segmentu pamieci wspoldzielonej"); exit(1); }
15
21
buf = (int*)shmat(shmid, NULL, 0); if (buf == NULL){ perror("Przylaczenie segmentu pamieci wspoldzielonej"); exit(1); }
24
for (i=0; i<10000; i++) printf("Numer: %5d Wartosc: %5d\n", i, buf[i%MAX]);
18
}
Listing 2: Odczyt bufora cyklicznego Opis programu: Powyższy program jest analogiczny, jak program na listingu 2, przy czym w pętli w liniach 23-24 następuje cykliczny odczyt, czyli odczyt z pozycji w buforze, zmieniającej się cyklicznie od 0 do MAX-1.
key_t ftok ( const char* path, int id ) Wartości zwracane: • •
poprawne wykonanie funkcji: unikalny klucz zakończenie błędne: -1
Argumenty funkcji: •
path – nazwa ścieżki istniejacego pliku
•
id – pożądany numer klucza
UWAGI: W rzeczywistości ftok może generować bardzo wiele różnych kluczy na podstawie tej samej ścieżki; pożądany klucz jest określony przez argument id, którym może być znak, ponieważ używa się tylko jego ośmiu najmniej znaczących bitów. Nie może być jednak zerem. Nazwa ścieżki musi już istnieć.
Zadnia przygotowawcze: Napisać programy: klient i serwer. Program serwer obsługuje grupę klientów o identyfikatorach podanych jako parametry wywołania. Program klient może wysyłać komunikaty (linie tekstu wprowadzone ze standardowego wejścia) do serwera poprzez kolejkę komunikatów IPC. Każdy komunikat jest ciągiem znaków o długości <=80 bajtów, zakończonym przez '\0'. Serwer odbiera komunikaty z kolejki IPC od dowolnego klienta i rozsyła do wszystkich pozostałych klientów (z wyjątkiem tego, który nadał komunikat). Klienci odczytują przeznaczone dla nich komunikaty i wypisują na terminalu. Klient.c #include #include #include #include #include #include
#define KOMUNIKAT(x) printf("Blad wywolania funkcji " #x "()!.\n"); #define MAX_SIZE 81 #define MAX 20 struct meseg { long type; char text[MAX_SIZE]; }; void *read_meseg(void * arg); int main(void) { int msgid, msgid2, i; struct meseg elem; pthread_t tid; printf("PID procesu %d\n", getpid()); msgid = msgget(45266, IPC_CREAT|IPC_EXCL|0600); if (msgid == -1){ msgid = msgget(45266, IPC_CREAT|0600);
}
if (msgid == -1){ perror("Utworzenie kolejki komunikatów\n"); exit(1); }
msgid2 = msgget(45285, IPC_CREAT|IPC_EXCL|0600); if (msgid2 == -1){ msgid2 = msgget(45285, IPC_CREAT|0600); if (msgid2 == -1){ perror("Utworzenie kolejki komunikatow\n"); exit(1); } } if(pthread_create( &tid, NULL, read_meseg, &msgid2) ){ printf("Blad tworzenia watku!\n"); abort(); } for (i=0; i<5; i++){ printf("Podaj komunikat: \n"); fgets(elem.text, MAX_SIZE-1, stdin); elem.type = getpid(); if (msgsnd(msgid, &elem, sizeof(elem.text), 0) == -1){ perror("Wyslanie pustego komunikatu\n"); exit(1); } } }
return 0;
void *read_meseg(void * arg) { int* num = (int*) arg; struct meseg elem2; elem2.type = getpid(); while(1){ if ( (msgrcv(*num,&elem2,MAX_SIZE,elem2.type, 0)) < 0){ perror("Odebranie pustego komunikatu\n"); exit(1); } printf("Proces o otzymal komunikat: %s.\n", elem2.text); } } Serwer.c #include #include #include #include #include #include
#define KOMUNIKAT(x) printf("Blad wywolania funkcji " #x "()!.\n"); #define MAX_SIZE 81
#define MAX 20 struct meseg { long type; char text[MAX_SIZE]; }; int main(int argc, char *argv[]) { int i, msgid, msgid2; struct meseg elem; long pidy[argc-1]; if(argc < 2){ KOMUNIKAT(argv[0]); exit(1); } for(i = 1; i < argc; ++i){ if( (pidy[i-1] = atol(argv[i])) == 0 ){ printf("%ld\n", pidy[i-1]); KOMUNIKAT(atol); exit(2); } }
msgid = msgget(45266, IPC_CREAT|IPC_EXCL|0600); if (msgid == -1){ msgid = msgget(45266, IPC_CREAT|0600); if (msgid == -1){ perror("Utworzenie kolejki komunikatow\n"); exit(1); } } msgid2 = msgget(45285, IPC_CREAT|IPC_EXCL|0600); if (msgid2 == -1){ msgid2 = msgget(45285, IPC_CREAT|0600); if (msgid2 == -1){ perror("Utworzenie kolejki komunikatow\n"); exit(1); } } while(1){ if ( (msgrcv(msgid,&elem,MAX_SIZE,0, 0)) < 0){ perror("Odebranie pustego komunikatu\n"); exit(1); } long temp = elem.type; for(i = 0; i < argc - 1; ++i) { if(temp != pidy[i]){ elem.type = pidy[i]; printf("Wysylam do procesu o PID = %ld\n", pidy[i]); if (msgsnd(msgid2, &elem, sizeof(elem.text), 0) == -1){
} }
perror("Wyslanie pustego komunikatu\n"); exit(1);
}
} }
return 0;
Napisać program rejestr, który przyjmuje zlecenia rejestracji użytkowników. Dane dotyczące użytkownika (identyfikator alfanumeryczny (login name), nazwisko, imię) mają być przechowywane w pamięci dzielonej razem z przydzielonym użytkownikowi unikalnym identyfikatorem numerycznym. Napisać też program ktoto, który sprawdza, czy użytkownik o podanym identyfikatorze (alfanumerycznym czy numerycznym) istnieje w bazie danych, a jeśli tak - wypisuje na terminalu komplet danych o użytkowniku. Rejestr.c #include #include #include #include #include
#define MAX 30 #define TEST printf("I am here!!!\n"); #define KOMUNIKAT(x) printf("Blad wywolania funkcji " #x "()!.\n");
struct user{ key_t key; int number; char login[MAX]; char name[MAX]; char firstname[MAX]; };
int main(void){ int shmid, i;
struct user *buf; struct user queue[MAX];
shmid = shmget(45281, MAX*(sizeof(queue[0].login)*3+sizeof(key_t) +sizeof(int)), IPC_CREAT|0600); if (shmid == -1){ perror("Utworzenie segmentu pamieci wspoldzielonej"); exit(1); }
buf = (struct user *)shmat(shmid, NULL, 0); if (buf == NULL){ perror("Przylaczenie segmentu pamieci wspoldzielonej"); exit(1); }
printf("Rejestr jest w stanie pomieścić %d uzytkownikow!\n", MAX); for(i = 0; i < MAX ; ++i){ printf("W rejestrze aktualnie znajduja sie dane %d uzytkownkow.\n", i); buf[i].key = ftok("rejestr", i); printf("%d\n", buf[i].key); buf[i].number = i+1; printf("Podaj login uzytkownika:\n"); fgets(buf[i].login, MAX, stdin); printf("Podaj nazwisko uzytkownika:\n"); fgets(buf[i].name, MAX, stdin); printf("Podaj imie uzytkownika:\n"); fgets(buf[i].firstname, MAX, stdin);
}
exit(0); }
Ktoto.c #include #include #include #include #include
#define MAX 30 #define TEST printf("I am here!!!\n"); #define KOMUNIKAT(x) printf("Blad wywolania funkcji " #x "()!.\n");
struct user{ key_t key; int number; char login[MAX]; char name[MAX]; char firstname[MAX]; };
int main(void){ int shmid, i, j, klucz; char login[MAX]; struct user *buf; struct user queue[MAX];
shmid = shmget(45281, MAX*(sizeof(queue[0].login)*3+sizeof(key_t) +sizeof(int)), IPC_CREAT|0600); if (shmid == -1){ perror("Utworzenie segmentu pamieci wspoldzielonej"); exit(1); }
buf = (struct user*)shmat(shmid, NULL, SHM_RDONLY); if (buf == NULL){ perror("Przylaczenie segmentu pamieci wspoldzielonej"); exit(1); } /* for (i=0; i
*/ while(1){ ID??\n");
printf("Czy chcesz wyszukac uzytkownika za pomoca loginu czy printf("1) login\n2) ID\n"); scanf("%d", &i); getchar(); switch(i){ case 1 : printf("Podaj login:\n"); fgets(login, MAX, stdin); for(j = 0; j < MAX; ++j){ if(!strcmp(buf[j].login, login,
strlen(login))){ printf("Key - %d\n", buf[j].key); buf[j].number);
printf("Numer - %d\n",
printf("Login - %s\n", buf[j].login); printf("Nazwisko - %s\n",
buf[j].name);
printf("Imie - %s\n", buf[j].firstname); }
} break; case 2 : printf("Podaj klucz:\n"); scanf("%d", &klucz); getchar(); for(j = 0; j < MAX; ++j){ if(buf[j].key == klucz){ printf("Key - %d\n", buf[j].key); printf("Numer - %d\n",
buf[j].number);
printf("Login - %s\n", buf[j].login); printf("Nazwisko - %s\n",
buf[j].name);
printf("Imie - %s\n", buf[j].firstname); }
} break; }
}
exit(0); }