Programowanie współbieżne i rozproszone/PWR Ćwiczenia shm: Różnice pomiędzy wersjami
Nie podano opisu zmian |
Nie podano opisu zmian |
||
Linia 57: | Linia 57: | ||
A oto typowy fragment programu działający na segmencie pamięci dzielonej: | A oto typowy fragment programu działający na segmencie pamięci dzielonej: | ||
<Source> | <Source> | ||
int id; | |||
char* adr; | |||
if ((id=shmget (KLUCZ, 100, 0666|IPC_CREAT)) == -1) /* Stworzenie segementu */ | |||
syserr ("blad"); | |||
if ((adr=shmat(id, 0, 0)) < 0) /* Przyłączenie */ | |||
syserr ("blad") | |||
</Source> | </Source> | ||
Linia 72: | Linia 72: | ||
co powoduje umieszczenie napisu w segmencie pamięci dzielonej. | co powoduje umieszczenie napisu w segmencie pamięci dzielonej. | ||
< | Należy zauważyć, że ten sam segment pamięci dzielonej może zostać przyłączony wielokrotnie do tego samego procesu, w różne miejsca pamięci wirtualnej, na przykład: | ||
<Source> | |||
int id; | |||
char* adr, adr2; | |||
if ((id=shmget (KLUCZ, 100, 0666|IPC_CREAT)) == -1) /* Stworzenie segementu */ | |||
syserr ("blad"); | |||
if ((adr=shmat(id, 0, 0)) < 0) /* Przyłączenie */ | |||
syserr ("blad") | |||
if ((adr2=shmat(id, 0, 0)) < 0) /* Przyłączenie */ | |||
syserr ("blad") | |||
</Source> | |||
Później w programie można napisać na przykład: | |||
<Source> | |||
strcpy (adr, "Hello"); | |||
printf ("%s\n", adr2); | |||
</Source> | |||
uzyskując na wyjściu napis "Hello". | |||
=== Odłączanie segmentu pamięci dzielonej === | |||
Gdy proces przestaje korzystać z przyłączonego segmentu należy go odłączyć od przestrzeni adresowej za pomocą funkcji systemowej <tt>shmdt</tt>: | |||
<Source> | |||
przestrzeni adresowej | int shmdt (const void *adres) | ||
</Source> | |||
W odróżnieniu od pozostałych funkcji działających na zasobach IPC (z wyjątkiem rodziny funkcji <tt>get</tt>), funkcja ta nie bierze jako argumentu identyfikatora zasoba. Powód jest prosty: jeśli ten sam segment jest przyłączony w różne miejsca przestrzeni adresowej tego procesu, to identyfikator nie określa jednoznacznie, który segment chcemy odłączyć. Jednoznaczną informacje dostarcza adres. W przypadku błędu wynikiem funkcji jest -1 w przeciwnym razie jest to wartość 0. | |||
Warto zauważyć, że po odłączeniu segmentu pamięci dzielonej od procesu dane w segmencie pozostają niezmienione, nawet wówczas, gdy nie jest on już przyłączony do żadnego procesu. Dzięki temu dane pozostawione w segmencie pamięci dzielonej przez pewien proces mogą zostać odczytane przez proces, który działa w zupełnie innym czasie niż proces nadawcy. | |||
=== Usuwanie segmentu pamięci dzielonej === | |||
Do wykonywania czynności zarządzających segmentem pamięci dzielonej służy funkcja systemowa <tt>shmctl</tt>. Szczegółowe informacje o nie znajdują się na stronach podręcznika ''man''. Dla nas istotna jest tylko jedna postać: | |||
<Source> | |||
int shmctl (int ident, IPC_RMID, 0) | |||
</Source> | |||
powodująca usunięcie segmentu pamięci dzielonej. Usuwanie to nie jest jednak natychmiastowe. Tak naprawdę segment jest usuwany fizycznie dopiero wówczas, gdy nie jest przyłączony do żadnego procesu (z tego powodu istotne jest odłączanie niepotrzebnych już segmentów). Warto zaznaczyć, że funkcja systemowa <tt>exit</tt> odłącza wszystkie przyłączone do procesu segmenty pamięci dzielonej. Podobnie zachowuje się funkcja systemowa <tt>exec</tt>, gdyż pamięć wirtualna jest na skutek jej wykonania zupełnie podmieniana. | |||
segmenty | |||
Wersja z 12:30, 17 paź 2006
Tematyka zajęć
Segmenty pamięci dzielonej
Literatura
Scenariusz
Wstęp
Segmenty pamięci dzielonej to trzeci, ostatni już mechanizm z pakietu IPC. Jak zwykle do segmentów pamięci dzielonej stosują się wszystkie uwagi dotyczące mechanizmów IPC, omówione przy okazji kolejek komunikatów. Dotychczas wszystkie programy, które pisaliśmy w środowisku Unix składały się z procesów, z których każdy korzystał z własnych zmiennych. Zmienne współdzielone, dostępne dla wielu procesów nie występowały, a synchronizacja odbywała się poprzez wymianę komunikatów bądź to za pomocą łączy bądź kolejek komunikatów. Pewne odstępstwo od tej zasady nastąpiło z chwilą wprowadzenia semaforów, ale ponieważ to system dba o zarządzanie nimi i przydzielenie im miejsca w pamięci, więc programista nie musiał się tym zajmować. Dziś chcemy korzystać ze wspólnej pamięci w postaci zmiennych dostępnych dla wielu procesów.
Choć procesy uniksowe uruchamiane na tym samym komputerze wykonują się we wspólnej pamięci, to jednak z logicznego punktu widzenia przestrzenie adresowe różnych procesów są rozłączne. Aby umożliwić korzystanie na przykład ze wspólnych zmiennych wprowadzono, początkowo do Uniksa Systemu V, a później już do wszystkich wersji systemu, mechanizm segmentow pamieci dzielonej.
Segment pamięci dzielonej jest fragmentem pamięci, który może być podpinany do przestrzeni adresowych różnych procesów. Zarządzaniem segmentami pamięci dzielonej zajmuje się system, ale to programista jest odpowiedzialny za utworzenie segmentu o potrzebnej mu wielkości oraz jego podpięcie pod pewien adres przestrzeni adresowej (najczęściej wybrany przez system operacyjny). Jeśli ten sam segment zostanie przyłączony do przestrzeni adresowych różnych procesów, to wszelkie dane (a więc zmienne) w nim przechowywanie stają się widoczne i dostępne dla tych procesów. Oczywiście dostęp do nich wymaga wówczas synchronizacji, ale mamy już do tego odpowiednie mechanizmy --- na przykład semafory.
Operacje zapisu i odczytu z segmentu pamięci wirtualnej do zwykłe operacje dostępu do pamięci. Dzięki temu są one realizowane szybko i wydajnie, znacznie szybciej niż na przykład wysłanie komunikatu za pomocą kolejki komunikatów, które wymaga kopiowania komunikatu między przestrzenią adresową nadawcy, jądra oraz odbiorcy. Tutaj występuje zapis po stronie nadawcy i odczyt po stronie odbiorcy.
Ponieważ segment pamięci jest zasobem IPC, trzeba go najpierw utworzyć lub uzyskać dostęp do segmentu już istniejącego. Identyfikacja segmentów odbywa się jak zwykle za pomocą ustalonego klucza, a następnie identyfikatora przekazywanego przez funkcję get, która w przypadku segmentów pamięci dzielonej nazywa się shmget. Ale to jeszcze nie koniec. Aby proces mógł coś zapisać do segmentu lub z niego odczytać, segment musi zostać podpięty do przestrzeni adresowej. Podobnie, gdy segment staje się niepotrzebny w danym procesie należy go odłączyc od przestrzeni adresowej. Dopóki jednak segment jest przyłączony do jakiekolwiek procesu, dane w nim zapisywane są utrzymywane. Segment, który jest już niepotrzebny w żadnym procesie należy usunąć, jak każdy zasób IPC.
Tak wygląda typowy scenariusz pracy z segmentami pamięci dzielonej. Do jego realizacji służą następujące funkcje systemowe:
- shmget --- tworząca segment pamięci dzielonej
- shmat --- przyłączająca segmentu pamięci dzielonej do przestrzeni adresowej procesu
- shmdt --- odlączająca segmentu pamięci dzielonej od przestrzeni adresowej procesu
- shmctl --- realizująca operacje sterujące na segmencie pamięci dzielonej
Program w C wykorzystujący mechanizm pamięci dzielonej musi wykorzystać następujące pliki nagłówkowe:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
Tworzenie (uzyskanie dostępu do) segmentu pamięci dzielonej
Do utworzenia segmentu pamięci dzielonej służy funkcja systemowa shmget:
int shmget (key_t klucz, int rozmiar, int flagi)
- Pierwszym parametrem tej funkcji jest jak zwykle klucz zasobu
- Trzecim parametrem są opcje, które jak zwykle określają prawa dostępu do pamięci oraz ewentualne opcje IPC_CREAT, IPC_EXCL lub IPC_PRIVATE
- Wynikiem funkcji shmget jest identyfikator segmentu pamięci dzielonej lub -1 w przypadku błędu
Parametrem specyficznym dla segmentów pamięci dzielonej jest rozmiar, który określa rozmiar tworzonego segmentu pamięci dzielonej. Ma on znaczenie jedynie wtedy, gdy segment jest faktycznie tworzony. Jeśli funkcja powoduje jedynie uzyskanie dostępu do już istniejącego segmentu, to parametr ten jest ignorowany. Tak naprawdę, utworzony segment będzie miał będzie składał się z pewnej (odpowiednio dużej) liczby stron, więc jego rozmiar jest zawsze wielokrotnością rozmiaru strony.
Przyłączanie segmentu pamięci dzielonej
Do przyłączenia istniejącego segmentu pamięci dzielonej do przestrzeni adresowej procesu służy funkcja systemowa shmat:
void *shmat (int ident, const void *adres, int flagi)
- Pierwszy parametr jest identyfikatorem segmentu pamięci dzielonej.
- Drugi parametr jest to adres wirtualny, pod który zostanie przyłączony segment. Wartość tego parametru może być:
- zerowa, wtedy system sam dobiera odpowiednio adres. Taka postać operacji shmat jest zdecydowanie zalecana, a w niektórych wariantach Uniksa jest nawet jedyną dopuszczalną!
- niezerowa, wtedy system próbuje przyłączyć segment pod wskazany adres. System może, w zależności od opcji podanych na trzecim argumencie, dokonać korekty tego adresu, ale nie będziemy się tym zajmować, stosując zawsze zalecaną wersję z wartością parametru adres równą zero.
- Trzeci parametr to opcje, które mają wpływ na sposób przyłączenia. Jest wśród nich między innymi opcja SHM_RDONLY, wymuszająca przyłączenie w trybie jedynie do odczytu
Wynikiem funkcji systemowej jest adres, pod który przyłączono segment lub wartość (void *) -1 w przypadku błędu.
A oto typowy fragment programu działający na segmencie pamięci dzielonej:
int id;
char* adr;
if ((id=shmget (KLUCZ, 100, 0666|IPC_CREAT)) == -1) /* Stworzenie segementu */
syserr ("blad");
if ((adr=shmat(id, 0, 0)) < 0) /* Przyłączenie */
syserr ("blad")
Później w programie można napisać na przykład:
strcpy (adr, "Hello")
co powoduje umieszczenie napisu w segmencie pamięci dzielonej.
Należy zauważyć, że ten sam segment pamięci dzielonej może zostać przyłączony wielokrotnie do tego samego procesu, w różne miejsca pamięci wirtualnej, na przykład:
int id;
char* adr, adr2;
if ((id=shmget (KLUCZ, 100, 0666|IPC_CREAT)) == -1) /* Stworzenie segementu */
syserr ("blad");
if ((adr=shmat(id, 0, 0)) < 0) /* Przyłączenie */
syserr ("blad")
if ((adr2=shmat(id, 0, 0)) < 0) /* Przyłączenie */
syserr ("blad")
Później w programie można napisać na przykład:
strcpy (adr, "Hello");
printf ("%s\n", adr2);
uzyskując na wyjściu napis "Hello".
Odłączanie segmentu pamięci dzielonej
Gdy proces przestaje korzystać z przyłączonego segmentu należy go odłączyć od przestrzeni adresowej za pomocą funkcji systemowej shmdt:
int shmdt (const void *adres)
W odróżnieniu od pozostałych funkcji działających na zasobach IPC (z wyjątkiem rodziny funkcji get), funkcja ta nie bierze jako argumentu identyfikatora zasoba. Powód jest prosty: jeśli ten sam segment jest przyłączony w różne miejsca przestrzeni adresowej tego procesu, to identyfikator nie określa jednoznacznie, który segment chcemy odłączyć. Jednoznaczną informacje dostarcza adres. W przypadku błędu wynikiem funkcji jest -1 w przeciwnym razie jest to wartość 0.
Warto zauważyć, że po odłączeniu segmentu pamięci dzielonej od procesu dane w segmencie pozostają niezmienione, nawet wówczas, gdy nie jest on już przyłączony do żadnego procesu. Dzięki temu dane pozostawione w segmencie pamięci dzielonej przez pewien proces mogą zostać odczytane przez proces, który działa w zupełnie innym czasie niż proces nadawcy.
Usuwanie segmentu pamięci dzielonej
Do wykonywania czynności zarządzających segmentem pamięci dzielonej służy funkcja systemowa shmctl. Szczegółowe informacje o nie znajdują się na stronach podręcznika man. Dla nas istotna jest tylko jedna postać:
int shmctl (int ident, IPC_RMID, 0)
powodująca usunięcie segmentu pamięci dzielonej. Usuwanie to nie jest jednak natychmiastowe. Tak naprawdę segment jest usuwany fizycznie dopiero wówczas, gdy nie jest przyłączony do żadnego procesu (z tego powodu istotne jest odłączanie niepotrzebnych już segmentów). Warto zaznaczyć, że funkcja systemowa exit odłącza wszystkie przyłączone do procesu segmenty pamięci dzielonej. Podobnie zachowuje się funkcja systemowa exec, gdyż pamięć wirtualna jest na skutek jej wykonania zupełnie podmieniana.