P A M I Ę Ć W S P Ó L N A
1. WSTĘP
Pamięć wspólna to jedno z zaawansowanych urządzeń IPC - najbardziej efektywny mechanizm IPC. Umożliwia ona dwóm albo więcej procesom korzystanie wspólnie z segmentu fizycznej pamięci (współużytkowanie danych). Jest to najszybszy, ale nie koniecznie najprostszy, sposób realizowania komunikacji między procesami. Z reguły cały mechanizm korzystania z pamięci wspólnej polega na zarezerwowaniu (utworzeniu) przez jeden z procesów segmentu pamięci, podając jego żądany rozmiar i prawa dostępu do niego. Następnie dowiązuje go, powodując jego odwzorowanie w bieżący obszar danych procesu. W razie potrzeby proces tworzący segment pamięci wspólnej może go od razu zainicjować (zapisać danymi). Do utworzonego segmentu, jeśli pozwalają na to uprawnienia, mogą sięgać inne procesy, odwzorowując go w swoją przestrzeń danych. Każdy proces uzyskuje dostęp do pamięci wspólnej względem miejsca wyznaczonego przez jego adres dowiązania. Choć dane, do których sięgają procesy, są wspólne, każdy proces używa innego adresu dowiązania. Dostęp do segmentów pamięci wspólnej jest najczęściej kontrolowany za pomocą semaforów. Kiedy dany proces zakończy korzystanie z segmentu pamięci, może ten segment “odwiązać”, tzn. usunąć dowiązanie. Proces, który utworzył segment, może udzielić praw dostępu do segmentu innym procesom. Kiedy wszystkie procesy zakończą korzystanie z segmentu, za jego usunięcie najczęściej odpowiedzialny jest proces, który ten segment utworzył.
Segment pamięci wspólnej musi być najpierw utworzony za pomocą funkcji systemowej- shmget. Jeśli pamięć już istnieje, proces może przyłączyć się do niej za pomocą shmat i używać do własnych, tajemniczych celów. Gdy pamięć nie jest już potrzebna, proces może się odłączyć za pomocą shmdt.
2. TWORZENIE SEGMENTU PAMIĘCI WSPÓLNEJ
Do tworzenia segmentu oraz do uzyskiwania do dostępu do istniejących segmentów stosujemy funkcję systemową shmget
Pliki włączone |
<sys/types.h> <sys/ipc.h> <sys/shm.h> |
||
Prototyp |
int shmget (key_t key, int size, int shmflg); |
||
Zwracana wartość |
Sukces |
Niepowodzenie |
Zamienia errno |
Identyfikator pamięci wspó lnej |
-1 |
||
Segmenty pamięci identyfikowane są za pomocą różnowartościowych identyfikatorów, zwracanych przez funkcję shmget. Jeśli żaden z limitów systemowych nie zostanie przekroczony to funkcja shmget utworzy nowy segment pamięci wspólnej, gdy:
KLUCZ (key)
Klucz jest najważniejszą cechą urządzeń IPC. Klucze to liczby używane do identyfikacji obiektów IPC w systemie UNIX w podobny sposób, jak nazwa pliku identyfikuje plik. Inaczej mówiąc, klucz pozwala na wspólne użycie zasobów IPC (segmentów pamięci wspólnej) przez kilka procesów. Rzeczywisty typ danych klucza jest określany przez zależny od implementacji typ key_t, definiowany w systemowym pliku nagłówkowym <sys / types.h>.
Klucze nie są nazwami plików i mają mniejsze znaczenia. Powinny być wybierane uważnie (nie mogą się powtarzać, aby uniknąć kolizji między programami), być może na podstawie numerów projektów przypisanych różnym projektom na danym komputerze. Unix dostarcza prostą funkcję biblioteczną - ftok, która odwzorowuje nazwę ścieżki pliku na klucz (generuje jego wartość).
|
Procedura zwraca numer klucza w oparciu o informację powiązaną z path pliku. Parametr id też jest uwzględniany i dostarcza dodatkowego poziomu niepowtarzalności – inaczej: ta sama ścieżka dostępu (path) będzie produkować różne klucze dla różnych wartości id. Gdy plik zostanie usunięty, a następnie zastąpiony plikiem o tej samej nazwie to zwracane klucze powinny się różnić. Procedura ftok zawodzi i zwraca –1 (czyli błąd), jeśli path pliku nie istnieje. Procedura ta jest prawdopodobnie najbardziej użyteczna w zastosowaniach, gdzie funkcje IPC są używane w manipulacji nazwami plikami lub kiedy dana jest nazwa pliku, będąca istotną, trwałą i niezmienną częścią aplikacji.
Program używa klucza do tworzenia obiektów IPC (w naszym przypadku pamięci wspólnej) lub wzmocnienia dostępu do już istniejącego obiektu. Obie opcje są wywoływane za pomocą operacji IPC get (podobnej do plikowych creat lub open). Wynikiem tej operacji jest całkowity identyfikator urządzenia IPC - coś jak deskryptor pliku, ale w odróżnieni od niego identyfikator jest unikatowy. Różne procesy muszą używać tej samej wartości dla tego samego obiektu IPC.
ROZMIAR (size
) SEGMENTU PAMIĘCIParametr size podaje wymaganą minimalną wielkość (w bajtach) segmentu pamięci. Jeżeli funkcji shmget użyje się w celu uzyskania dostępu do istniejącego segmentu pamięci wspólnej, wartość parametru drugiego (size) może być równa zero, ponieważ rozmiar segmentu został już wcześniej zadeklarowany przez proces, który go utworzył.
Limity pamięci wspólnej:
shmflg
Trzeci argument shmget służy do określania warunków tworzenia segmentu (np. IPC_CREAT, IPC_EXCL), a także praw dostępu (zapisywanych na 9 najmłodszych bitach tego argumentu). Warunki generowania segmentu oraz prawa dostępu podaje się za pomocą wyrażeń składających się z elementów połączonych operatorem logicznym OR ( | )
Funkcja systemowa shmget zakończona powodzeniem powoduje utworzenie segmentu pamięci wspólnej i zwrócenie do wywołującego ją procesu całkowitego identyfikatora tego segmentu. Nie umożliwia to jednak procesowi tworzącemu korzystania z zarezerwowanej pamięci. Aby proces mógł z niej korzystać musi dowiązać utworzony segment pamięci, korzystając z osobnego wywołania systemowego (ale o tym później :-).
Np. identyf = shmget((key_t)0x10,100,IPC_CREAT|IPC_EXCL|0666) Gdzie pierwszy argument shmget to klucz pamięci wspólnej, drugi rozmiar segmentu pamięci = 100 bajtów. Jeśli wywołanie procedury będzie poprawne to identyf (-ikator) będzie zawierał wartość nieujemną.
W chwili tworzenia segmentu pamięci wspólnej powstaje i jest inicjowana systemowa struktura danych typu smhid_ds, której definicja znajduje się w pliku nagłówkowym <sys / shm.h>. Zamieszczone są w niej wszystkie informacje administracyjne na temat utworzonego (używanego) segmentu np. W chwili tworzenia segmentu pamięci polom cuid i uid tej wewnętrznej struktury przypisywane są efektywne identyfikatory użytkownika procesu wywołującego, a polom cgid oraz gid efektywne identyfikatory grup procesu wywołującego. Bity uprawnień zapisane w polu mode są inicjowane wartościami określanymi za pomocą argumentu shmflg funkcji shmget. Efektywne identyfikatory użytkownika i grupy w połączeniu z mode określają prawa dostępu do segmentu pamięci wspólnej. Z kolei parametr size funkcji shmget przypisywany jest polu shm_segsz. Pola shm_lpid, shm_nattch, shm_atime oraz shm_dtime wypełnione są zerami (ich wartości ulegają zmianie w trakcie używania utworzonych segmentów pamięci wspólnej). W polu shm_ctime jest wartość odpowiadająca bieżącej godzinie.
Funkcja shmget zakończona niepowodzeniem zwraca wartość –1 i ustawia errno na kod odpowiadający napotkanemu błędowi. Funkcji peeror zwraca tekst komunikatu , zamiast numeru błędu. Wartości errno oraz treści komunikatów i opis błędu znajdziecie się w książce pana Grey’a na stronie 216 (tabela 8.3).
/*** Program, który
próbuje utworzyć dwa segmenty pamięci wspólnej o różnych rozmiarach ***/# include <stdio.h>
# include <unistd.h>
# include <sys/types.h>
# include <stdlib.h>
# include <sys/ipc.h>
# include <sys/shm.h>
main (void) {
key_t klucz = 15;
int identyfikator1, identyfikator2;
if ((identyfikator1 = shmget(klucz,1000,0644|IPC_CREAT)) == -1) {
// 0644 to prawa dostępu => rw-r--r--
perror (”shmget identyfikator1”);
exit(1);
}
printf(”Pierwszy identyfi
kator pamięci wspólnej: %d\n”,identyfikator1);if ((identyfikator2 = shmget(IPC_PRIVATE, 20, 0644)) == -1) {
perror (”shmget identyfikator2”);
exit(2);
}
printf(”Drugi ide
ntyfikator pamięci wspólnej: %d\n”,identyfikator2);exit(0);
}
Program uruchomiono dwukrotnie. Pierwsze uruchomienie spowodowało utworzenie segmentów pamięci wspólnej o identyfikatorach 400 i 501. Pierwsze wywołanie funkcji shmget wygenerowało segment o numerze 400, ponieważ wartość klucza (=15) nie była skojarzona z żadnym wcześniej zarezerwowanym segmentem. W drugim wywołaniu shmget użyto znacznika IPC_PRIVATE, co spowodowało utworzenie drugiego segmentu, identyfikowanego wartością 501. Drugie uruchomienie programu dało nieco inne rezultaty. Pierwsze wywołanie funkcji shmget spowodowało zwrócenie identyfikatora segmentu pamięci wspólnej utworzonego w pierwszym przebiegu programu, ponieważ ten segment już istniał dla wartości klucza = 15 (0x0000000f). Drugie wywołanie shmget (ze znacznikiem IPC_PRIVATE) wciąż generuje kolejne niepowtarzalne identyfikatory segmentów pamięci wspólnej. Wywołanie polecenia ipcs ujawniło, że wartość klucza obu różnych segmentów pamięci wspólnej przy ustawionym znaczniku IPC_PRIVATE jest zerowa (0x0x00000000).
3. STEROWANIE PAMIĘCIĄ WSPÓLNĄ
Pliki włączone |
<sys/types.h> <sys/ipc.h> <sys/shm.h> |
||
Prototyp |
int shmctl (int shmid, int cmd, struct shmid_ds *buf); |
||
Zwracana wartość |
Sukces |
Niepowodzenie |
Zamienia errno |
0 |
-1 |
||
Wywołanie funkcji systemowej shmctl umożliwia przeprowadzanie różnych operacji na istniejących segmentach pamięci wspólnej, a także na systemowej strukturze danych (Inaczej: funkcja ta służy do wykonywania operacji kontrolnych, które mogą być używane do uzyskania informacji o stanie lub do ustawiania wartości kontrolnych). Funkcja ta wymaga określenia trzech argumentów:
Stałe symboliczne, których można używać w wywołaniach funkcji shmctl, oraz uzyskiwane dzięki nim operacje:
Np. shmctl(identyfikator1,IPC_RMID,NULL), spowoduje to skasowanie segmentu pamięci wspólnej o identyfikatorze równym identyfikator1.
W celu usunięcia segmentów pamięci wspólnej można też użyć polecenia ipcrm – z poziomu wiersza poleceń.
Wywołanie systemowe shmctl zakończone sukcesem zwraca wartość 0, w przeciwnym razie –1 i ustawia errno na kod napotkanego błędu (Lista błędów wraz z ich interpretacją znajduje się w tabeli 8.5 na str. 219 w książce Grey’a).
4. OPERACJE NA SEGMENTACH PAMIĘCI WSPÓLNEJ
Umożliwiają to dwie funkcje shmat i shmdt.
4.1.
Segment pamięci utworzony przez funkcję shmget jest częścią pamięci fizycznej, a nie przestrzenią danych logicznych procesu. Aby z niego korzystać, proces (i każdy proces współpracujący) musi jawnie przyłączyć (dowiązać, odwzorować) segment pamięci wspólnej do swojej przestrzeni danych logicznych. W tym celu musi użyć pierwszej z powyższych funkcji – shmat.
Pliki włączone |
<sys/types.h> <sys/ipc.h> <sys/shm.h> |
||
Prototyp |
void *shmat (int shmid, void *shmaddr, int shmflg);
|
||
Zwracana wartość |
Sukces |
Niepowodzenie |
Zamienia errno |
Wskaźnik do segmentu danych, do którego jest dowiązana pamięć wspólna |
-1 |
Argument shmid to poprawny identyfikator segmentu pamięci (który pochodzi z wywołani funkcji shmget). Shmaddr daje procesowi wywołującemu pewną swobodę w określeniu położenia (wyborze adresu) segmentów pamięci wspólnej. Jeśli zastosuje się wartość niezerową argumentu shmaddr, shmat użyje jej jako adresu dowiązania. Jeśli argument shmaddr będzie równy zero (NULL), system sam wybierze adres dowiązania z pierwszego dostępnego adresu. W większości sytuacji lepiej jest korzystać z wartości 0 i wybór adresu dowiązania pozostawić systemowi. Trzeci argument (shmflg) służy do określania uprawnień do segmentu pamięci wspólnej oraz specjalnych warunków dowiązania. Warunkiem takim może być wyrównany (aligned) adres dowiązania – stanowiący wielkość pewnej liczby całkowitej, lub utworzenie segmentu w trybie tylko do odczytu. Domyślnie dowiązane segmenty dostępne są w trybie zapisu i odczytu. W razie potrzeby można w argumencie shmflg dołączyć operatorem OR znacznik SHM_RDONLY, który spowoduje oznaczenie segmentu jako tylko do odczytu.
Wartość shmaddr w połączeniu z wartością shmflg jest używana przez system do określenia adresu dowiązania zgodnie z poniższym algorytmem.
Czy wartość shmaddr Czy znacznik SHM_RND
Dowiązanie do
podanego adresu
Czy użyto stałej
SHM_SHARE_MMU?
Dowiązanie do adresu najbliższej strony
dowiązanie do pierwszego (shmaddr - (shmaddr % SHMLBA))
dostępnego adresu
wyrównanego
dowiązanie do pierwszego
wolnego adresu
Jeśli użyje się znacznika SHM_SHARE_MMU, uprawnienia dowiązanego segmentu będą determinowane przez uprawnienia określone wywołaniem shmget podczas tworzenia segmentu. Zastosowanie tego znacznika powoduje, że dowiązanie zostanie wykonane pod pierwszym dostępnym wyrównanym adresem. Znacznik SHM_RND wpływa na sposób, w jaki shmat traktuje niezerową wartość parametru shmaddr. Jeśli jest on ustawiony, wywołanie zaokrągli shmaddr do granicy stron w pamięci. Za rozmiar strony przyjmowana jest wartość stałej SHMLBA. Jeśli znacznik SHM_RND nie jest ustawiony to funkcja shmat będzie używać dokładnej wartości shmaddr.
Jeśli shmat zakończy się powodzeniem zwróci adresu wykonanego dowiązania, w przeciwnym wypadku zwróci wartość –1 oraz ustawi errno na kod napotkanego błędu (Listę błędów wraz z interpretacją znajdziecie w tabeli 8.7 na str. 222 w książce Grey’a).
4.2.
Druga funkcja (shmdt) jest odwrotnością pierwszej (shmat) i służy do usuwania dowiązań segmentów pamięci wspólnej do obszaru danych (przestrzeni adresów logicznych) procesu.
Pliki włączone |
<sys/types.h> <sys/ipc.h> <sys/shm.h> |
||
Prototyp |
int shmdt (void *shmaddr);
|
||
Zwracana wartość |
Sukces |
Niepowodzenie |
Zamienia errno |
0 |
-1 |
||
Funkcja shmdt wymaga podania tylko jednego argumentu (shmaddr) będącego wskaźnikiem na wcześniej dowiązany segment. Zakończona sukcesem zwróci zero, a w przeciwnym wypadku –1 i ustawi errno na kod napotkanego błędu (Opis błędu, bo jest tylko jeden, wraz z interpretacją znajdziecie w tabeli 8.9 na str. 224 w książce Grey’a).
4.3. PR
ZYKŁADOWY PROGRAMW linii 14 tworzony jest prywatny segment pamięci o długości 30 bajtów. Natomiast w linii 18 jest on odwzorowany w przestrzeń danych procesu za pomocą pierwszego wolnego adresu (wybranego przez system). W liniach 23,24 program wyświetla uzyskany adres dowiązania wraz z adresami segmentów etext, edata i end. Dalej (linie 25-28) przypisuje do wskaźnika na ciąg znaków odwołanie do segmentu pamięci wspólnej, a następnie zapełnia nowe miejsce szeregiem dużych liter. Za pomocą funkcji fork (l. 31) tworzony jest proces potomny. Wyświetla on zawartość segmentu pamięci wspólnej (45) i przekształca litery w nim zawarte na małe (46,47). Po zakończeniu tej konwersji usuwa dowiązanie (48) i kończy pracę. Proces macierzysty pozostaje przez 5 s nieaktywny (37), po czym ponownie wyświetla zawartość pamięci wspólnej (38,39) – tym razem będzie to tekst pisany małymi literami. Następnie usuwa dowiązanie (41) i pozbywa się całego segmentu (42).
Wyniki jakie otrzymano po uruchomieniu programu:
$ program
Adresy w procesie macierzystym
pamięć wspólna:
EF7F0000, etext: 10B50, edata: 20CAC, end 20CD0Pamięć w procesie macierzystym przed wykonani
em fork: ABCDEFGHIJKLM NOPQRSTUVWXYZPamięć
w procesie potomnym po fork: ABCDEFGHIJKLMNOPQRSTUVWXYZ$
Pamięć
w procesie macierzystym po fork: abcdefghijklmnopqrstuvwxyzProces macierzysty usu
wa pamięć wspólną.
Jak widzimy adres wybrany przez system na segment pamięci wspólnej nie zawiera się w segmencie danych ani w segmencie tekstu bieżącego procesu. Proces potomny włącza się do akcji (za sprawą fork) uzyskując dostęp do segmentu bez konieczności wywołania funkcji shmget i shmat. Widzimy też, że zmiany dokonywane, w segmencie pamięci wspólnej, przez potomka widziane są z poziomu procesu macierzystego nawet, gdy potomek usunie dowiązanie i zakończy działanie.
5. INNE INFORMACJE
Na operacji przeprowadzane na pamięci wspólnej trzeba założyć semafory, aby mieć pewność, że proces zakończy to co zaczął robić.
Do operacji zapisu i odczytu z pamięci wspólnej można użyć odpowiednio poleceń write i read.