Do spisu tresci tematu 3

3.2.3 Pamiec dzielona




Spis tresci


Wprowadzenie

Pamiec dzielona (shared memory), zwana tez pamiecia wspolna, jest najszybsza metoda komunikacji miedzyprocesowej. Istota tego predkiego przesylania danych polega na nieprzesylaniu ich wcale. Pamiec dzielona umozliwia wielu procesom wspoldzielenie czesci swojej wirtualnej przestrzeni adresowej, a co za tym idzie, komunikowanie sie w sposob bezposredni. Zapisywanie oraz odczytywanie danych z takich segmentow odbywa sie przy pomocy zwyklych instrukcji maszynowych, bez uzycia zadnych funkcji systemowych - bez udzialu jadra. Jedyny konieczny narzut systemowy ogranicza sie do zapewnienia wylacznosci zapisu i odczytu danych, co czyni sie zwykle za pomoca semaforow lub komunikatow.
Komunikacja w systemie pamieci wspolnej jest symetryczna i dwukierunkowa. Informacja nie musi byc w zaden sposob formatowana (tzn. nie wymaga sie naglowkow czy dodatkowych parametrow mowiacych o jej dlugosci, rodzaju, itp.). Podczas calego cyklu przesylania danych wiadomosc moze nie byc w ogole kopiowana, a jezeli nie zdecydujemy sie na taka implementacje, to kopiowanie bedzie sie odbywac w obrebie jednej przestrzeni adresowej i bedzie bardzo szybkie. Zalety te dotycza oczywiscie procesow, ktore maja dostep do wspolnej sprzetowej pamieci. Systemy rozproszone ograniczaja jej stosowanie.
Mimo to wymienione cechy stawiaja pamiec dzielona wysoko wsrod metod komunikowania sie procesow.


Stale i struktury danych


Wprowadzenie

W implementacji pamieci wspolnej uzywanych jest wiele roznych stalych i struktur danych. Wazniejsze z nich to struktury: shmid_ds, shminfo, shm_info, vm_area_struct oraz globalna tablica shm_segs. Struktury shminfo i shm_info sa wykorzystywane tylko przez funkcje shmctl() do przekazywania danych systemowych i statystycznych. Tablica shm_segs[SHMMNI] trzyma wskazniki do strukur shmid_ds , z ktorych kazda zawiera opis jednego segmentu pamieci dzielonej. Ma ona miedzy innymi tablice wskaznikow do stron odpowiadajacych danemu segmentowi oraz liste struktur vm_area_struct, odpowiadajacych podlaczeniom tego segmentu do procesow. Po szczegolowy opis tej ostatniej struktury odsylam do czesci poswieconej pamieci w Linuxie.

Ponizszy rysunek przedstawia wyzej omowione zaleznosci:

(schemat)


Stale

Ponizej przedstawiam najistotniejsze stale - ograniczenia systemowe dotyczace segmentow pamieci dzielonej zdefiniowane w pliku include/asm/shmparam.h:
#define SHMMNI 128     /* maksymalna liczba segmentow dzielonych w systemie     */
#define SHMMAX 16MB    /* maksymalna wielkosc segmentu pamieci wspolnej         */
#define SHMMIN 1B      /* minimalna wielkosc segmentu pamieci dzielonej         */
                       /* segment nie moze zajac mniej niz 4KB - strone pamieci */
#define SHMALL 4194304 /* maksymalna liczba stron pamieci dzielonej w systemie  */


Struktura shmid_ds

Oto dokladna definicja podstawowej struktury uzywanej do opisu segmentow pamieci wspolnej zdefiniowanej w pliku include/linux/shm.h. Jedna instancja tej struktury opisuje wlasciwosci jednego segmentu.
struct shmid_ds {
  struct ipc_perm shm_perm;   /* struktura zdefiniowana w pliku linux/ipc.h       *
                               * zawiera informacje o prawach dostepu do segmentu *
                               * a takze dane o jego zalozycielu                  */
  int            shm_segsz;   /* wielkosc segmentu w bajtach  */
  time_t         shm_atime;   /* czas ostatniego przylaczenia segmentu */
  time_t         shm_dtime;   /* czas ostatniego odlaczenia segmentu   */
  time_t         shm_ctime;   /* czas ostatniej operacji na segmencie  */
  unsigned short shm_cpid;    /* pid zalozyciela segmentu */
  unsigned short shm_lpid;    /* pid procesu, ktory ostatnio dzialal na segmencie */
  short          shm_nattch;  /* liczba biezacych przylaczen segmentu */
  
  unsigned short shm_npages;  /* wielkosc segmentu w stronach */
  unsigned long  *shm_pages;  /* tablica wskaznikow do stron  */
  struct vm_area_struct *attaches; /* lista przylaczen segmentu    */
};


Funkcje i ich implementacja


Wprowadzenie

Funkcje systemowe sluzace do obslugi segmentow pamieci dzielonej sa zgodne z regulami ustanowionymi dla calego pakietu IPC systemu UNIX, ktorego pamiec dzielona jest skladnikiem. Istnieja cztery takie funkcje:

Funkcja shmget()

Funkcja ta sluzy do utworzenia nowego segmentu pamieci dzielonej lub uzyskania identyfikatora, a tym samym dostepu, do segmentu juz istniejacego.
DEFINICJA: int shmget(key_t key, int size, int shmflg)
    WYNIK: w przypadku sukcesu identyfikator segmentu pamieci dzielonej 
           gdy blad -1 oraz na zmiennej errno:
                                 EACCESS (brak praw)
                                 EEXIST  (segment o podanym kluczu istnieje,
                                          wiec niemozliwe jest jej utworzenie)
                                 EIDRM   (segment jest zaznaczony do usuniecia)
                                 ENOENT  (segment nie istnieje)
                                 ENOMEM  (brak pamieci na segment)
                                 EINVAL  (zla wielkosc segmentu)
Pierwszym argumentem funkcji jest wartosc klucza, porownywana z wartosciami kluczy istniejacych segmentow. Zwracany jest identyfikator segmentu (odpowiednik plikowego deskryptora) o podanym kluczu, przy czym: Argument size nie moze przekraczac wartosci stalej SHMMAX dla segmentow tworzonych, albo rozmiaru segmentu juz istniejacego, ale nie musi byc rowny tej ostatniej wartosci (oznacza to, ze proces moze zazadac podlaczenia tylko czesci segmentu).

Dzialanie funkcji jest analogiczne do odpowiednich funkcji na semaforach oraz kolejkach komunikatow (patrz opis algorytmu *get w rozdziale "Cechy wspolne IPC").
Najpierw sprawdzana jest poprawnosc podanych parametrow, pozniej odnajduje sie zadany segment w tablicy, nastepnie kontroluje sie prawa dostepu do segmentu i oblicza jego identyfikator.
W przypadku koniecznosci utworzenia nowgo segmentu pamieci dzielonej odnajduje sie wolne miejsce w globalnej tablicy segmentow, przydzielana jest nowa struktura typu shmid_ds oraz alokowane sa strony pamieci. Jezeli wszystkie te czynnosci powioda sie zapisuje sie dane o segmencie do niezainicjalizowanej dotad struktury shmid_ds, aktualizowane sa liczniki i zmienne mowiace o uzywanych przez system zasobach, az wreszcie obliczany jest i zwracany identyfikator nowego segmentu.


Funkcja shmat()

Algorytm sluzy do przylaczenia segmentu pamieci dzielonej do przestrzeni adresowej procesu.
DEFINICJA: char *shmat(int shmid, char *shmaddr, int shmflg)
    WYNIK: w przypadku sukcesu 0 oraz adres podlaczenia na zmiennej raddr
           gdy blad -1 oraz na zmiennej errno:
                                 EACCES (brak praw)
                                 EIDRM  (segment zaznaczony do skasowania)
                                 EINVAL (zly identyfikator kolejki lub zly adres
                                         doczepienia)
                                 ENOMEM (brak pamieci na podlaczenie segmentu)
Pierwszym argumentem funkcji jest identyfikator segmentu pamieci dzielonej. Drugi argument - shmaddr - jest propozycja wirtualnego adresu procesu, pod ktory segment ma zostac podlaczony, adres zwrocony przez funkcje przez nazewnik funkcji nie musi zawsze byc zgodny z shmaddr (shmaddr==0 oznacza, ze adres ten wybierze funkcja). W przypadku pomyslnego przebiegu wartoscia zwracana przez funkcje jest adres segmentu, a w przypadku niepomyslnego przebiegu jest to wartosc -1. Dopuszczalne wartosci trzeciego argumentu - zmiennej shmflg to: Algorytm funkcji kontroluje podany identyfikator i odnajduje zadany segment w tablicy shm_segs (sprawdza sie, czy segment o podanym identyfikatorze istnieje, a takze czy zostal juz w pelni poprawnie zainicjalizowany). W kolejnym kroku funkcja sprawdza poprawnosc proponowanego adresu podlaczenia shmaddr lub (gdy shmaddr ==0) sama wyszukuje taki adres i stosuje ewentualne opcje zaokraglania.
Poza oczywistym warunkiem, ze po doczepieniu segmentu do pamieci wirtualnej procesu nie moze ona przekroczyc wartosci granicznych dla tego procesu ustalonych, spelniona musi byc jeszcze jedna regula - nie umieszcza sie segmentu pamieci dzielonej blizej niz 4 strony od pamieci przeznaczonej na stos.
Nastepnie weryfikuje sie prawa dostepu do segmentu. Przydzielona zostaje pamiec na nowa strukture vm_area_struct i wypelnia sie jej pola po czym wstawiana jest ona do drzewa AVL procesu oraz listy odpowiadajacej segmentowi pamieci dzielonej. Struktura vm_area_struct zawiera przedefiniowane wskazniki do funkcji wywolywanych w przypadku nieobecnosci segmentu w pamieci glownej, zakonczenia dzialania procesu, wykonania fork() itp, przy czym: Wreszcie funkcja zwieksza licznik odwolan do segmentu, modyfikuje dane dotyczace pidu procesu i czasu ostatnio wykonywanej operacji. Nastepuje poprawne zakonczenie funkcji.


Funkcja shmdt()

Ta procedura sluzy do odlaczania segmentu pamieci wspolnej od procesu.
DEFINICJA: int shmdt(char *shmaddr);
    WYNIK: w przypadku sukcesu 0
           gdy blad -1 oraz na zmiennej errno EINVAL (podano zly adres podlaczenia)
Jedynym argumentem funkcji jest adres podlaczenia segmentu pamieci dzielonej. O ile jest on poprawny, funkcja usuwa to podlaczenie.

Dzialanie tej funkcji jest niejako odwroceniem algorytmu shmat(). Poniewaz argumentem funkcji nie jest identyfikator segmentu klopotliwe jest odnalezienie segmentu.
Nie wystarcza wprost siegnac do tablicy segmentow pod indeks rowny identyfikatorowi, lecz nalezy przeszukiwac w procesie odpowiednia liste, az do uzgodnienia adresow. Jezeli podany adres byl poprawny mozna odlaczyc segment od procesu.
Wstawia sie nowy pid i czas ostatniej operacji. Zmniejsza sie licznik odwolan do segmentu i gdy spada on do 0 sprawdza sie, czy aby segment nie jest oznaczony do usuniecia. Jezeli tak bylo usuwa sie segment zwalniajac zajmowana przez niego i wszystkie struktury pomocnicze pamiec.
Algorytm konczy dzialanie.


Funkcja shmctl()

Funkcja sluzy do modyfikowania oraz odczytu rozmaitych wlasciwosci segmentu pamieci dzielonej, a takze do jego kasowania.
DEFINICJA: int shmctl(int shmid, int cmd, struct shmid_ds *buf)
    WYNIK: w przypadku sukcesu 0
           gdy blad -1 oraz na zmiennej errno:
                                 EACCES (brak praw czytania (IPC_STAT))
                                 EFAULT (niepoprawny adres buf)
                                 EIDRM  (segment zostal w miedzyczasie skasowany)
                                 EINVAL (zla wartosc argumentu shmqid)
                                 EPERM  (brak praw zapisu (IPC_SET lub IPC_RMID))
Pierwszy argument to oczywiscie identyfikator segmentu. Argument trzeci sluzy do wprowadzania lub wyprowadzania informacji z funkcji. Dopuszczalne komendy, czyli wartosci argumentu cmd to:

Dzialanie funkcji sprowadza sie do sprawdzenia poprawnosci argumentow, a przy niektorych opcjach takze praw dostepu i wykonania zadanej czynnosci: przekopiowania odpowiednich wartosci od lub do uzytkownika, zaznaczenia segmentu do skasowania, zwrocenie wartosci 0, identyfikatora lub najwiekszego uzywanego przez system identyfikatora segmentu. Powyzej opisane jest dzialanie funkcji w roznych przypadkach.


Bibliografia

  1. Pliki zrodlowe Linuxa:
  2. Sven Goldt, Sven van der Meer, Scott Burkett, Matt Welsh: The Linux Programmer's Guide - rozdzial o segmentach pamieci dzielonej.
  3. Maurice J. Bach: Budowa systemu operacyjnego UNIX - rozdial 11.2.2.
  4. W. Richard Stevens: Programowanie zastosowan sieciowych w systemie UNIX - rozdzial 3.11.


Pytania i odpowiedzi

1. Dlaczego w funkcji shmdt() jako argument przekazuje sie adres podlaczenia segmentu, a nie identyfikator segmentu, skoro to drugie pozwala na latwiejsze znalezienie, a nastepnie odczepienie segmentu?

Przyjete rozwiazanie dziala sensownie takze w przypadku, gdy jeden proces ma podczepiony wielokrotnie ten sam segment. Sam identyfikator segmentu nie mowilby nam w przypadku takiego wielokrotnego podczepienia, ktora z instancji mamy odlaczyc.

2. Jak komunikowac procesy za pomoca segmentow prywatnych (tworzonych z uzyciem klucza IPC_PRIVATE) oraz jaki jest sens tworzenia takich segmentow?

Stworzenie segmentu prywatnego zwalnia z obowiazku ustalania i uzgadniania wspolnego klucza przez wszystkie zainteresowane procesy. W zamian za to proces tworzacy segment jest odpowiedzialny za rozeslanie identyfikatora tegoz do procesow zainteresowanych dostepem. Dodatkowo zapewniona jest unikatowosc segmentu prywatnego oraz niemoznosc podlaczenia go przez inne procesy za pomoca funkcji shmctl() (moze to byc wada lub zaleta).


Autor: Przemyslaw Zabijak