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ść).

 

 

# include <sys/ipc.h>

key_t ftok (const char *path, int id);

 

 

   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ĘCI

    Parametr 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 identyfikator pamięci wspólnej: %d\n”,identyfikator1);

if ((identyfikator2 = shmget(IPC_PRIVATE, 20, 0644)) == -1) {

perror (”shmget identyfikator2”);

exit(2);

}

printf(”Drugi identyfikator 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:

  1. shmid – poprawny identyfikator segmentu pamięci wspólnej wygenerowany wcześniej funkcja shmget.
  2. cmd – określa operację wykonywaną funkcją shmctl.
  3. buf – wskaźnik na strukturę typu shmid_ds.

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


jest równa (* void) 0? jest ustawiony?

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.  PRZYKŁADOWY PROGRAM

  1. # include <stdlib.h>
  2. # include <sys/types.h>
  3. # include <sys/ipc.h>
  4. # include <sys/shm.h>
  5. # include <stdio.h>
  6. # include <unistd.h>
  7. # include <string.h>
  8. # define ROZMIAR 30
  9. extern etext, edata, end;
  10. main (void) {
  11. pid_t pid;
  12. int identyf;
  13. char c, *adres, *s;
  14. if ((identyf = shmget(IPC_PRIVATE,ROZMIAR,IPC_CREAT|0666))<0){
  15. perror(”niepowodzenie funkcji shmget”);
  16. exit(1);
  17. }
  18. if ((adres = (char *) shmat(identyf,0,0)) == (car *) -1){
  19. perror(”shmat: proces macierzysty”);
  20. exit(2);
  21. }
  22. printf(”Adresy w procesie macierzystym\n\n”);
  23. printf(”pamięć wspólna: %X ,etext: %X, edata: %X, end %X\n\n”,
  24. adres, &etext, &edata, &end);
  25. s = adres; // teraz s wskazuje na pamięć wspól
  26. for (c=”A”; c<=”Z”; ++c) // umieść jakieś dane
  27. *s++ = c;
  28. *s = NULL; // zakończ sekwencję
  29. printf(”Pamięć w procesie macierzystym przed wykonaniem fork:
  30. %s\n”, adres);
  31. pid = fork();
  32. switch (pid){
  33. case –1:
  34. perror(”fork”);
  35. exit(3);
  36. defaulut:
  37. sleep(5); // niech potomek się skończy
  38. printf(”\nPamięć w procesie macierzystym po fork: %s\n”,
  39. adres);
  40. printf(”Proces macierzysty usuwa pamięć wspólną.\n”);
  41. shmdt(adres);
  42. shmctl(identyf, IPC_RMID, (struct shmid_ds *) 0);
  43. exit(0);
  44. case 0:
  45. printf(”Pamięć w procesie potomnym po fork: %s\n”, adres);
  46. for (; *adres; ++adres) //zmień pamięć wspólną
  47. *adres += 32;
  48. shmdt(adres);
  49. exit(0);
  50. }
  51. }

     W 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 20CD0

Pamięć w procesie macierzystym przed wykonaniem fork: ABCDEFGHIJKLM NOPQRSTUVWXYZ

Pamięć w procesie potomnym po fork: ABCDEFGHIJKLMNOPQRSTUVWXYZ

$

Pamięć w procesie macierzystym po fork: abcdefghijklmnopqrstuvwxyz

Proces macierzysty usuwa 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.