Zaawansowana komunikacja
międzyprocesowa
IPC
IPC jest zbiorem urządzeń komunikacji międzyprocesowej (ang.
interprocess communication). W skład tego zbioru wchodzą:
Przekazywanie komunikatów (ang. message passing): pozwala
procesowi wysyłać i odbierać komunikaty, będące dowolną sekwencją bajtów lub znaków
Semafory (ang. semaphores) niskopoziomowy sposób
synchronizacji procesów, nieodpowiedni do przesyłania danych. Podstawy teoretyczne E.W. Dijkstra (1968)
Pamięć współdzielona (ang. shared memory) najszybsze
urządzenie IPC pozwalające pewnej liczbie procesów korzystać z danych zawartych w określonych segmentach pamięci.
Urządzenia IPC są powszechnie używane i pochodzą z Systemu V Uniksa.
Klucze IPC
3 Wszystkie urządzenia IPC posiadają podobny interfejs, odbijając
podobieństwo ich implementacji wewnątrz jądra. Najważniejszą wspólna cechą jest klucz urządzenia IPC. Klucze są liczbami używanymi do
identyfikacji obiektów IPC w Uniksie w podobny sposób jak nazwa
identyfikuje plik. Umożliwia to wspólne użycie zasobów IPC przez kilka (niespokrewnionych) procesów. Klucz jest określany przez zależny od implementacji typ key_t z <sys/types.h>.
Klucze różnych obiektów nie mogą się powtarzać. Do znajdowania unikatowego klucza zależnego od ścieżki pliku służy funkcja:
key_t ftok(const char *path,int id);
Zwraca ona numer klucza w oparciu o path pliku. Parametr id
dostarcza dodatkowy poziom niepowtarzalności – ta sama path dla różnych id daje różne klucze.
Przekazywanie komunikatów
4 Komunikat jest sekwencją znaków. Komunikaty są przekazywane
między procesami za pomocą kolejek komunikatów tworzonych przez funkcję:
int msgget(key_t key, int permflags);
key identyfikuje kolejkę jako urządzenie IPC, permflags może
przyjmować dwie wartości zdefiniowane w <sys/ipc.h>:
IPC_CREAT utwórz kolejkę, o ile nie istnieje; nie będzie nadpisana,
jeżeli istnieje; bez tego parametru wywołanie dla istniejącej kolejki odda identyfikator istniejącej kolejki
IPC_EXCL jeżeli użyte z IPC_CREATE, to tworzy kolejkę; jeżeli kolejka
istnieje zwraca -1 a errno zwraca EEXIST.
9 mniej znaczących bitów jest permflags używane do nadawania uprawnień:
Wysyłanie i odbieranie
5 Do operowania na istniejącej kolejce komunikatów służą funkcje:
int msgsnd(int mqid, const void *msg, size_t size, int flags); int msgrcv(int mqid, void *msg,
size_t size, long msg_type);
mqid to identyfikator kolejki zwrócony przez msgget, komunikat jest
zawarty w strukturze o następującym wzorcu:
struct mymsg{
long mtype; /* typ komunikatu */
char mtext[SOMEVALUE]; /* tekst komunikatu */ }
Składowa mtype może być używana do klasyfikacji komunikatów.
size to dla wysyłania liczba wysłanych bajtów < SOMEVALUE lub
Wysyłanie i odbieranie
II
6 Parametr flags dla msgsnd może przybierać tylko jedną wartość:
IPC_NOWAIT. Bez niej proces wywołujący będzie wstrzymany, jeżeli
brak zasobów systemowych do wysłania komunikatu, np. całkowita długośc komunikatów przekracza max. systemowe lub max. kolejki. Zmienna errno będzie ustawiona na EAGAIN.
Wysyłanie komunikatu może zawieść z powodu oprawnień. W zmiennej
errno będzie wówczas EACCES.
Odczyt komunikatu, o ile mamy uprawnienia, powoduje usuniecie go z kolejki. Parametr msg_type określa jaki typ komunikatu został
odczytamy, jeżeli był ustawiony na 0. Jeżeli msg_type ma wartość >0, to zostanie odczytany komunikat o tym typie. Jeżeli ma wartość ujemną, to zostanie odczytany pierwszy komunikat z najniższą liczbą mtype,
mniejszą niż albo równą wartości bezwzględnej msg_type.
flags przyjmuje wartości IPC_NOWAIT (działa jak dla wysyłania) i/lub MSG_NOERROR – komunikat obcięty, jeżeli przekracza size.
Funkcja msgctl
7 Funkcja msgctl służy trzem celom:
uzyskanie informacji o stanie kolejki komunikatów
zmiana niektórych ograniczeń zwiazanych z kolejką
usuwanie kolejki z systemu Postać funkcji:
int msgctl(int mqid, int command,
struct msqid_ds *msq_stat);
Parametr mqid jest identyfikatorem kolejki komunikatów. Trzeci
parametr msq_stat zawiera adres struktury msqid_ds zdefiniowanej w <bits/msg.h>. Opis w pliku msg/msqid_ds.
Funkcja msgctl
II
8 Parametr command opisuje operację wykonywaną przez msgctl. Istnieją trzy dostępne wartości wspólne dla wszystkich urządzeń IPC:
IPC_STAT umieść w msq_stat informacje o stanie struktury IPC_SET zmiana parametrów urządzenia IPC; dla kolejek
komunikatów można zmieniać:
msg_stat.msg_perm.uid, msg_stat.msg_perm.gid, msg_stat.msg_perm.mode,
msg_stat.msg_qbytes; operacja zakończy się
pomyślnie, jeżeli jest wykonywana przez SU lub właściciela kolejki; tylko SU może powiększyć msg_qbytes (max.
liczba znaków w kolejce)
IPC_RMID usuwa kolejkę komunikatów z systemu; operacja
dostępna tylko dla SU lub właściciela kolejki; po wykonaniu
msq_stat ustawiony na NULL. Zobacz: msg/show_msg.c
Semafory
9 Pojęcie semafora zostało po raz pierwszy wprowadzone przez
E.W.Dijkstrę, jako rozwiazanie problemów synchronizacji procesów. Semafor jest zmienną całkowitą, na której dozwolone są następujące operacje:
p(sem):
if (sem != 0) zmniejsz sem o 1 else czekaj aż sem stanie się
różne od zera, wtedy zmniejsz
v(sem):
zwiększ sem o 1
if(kolejka czekających procesów nie jest pusta)
uruchom pierwszy proces z kolejki oczekujących
Część testująca i ustawiająca obu operacji musi być niepodzielna. Niezmiennik semafora:
(początkowa wartość sem + liczba operacji v – liczba zakończonych operacji p) >= 0
Semafory:
get
10 Funkcja semget jest analogiczna do funkcji msgget.
int semget(key_t key, int nsems, int permflags);
Operacje semaforowe Unixa są nastawione na pracę z zestawami semaforów, a nie z pojedynczymi obiektami. Parametr nsems podaje liczbę semaforów w tworzonym zestawie. Wartością zwracaną przez pomyślne wywołanie semget jest identyfikator zestawu semaforów.
semval = 2 semval = 4 semval = 1 semval = 3 nsems = 4 semid
Semafory:
wartości
11 Z każdym semaforem w zestawie związane są następujące wartości:
semval wartość semafora, zawsze dodatnia liczba całkowita. Musi być
ustawiana za pomocą funkcji systemowej semafora; oznacza to, że semafor nie jest dostępny dla programu jako obiekt
danych.
sempid identyfikator procesu, który ostatnio miał do czynienia z
semaforem.
semncnt liczba procesów, które czekają, aż semafor osiagnie wartość
większą niż jego aktualna wartość.
semzcnt liczba procesów, które czekają, aż semafor osiągnie wartość
Semafory:
semctl
12
int semctl(int semid, int sem_num, int command, union semun ctl_arg);
Parametr semid musi być ważnym identyfikatorem semafora (wynik
semget). Parametry command są podzielone na trzy grupy:
standardowe funkcje IPC (jak IPC_STAT),
funkcje działające tylko na pojedynczy semafor, funkcje dotyczące całego zestawu semaforów.
Parametr sem_num jest używany z drugą grupą funkcji do identyfikacji konkretnego semafora. Ostatni parametr jest unią:
union semun{ int val;
struct semid_ds *buf; unsigned short *array; }
Używana do przekazywania wartości – wykorzystanie poszczególnych pól zależne od operacji.
Kody funkcji
semctl
13
Standardowe
IPC_STAT umieszcza informację o stanie w ctl_arg.stat IPC_SET ustawia informację o prawach własności i dostępu z
ctl_arg.stat
IPC_RMID usuwa zestaw semaforów z systemu Operacje na pojedynczym semaforze
GETVAT zwraca wartość semafora (tj. semval)
SETVAL ustawia wartość semafora z ctl_arg.val GETPID zwraca wartość sempid
GETNCNT zwraca semncnt GETZCNT zwraca semzcnt
Operacje na wszystkich semaforach
GETALL umieszcza wszystkie semvals w ctl_arg.array
Semafory
semop
14
int semop(int semid, struct sembuf *op_array, size_t num_ops);
Parametr semid musi być ważnym identyfikatorem semafora (wynik
semget). Parametr op_array to tablica struktur sembuf,
zdefiniowane w <sys/sem.h>. Parametr num_ops jest liczbą struktur
sembuf w tablicy. Każda struktura sembuf zawiera specyfikację
operacji do wykonania na semaforze.
semop wykonuje się niepodzielnie na zestawach semaforów. Tj., jeśli
jedna z operacji nie może być wykonana, to żadna operacja nie będzie wykonana. Jeżeli nie określono inaczej, proces powinien się zablokować do czasu, póki nie może wykonać wszystkich operacji naraz.
Sembuf zawiera następujące składowe:
unsigned short sem_num;/* indeks semaf w zestawie */ short sem_op; /* określa co zrobić */
Operacje
sem_op
15
sem_op < 0
Jest to ogólna forma polecenia p(); realizuje następujący algorytm:
if(semval >=ABS(sem_op)) {
ustaw semval na (semval – ABS(sem_op)) } else {
if(sem_flg & IPC_NOWAIT) )
natychmiast zwróć -1
else {
czekaj, aż semval osiągnie lub przekroczy ABS(sem_op) następnie odejmij ABS(sem_op), jak powyżej
} }
Funkcja testuje czy semval jest jest wystarczająco duża. Jeżeli jest, to zostaje zmniejszona. Jeżeli nie, to proces czeka, aż semval stanie się wystarczająco duża. Jeżeli jednak w sem_flg ustawiono IPC_NOWAIT
Operacje
sem_op cd
16
sem_op > 0
Jest zgodna z operacją v(); wartość sem_op zostaje dodana do odpowiedniej wartości semval. Jeżeli jakiś proces czeka na nową wartość semafora, to zostanie obudzony.
sem_op = 0
W tym przypadku funkcja będzie czekać, aż wartość semafora stanie się zerem, a wartość semval nie jest zmieniana. Jeśli w sem_flg jest
ustawiony znacznik IPC_NOWAIT, a wartość semval już nie jest zerowa, wtedy funkcja zwraca błąd.
SEM_UNDO
Ustawienie tego znacznika w sem_flg informuje system by
automatycznie cofnął operację gdy proces się zakończy. Proces
utrzymuje wówczas zmienną całkowitą semdj, zmniejszaną o sem_num po każdej operacji semop z flagą SEM_UNDO. Po zakończeniu procesu
semdj jest dodawana do semval.
Wspólna pamięć
17 Zwykle obszary danych dwóch programów są całkowicie rozłączne. Operacje wspólnej pamięci pozwalaja dwóm lub więcej procesom korzystać wspólnie z fragmentu pamięci fizycznej.
init shmget(key_t key, size_t size, int permflag);
Funkcja ta odpowiada msgget i semget – parametry key i permflag mają takie samo znaczenie. Parametr size podaje wymaganą
minimalną wielkość (w bajtach) segmentu pamięci.
Segment pamięci utworzony przez funkcję shmget jest częścią pamięci fizycznej, a nie przestrzenią danych logicznych procesu. Aby z niego
korzystać, proces musi jawnie przyłączyć segment pamięci do swojej przestrzeni danych logicznych, używając do tego funkcji shmat.
Wspólna pamięć:
shmat
18
init shmat(int shmid, const void *daddr, int shmflags);
Funkcja przyłącza segment pamięci identyfikowany przez shmid do przestrzeni adresów logicznych wywołującego procesu. Wartością zwracaną jest adres.
Jeżeli daddr = NULL, to dołączony zostaje segment z pierwszego
dostępnego adresu wybranego przez system. Jeżeli daddr != NULL, a
shmflag ma ustawiony znacznik SHM_RND, to zwracany jest segment o
adresie daddr zaokrąglonym do granicy strony w pamięci. Jeżeli znacznik nie jest ustawiony, to zwraca segment o adresie daddr. Jeżeli wystąpi błąd, to otrzymamy wartość (void *) -1.
Znacznik SHM_RDONLY – segment tylko do odczytu.
Funkcja shmdt(memptr) odłącza przydzielony segment pamięci od przestrzeni adresów logicznych procesu.
Wynik: 0 = powodzenie, -1 = błąd.
Zobacz:
Polecenia ipcs i ipcrm
19 Istnieją dwa polecenia na poziomie powłoki, które umożliwiają użycie urządzeń IPC. Pierwsze z nich, ipcs, drukuje informację o bieżącym stanie urzadzeń IPC. Przykład użycia
$ ipcs
Kolejne polecenie, używane do usuwania urzadzeń IPC z systemu (pod warunkiem, że użytkownik jest właścicielem urządzenia lub SU), to ipcrm. Przykłady:
$ ipcrm -s 0
usuwa np. semafor związany z identyfikatorem 0.
$ ipcrm -S 200