11. PAKIET IPC
Narzędzia z pakietu IPC
(InterProcess Communication)
służą do koordynacji
procesów wykonywa-
nych na jednym komputerze (nie są przeznaczone do komunikacji sieciowej). W
skład tego pakietu
wchodzą biblioteki funkcji obsługujących kolejki komunikatów
(message
queue)
, pamięć dzieloną
(shared memory)
i semafory
(semaphore)
. Z wszystkimi trzema rodzajami
obiektów związane są
odpowiednie struktury danych tworzone przez jądro systemu, do których dostęp
jest możliwy jedynie
poprzez wywoływanie przeznaczonych do tego funkcji systemowych.
Zestawienie głównych funkcji pakietu IPC (wg W.R. Stevensa):
Kolejki komun. Semafory
Pamięć dziel.
Plik nagłówkowy < sys/msg.h > <
sys/sem.h> < sys/shm.h>
Funkcja systemowa tworzenia lub otwierania msgget semget
shmget
Funkcja systemowa operacji sterujących msgctl semctl
shmctl
Funkcje systemowe przesyłania msgsnd semop
shmat
msgrcv
shmdt
Ze względu na to, że struktury kontrolne obiektów pakietu IPC są przechowywane
w jądrze systemu
i mogą być widoczne dla wszystkich procesów, muszą mieć klucze unikalne w
obrębie całego systemu.
Zalecane jest stosowanie funkcji ftok generującej unikalne klucze na podstawie
ścieżek dostępu do
plików z programami wykonywanymi przez procesy tworzące obiekty pakietu IPC.
Dopuszczalny
zakres kluczy zależy od ustawień systemowych - odpowiada mu typ key_t
zdefiniowany w nagłówku
< sys/types.h >.
Kolejki komunikatów
Kolejki komunikatów nie są kolejkami prostymi (komunikaty mogą być z nich
wybierane w innej
kolejności, niż zostały umieszczone). Komunikaty posiadają pewną strukturę (nie
są tylko ciągami
bajtów, jak w przypadku łącz):
struct msgbuf { long mtype ;
char mtext[1] ; }
Uwaga. Typ char zawartości komunikatu jest zdefiniowany tylko pro forma - może
być rzutowany.
int msgget (key_t klucz, int flagi);
Zwraca: identyfikator kolejki w przypadku sukcesu ;
-1 w przypadku błędu.
Flagi określają prawa dostępu, oraz czy ma być zwrócony błąd, jeśli kolejka o
danym kluczu już istnieje.
Działanie: tworzy kolejkę o podanym kluczu, jeśli taka kolejka jeszcze nie istnieje.
int msgsnd (int ident, struct msgbuf kom, int rozmiar, int flagi) ;
Zwraca: 0 w przypadku sukcesu ;
-1 w przypadku błędu.
ident - identyfikator kolejki (zwrócony przez msgget)
kom - wskaźnik do struktury przechowującej typ komunikatu i sam komunikat
(bufora komunikatu)
rozmiar - rozmiar komunikatu „netto” (nie licząc typu)
flagi - 0 lub IPC_NOWAIT (decydują, czy w sytuacji przepełnienia kolejki proces ma
być zawieszony)
Działanie: wstawia komunikat wraz z podanym typem na koniec kolejki.
int msgrcv (int ident; struct msgbuf kom, int rozmiar, long mtype, int flagi);
Zwraca: liczbę faktycznie pobranych bajtów z kolejki w przypadku sukcesu ;
-1 w przypadku błędu.
ident - identyfikator kolejki
kom - wskaźnik do bufora komunikatu
rozmiar - rozmiar struktury komunikatu (nie licząc typu)
mtype - typ komunikatu, jaki chcemy pobrać z kolejki (może być 0)
flagi - można ustawić IPC_NOWAIT i / lub MSG_NOERROR (powoduje
odpowiednie zachowanie,
jeśli komunikat jest większy, niż przewiduje rozmiar)
Działanie: pobiera z kolejki najdawniej wstawiony komunikat o danym typie (jeśli
istnieje), zaś
jeśli został podany typ 0, pobiera najdawniej wstawiony komunikat (o
dowolnym typie).
int msgctl (int ident, int polecenie, struct msgqid_ds struktura);
Zwraca: 0 w przypadku sukcesu ;
-1 w przypadku błędu.
ident - identyfikator kolejki
polecenie - kod czynności do wykonania na strukturze kontrolnej kolejki
struktura - wskaźnik do bufora struktury kontrolnej
Działanie: może wykonywać mnóstwo różnych czynności (w tym również takich,
które mogą
pozbawić programistę kontroli nad kolejką) - zmieniać prawa dostępu,
odczytywać
informacje o ostatnio wykonanej operacji na kolejce itp. Najczęściej jest
wykorzystywana
do usunięcia kolejki:
msgctl (ident, IPC_RMID, 0);
Kolejki komunikatów są narzędziem bardziej skomplikowanym w użyciu, a
jednocześnie oferującym
bogatsze możliwości, niż kolejki FIFO. W typowym zastosowaniu - implementacji
par programów
typu klient / serwer - stosując kolejki FIFO musimy otworzyć oddzielną kolejkę dla
serwera i oddzielne
dla wszystkich klientów (gdyż nie ma możliwości testowania danych
umieszczonych w jednej kolejce
dla wielu odbiorców).
klient 1
kolejka
klienta 1
Dane od klientów powinny na początku zawierać informację o swojej długości oraz
adres zwrotny
(to jest adres kolejki odbiorczej klienta).
serw
er
kolejka
serwera
klient
2
kolejka klienta
2
klient
n
kolejka
klienta n
W przypadku stosowania kolejek komunikatów wystarczają dwie takie kolejki
(związane z serwerem),
gdyż umożliwiają demultipleksowanie odpowiedzi serwera do różnych klientów.
serwe
r
odpowiedzi
pytania
Klienci muszą mieć unikalne adresy kodowane jako typy komunikatów.
Odpowiedzi serwera są
opatrywane tymi samymi typami, jakie miały przysyłane przez klientów pytania -
klienci mogą je
selektywnie wybierać z kolejki zwrotnej.
Uwaga. Jest również możliwe rozwiązanie przy użyciu tylko jednej kolejki (dla
pytań i odpowiedzi).
klient
1
klient
2
klient
n
Pamięć dzielona
Poza własnym segmentem danych przydzielonym w momencie utworzenia,
proces może mieć
przydzielony dynamicznie (w trakcie wykonywania) jeden lub więcej segmentów
pamięci z ogólnych
zasobów systemowych. Takie segmenty są dołączane do przestrzeni adresowej
procesu i można na
nich operować bezpośrednio (na przykład wykonując operacje przypisania). Jeśli
programista ustanowi
odpowiednie prawa dostępu, segmenty takie mogą być niezależnie przydzielane
wielu procesom
jednocześnie. Komunikowanie się przez pamięć dzieloną jest zdecydowanie
najszybszym sposobem
komunikowania się procesów (choć najtrudniejszym do synchronizacji).
Uwaga.
1) Podobnie jak w przypadku plików i dowiązań do nich, segment pamięci
dzielonej jest zwracany do
puli wolnych zasobów systemowych dopiero wtedy, gdy ostatni z użytkujących
go procesów
zrzeknie się jego dalszego używania (odłączy go od swojej przestrzeni
adresowej).
2) Nie ma możliwości operowania w segmencie pamięci dzielonej inaczej, niż za
pomocą zmiennych
dynamicznych (wskaźników). W gruncie rzeczy segmenty pamięci dzielonej są
widziane przez
programy jako dodatkowe (współdzielone) sterty.
3) Jeden i ten sam segment pamięci dzielonej może być dołączony do przestrzeni
adresowej procesu
w wielu różnych miejscach. W ten sposób możemy dysponować wieloma
kopiami jednej i tej
samej zmiennej, zmiana wartości jednej kopii jest natychmiast widoczna w
pozostałych miejscach.
4) Proces potomny dziedziczy utworzony (i przyłączony) segment pamięci
dzielonej.
int shmget (key_t klucz, int rozmiar, int flagi);
Zwraca: identyfikator segmentu w przypadku sukcesu;
-1 w przypadku błędu.
klucz, flagi - pełnią podobną rolę, jak dla kolejek komunikatów
rozmiar - rozmiar tworzonego segmentu w bajtach (argument nieistotny, jeśli
segment już istnieje)
Działanie: tworzy nową pozycję w tablicy segmentów, jeśli segment wcześniej nie
istniał.
int shmat (int ident, char adres, int flagi);
Zwraca: wskaźnik do miejsca, gdzie rzeczywiście został dołączony segment, w
przypadku sukcesu;
-1 w przypadku błędu.
ident - identyfikator segmentu (zwrócony przez funkcję shmget)
adres - wskaźnik do miejsca, gdzie programista proponuje dołączyć segment
(może być 0)
flagi - rozmaite role ( na przykład mogą nakazać dołączenie segmentu tylko do
odczytu)
Działanie: dołącza segment (i zwiększa jego licznik dowiązań o 1) pod podanym
adresem (w miarę
możności) jeśli adres jest większy od 0, zaś pod adresem wybranym
przez system, jeśli
podany adres jest równy 0 (najczęściej stosowane i najbardziej
zalecane postępowanie).
int shmdt (char *adres);
Zwraca: 0 w przypadku sukcesu;
-1 w przypadku błędu.
adres - adres, pod którym był dołączony (przez funkcję shmat) segment pamięci
dzielonej
Działanie: segment jest odłączany od przestrzeni adresowej procesu, a jego licznik
dowiązań zmniej-
szany o 1. Jeżeli stan licznika dowiązań zmniejszył się w wyniku tego do
0, a segment był
oznaczony do usunięcia, w tym momencie następuje jego usunięcie z
tablicy segmentów.
int shmctl (int ident, int polecenie, struct shmid_ds struktura);
Zwraca: 0 w przypadku sukcesu;
-1 w przypadku błędu.
ident - identyfikator segmentu (zwrócony przez funkcję shmget)
polecenie - kod polecenia do wykonania na strukturze zarządzającej segmentem
struktura - wskaźnik do bufora struktury zarządzającej segmentem
Działanie: podobnie, jak w przypadku kolejek komunikatów, może wykonywać
wiele różnych
czynności, a najczęsciej wykonywaną jest oznaczenie segmentu do
usunięcia:
shmctl (ident, IPC_RMID, 0);
Uwaga. Zalecane jest odłączenie segmentu (wykonanie funkcji shmdt) przed
oznaczeniem segmentu
do usunięcia.
Semafory
Semafory są uważane za najbardziej skomplikowane w użyciu obiekty pakietu
IPC. Ich najbardziej
typowym zastosowaniem jest synchronizacja dostępu różnych procesów do
zmiennych w pamięci
dzielonej. Semafory zaimplementowane w pakiecie IPC są podobne do
semaforów Agerwali.
Występują nie jako pojedyncze obiekty, ale jako elementy tablic semaforów, na
których można
wykonywać jednocześnie (niepodzielne) operacje. Maksymalny rozmiar tablicy
semaforów zależy od
ustawień systemowych. Zakres wartości przyjmowanych przez pojedynczy
semafor jest zakresem
wartości typu ushort (czyli od 0 do 255).
0 1 2 3
n-1
sem. 0 sem. 1 sem. 2 sem. 3
sem. n-1
int semget (key_t klucz, int liczbasem, int flagi);
Zwraca: identyfikator tablicy semaforów w przypadku sukcesu;
-1 w przypadku błędu.
klucz, flagi - jak dla kolejek komunikatów i pamięci dzielonej
liczbasem - liczba semaforów w tablicy (argument nieistotny, jeśli tablica już
istnieje)
Działanie: tworzy nową tablicę semaforów, jeśli wcześniej nie istniała.
int semop (int ident, struct sembuf oper, unsigned liczbaoper);
gdzie struct sembuf
{ ushort sem_num; numer semafora w tablicy
short sem_op; operacja na semaforze
short sem_flg; } flagi operacji
Zwraca: 0 w przypadku sukcesu;
-1 w przypadku błędu.
ident - identyfikator tablicy semaforów (zwrócony przez funkcję semget)
oper - wskaźnik do początku tablicy operacji (tablicy struktur sembuf)
liczbaoper - liczba elementów w tablicy wskazywanej przez oper
Działanie: system wykonuje niepodzielnie wszystkie operacje nakazane w tablicy
struktur wskazywanej
przez oper - albo nie wykonuje żadnej, jeśli choć jedna z nich jest w danej chwili
niemożliwa.
Pojedyncza operacja na pojedynczym semaforze wygląda następująco:
- jeżeli wartość sem_op jest dodatnia, wartość semafora zwiększa się o nią
(zatem, w przeciwieństwie
do tego, co jest podane w klasycznej definicji semafora, wartość semafora może
wzrosnąć o więcej,
niż 1), a jednocześnie jest budzona odpowiednia liczba procesów śpiących pod
tym semaforem (jeśli
są takie);
- jeżeli wartość sem_op jest ujemna, wartość semafora odpowiednio się
zmniejsza, jeśli to możliwe,
a jeśli niemożliwe, zmniejszenie nie jest wykonywane, a proces albo zasypia
czekając na zaistnienie
takiej możliwości, albo od razu następuje powrót z funkcji z błędem (w
zależności od ustawienia
flagi IPC_NOWAIT);
- jeżeli wartość sem_op wynosi zero, proces zasypia, jeśli wartość semafora nie
jest zerem (lub wraca
od razu z błędem, jeśli jest ustawiona flaga IPC_NOWAIT) i budzi się dopiero po
osiągnięciu
wartości zero przez semafor.
int semctl (int ident, int numer, int polecenie, union semun argument);
gdzie union semun
{ int val; do ustawienia wartości pojedynczego
semafora
struct semid_ds buf; bufor struktury zarządzającej tablicą
semaforów
ushort array; wskaźnik do tablicy ustawień
wartości całej tablicy sem.
struct seminfo __buf; specyficzne dla Linuxa, używane przez
void __pad; } jądro systemu operacyjnego
Zwraca: liczbę dodatnią będącą wynikiem wykonania polecenia - w przypadku
sukcesu
-1 w przypadku błędu
ident - identyfikator tablicy semaforów (zwrócony przez funkcję semget)
numer - numer semafora w tablicy (istotny w przypadku, gdy polecenie dotyczy
pojedynczego semafora)
polecenie - kod polecenia do wykonania
argument - argument jednego z typów wchodzących w skład unii, zależnego od
polecenia
Działanie: może wykonywać mnóstwo różnych czynności (najwięcej z wszystkich
funkcji IPC) na
pojedynczych semaforach, na całej ich tablicy lub na strukturze
zarządzającej. Najczęściej
używanymi poleceniami są:
IPC_RMID usunięcie tablicy semaforów
GETALL odczytanie wartości wszystkich semaforów w
tablicy
SETALL nadanie wartości wszystkim semaforom w
tablicy
GETVAL odczytanie wartości pojedynczego semafora
SETVAL nadanie wartości pojedynczemu semaforowi
Uwaga. Operacje nadania lub odczytania wartości semaforów nie wiążą się z
możliwością wstrzymania
procesu wykonującego daną operację.