Synchronizacja i komunikacja procesów

ŁF
<html> <body style="FONT-FAMILY: Verdana; FONT-SIZE: 10pt;"> <style> table {font-size: 10pt} h3 { color: #000000; font-weight: bold; font-size: 18pt; background-color: #e8d5ba; text-align: center; padding: 5; border: double 1pt; width: 100%; margins: 0; paddings: 4 } h4 { color: #000000; font-weight: bold; font-size: 16pt; background-color: #c5dcdc; text-align: center; padding: 5; border: double 1pt; width: 100%; margins: 0; paddings: 4 } h5 { color: #000000; font-weight: normal; font-size: 11pt; background-color: #c5dcc5; text-align: left; padding: 5; border: double 1pt; width: 100%; margins: 0; paddings: 4 } h6 { color: #0000cd; font-weight: bold; font-size: 12pt; font-family: monospace, Courier, "Courier CE"; text-align: left; text-indent: 10pt; word-spacing: 3pt; width: auto } em { color: #0000cd; font-weight: normal; font-size: 12pt; font-family: Courier, "Courier CE"; text-align: left; text-indent: 10pt; word-spacing: 0pt; width: auto } </style>

LINUX - Synchronizacja i komunikacja procesów.

1. Mechanizmy synchronizacji i komunikacji między procesami

System Linux udostępnia następujące mechanizmy synchronizacji i komunikacji procesów:

  • sygnały,
  • pliki,
  • łącza komunikacyjne:
    • łącza nienazwane,
    • łącza nazwane,
  • mechanizmy IPC Systemu V:
    • semafory,
    • kolejki komunikatów,
    • pamięć dzielona,
  • gniazda.
Wykorzystanie sygnałów do komunikowania się procesów zostało już omówione w niniejszym podręczniku. Stosowne informacje można odnaleźć w lekcjach 2 i 6. Korzystaniu z plików poświęcone są lekcje 3 i 9. Wykorzystanie gniazd w komunikacji sieciowej procesów opisano w Lekcji 10.

2. Łącza nienazwane

Łącze nienazwane umożliwia jednokierunkową komunikację pomiędzy procesami (w ramach jednego programu).

Jeden z procesów wysyła dane do łącza a drugi odczytuje te dane w kolejności, w jakiej zostały wysłane. Łącze ma więc organizację kolejki FIFO (ang. First In First Out) i ograniczoną pojemność.

Łącza realizowane są jako obiekty tymczasowe w pamięci jądra i udostępniane poprzez interfejs systemu plików. Każde łącze reprezentowane jest przez strukturę danych zwaną i-węzłem, podobnie jak każdy plik w systemie. Różnica polega na tym, że tymczasowy i-węzeł łącza wskazuje stronę w pamięci a nie bloki dyskowe.

Tworząc łącze na zlecenie procesu, jądro otwiera je od razu do czytania i pisania oraz dodaje dwie nowe pozycje do tablicy deskryptorów plików otwartych w procesie. Procesy nie posługują się więc w ogóle nazwą łącza i stąd pochodzi określenie "łącze nienazwane". Z tego też powodu korzystać z łącza mogą wyłącznie procesy spokrewnione, czyli proces macierzysty i kolejne pokolenia procesów potomnych. Procesy potomne dziedziczą po procesie macierzystym deskryptory wszystkich otwartych plików, w tym deskryptory otwartych łączy, co umożliwia im korzystanie z łączy utworzonych przez proces macierzysty.

Funkcja systemowa pipe() tworzy łącze nienazwane i otwiera je zarówno do czytania jak i do pisania.

int pipe(int fd[2]);
Funkcja zwraca tablicę dwóch deskryptorów: fd[0] umożliwiający czytanie z łącza i fd[1] umożliwiający pisanie do łącza. Ponieważ dwa procesy mogą się komunikować przez łącze tylko w jedną stronę, więc żaden z nich nie wykorzysta obydwu deskryptorów. Każdy z procesów powinien zamknąć nieużywany deskryptor łącza za pomocą funkcji close().
int close(int fd);
Jeden z procesów zamyka łącze do czytania a drugi do pisania. Uzyskuje się wtedy jednokierunkowe połączenie między dwoma procesami, co ilustruje rys. 9.1.
PIPE1.JPG

Rys. 9.1 Zastosowanie łącza nienazwanego do jednokierunkowej komunikacji między procesami

W podobny sposób interpreter poleceń realizuje przetwarzanie potokowe. W celu wykonania złożonego polecenia:

ps -ef | more
powłoka tworzy łącze komunikacyjne i dwa procesy potomne, które wykonują programy ps i more.

Dwukierunkowa komunikacja między procesami wymaga użycia dwóch łączy komunikacyjnych. Jeden z procesów pisze do pierwszego łącza i czyta z drugiego, a drugi proces postępuje odwrotnie. Obydwa procesy zamykają nieużywane deskryptory. Sytuację taką przedstawia rys. 9.2.

PIPE2.JPG

Rys. 9.2 Zastosowanie łączy nienazwanych do dwukierunkowej komunikacji między procesami

Do czytania i pisania można użyć funkcji systemowych read() i write().

ssize_t read(int fd, void *buf, size_t count);
Funkcja wczytuje count bajtów z łącza o deskryptorze fd do bufora buf i zwraca liczbę wczytanych bajtów. Jeżeli łącze jest puste lub brakuje w nim odpowiedniej porcji danych, to funkcja blokuje proces do momentu pojawienia się danych.
ssize_t write(int fd, const void *buf, size_t count);
Funkcja zapisuje count bajtów z bufora buf do łącza o deskryptorze fd i zwraca liczbę zapisanych bajtów. Jeżeli liczba bajtów nie przekracza pojemności łącza, to jądro gwarantuje niepodzielność zapisu danych do łącza. W przeciwnym przypadku dane będą zapisane w kilku porcjach i może nastąpić ich przemieszanie, jeśli z łącza korzysta jednocześnie kilka procesów piszących. Jeżeli łącze jest przepełnione, to funkcja blokuje proces w oczekiwaniu na zwolnienie miejsca.

Ustawienie flagi O_NDELAY zmienia działanie obydwu funkcji na nieblokujące i powoduje natychmiastowy powrót z błędem, gdy operacja nie może być zrealizowana. W celu ustawienia flagi trzeba wykorzystać funkcję systemową fcntl(), ponieważ proces nie używa jawnie funkcji open() do otwarcia łącza.

3. Łącza nazwane

Łącza nazwane realizowane jest przez system jako pliki typu FIFO. Dzięki temu umożliwiają komunikację między dowolnymi procesami.

Łącze nazwane można utworzyć posługując się funkcją systemową mknod(). Funkcja ta służy do tworzenia plików specjalnych (plików urządzeń) oraz plików FIFO (łączy nazwanych):

int mknod(const char *pathname, mode_t mode, dev_t dev);
gdzie:
pathname - nazwa scieżkowa tworzonego pliku,
mode - tryb pliku, definiujący typ i prawa dostępu do pliku,
dev - numery urządzenia, główny i drugorzędny.

Tryb pliku podaje się jako sumę bitową stałej określającej typ tworzonego pliku oraz praw dostępu zapisanych w kodzie ósemkowym.

Argument dev nie ma znaczenia podczas tworzeniu łącza nazwanego.

Wywołanie funkcji może więc wyglądać następująco:

mknod("/tmp/fifo", S_IFIFO|0666, 0);

Z funkcji mknod() korzystają dwa polecenia systemowe umożliwiajace utworzenie łącza z poziomu interpretera poleceń:
mkfifo [opcje] plik
mknod [opcje] plik typ
gdzie:
plik - nazwa ścieżkowa tworzonego pliku,
typ - typ pliku: p (FIFO), b, c.

Po utworzeniu, łącze należy otworzyć do czytania bądź pisania. Można w tym celu skorzystać z funkcji systemowej open() lub funkcji fopen() z biblioteki standardowej języka C.

int open(const char *pathname, int flags, mode_t mode);
Domyślnie otwarcie łącza jest operacją blokującą. Proces jest wstrzymywany do momentu otwarcia łącza przez inny proces do komplementarnej operacji w stosunku do czytania bądź pisania. Sytuacja taka nie wystąpi, jeżeli proces otwiera łącze jednocześnie do czytania i pisania, jak to ma miejsce w przypadku łączy nienazwanych. Ustawienie flag O_NDELAY lub O_NONBLOCK w wywołaniu funkcji powoduje, że otwarcie oraz wszystkie inne operacje na deskryptorze pliku FIFO stają się nieblokujące. W przypadku braku drugiego procesu funkcja open() zwraca wtedy błąd. Wspomniane flagi można też ustawic funkcją fcntl().

Operacje czytania i pisania do łącza można zrealizować za pomocą funkcji systemowych read() i write() albo za pomocą licznych funkcji wejścia/wyjścia z biblioteki standardowej języka C, w zależności od sposobu otwarcia łącza. Domyślnie wszystkie operacje są blokujące, podobnie jak dla łączy nienazwanych.

Zamykanie łącza, podobnie jak każdego innego pliku, odbywa się za pomocą funkcji close() lub fclose() w zależności od sposobu otwarcia.

W przeciwieństwie do łączy nienazwanych, pliki FIFO pozostają w systemie plików po zakończeniu ich używania przez wszystkie procesy. Dopiero jawne wywołanie funkcji unlink() powoduje usunięcie łącza nazwanego.

int unlink(const char *pathname);

Przykład
Oto dwa proste programy pokazujące sposób utworzenia i wykorzystania FIFO.

#include <stdio.h>
#include <stdlib.h>
#define FIFO_FILE "MYFIFO"

int main(int argc, char *argv[])
{
FILE *fp;

if ( argc != 2 ) {
printf("USAGE: fifoclient [string]\n");
exit(1);
}

if((fp = fopen(FIFO_FILE, "w")) == NULL) {
perror("fopen");
exit(1);
}

fputs(argv[1], fp);

fclose(fp);
return(0);
}


#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/stat.h>

#define FIFO_FILE "MYFIFO"

int main(void)
{
FILE *fp;
char readbuf[80];

/* Create the FIFO if it does not exist */
umask(0);
mknod(FIFO_FILE, S_IFIFO|0666, 0);

while(1)
{
fp = fopen(FIFO_FILE, "r");
fgets(readbuf, 80, fp);
printf("Received string: %s\n", readbuf);
fclose(fp);
}

return(0);
}

4. Mechanizmy IPC Systemu V

W wersji UNIX-a System V wprowadzono trzy nowe mechanizmy komunikacji międzyprocesowej:

  1. semafory,
  2. kolejki komunikatów,
  3. pamięć dzieloną albo wspólną.

Obecnie większość implementacji systemu UNIX oraz Linux udostępnia te mechanizmy. Są one powszechnie określane wspólną nazwą "komunikacja międzyprocesowa Systemu V" lub w skrócie IPC (ang. System V Interprocess Communication).

(4.1) Implementacja

W ramach każdego mechanizmu jądro tworzy pojedyńcze obiekty na zlecenie procesów. Każdy obiekt reprezentowany jest przez oddzielną strukturę danych. Dla każdego z mechanizmów jądro przechowuje tablicę wskaźników na struktury poszczególnych obiektów.

Tablica 9.1 Mechanizmy i obiekty IPC</span></caption> Mechanizm </td> Obiekt </td> Reprezentacja </td> </tr> semafory</td> zbiór semaforów</td> struktura semid_ds</td> </tr> kolejki komunikatów</td> kolejka komunikatów</td> struktura msqid_ds</td> </tr> pamięć dzielona</td> SEGMENT pamięci dzielonej</td> struktura shmid_ds</td> </tr> </table>

Do utworzenia obiektu potrzebny jest unikalny klucz w postaci 32-bitowej liczby całkowitej. Klucz ten stanowi nazwę obiektu, która jednoznacznie go identyfikuje i pozwala procesom uzyskać dostęp do utworzonego obiektu. Każdy obiekt otrzymuje również swój identyfikator, ale jest on unikalny tylko w ramach jednego mechanizmu. Oznacza to, że może istnieć kolejka i zbiór semaforów o tym samym identyfikatorze.

Wartość klucza można ustawić dowolnie. Zalecane jest jednak używanie funkcji ftok() do generowania wartości kluczy. Nie gwarantuje ona wprawdzie unikalności klucza, ale znacząco zwiększa takie prawdopodobieństwo.

key_t ftok (char *pathname, char proj);
gdzie:
pathname - nazwa ścieżkowa pliku,
proj - jednoliterowy identyfikator projektu.

Wszystkie tworzone obiekty IPC mają ustalane prawa dostępu na podobnych zasadach jak w przypadku plików. Prawa te ustawiane są w strukturze ipc_perm niezależnie dla każdego obiektu IPC.

Obiekty IPC pozostają w pamięci jądra systemu do momentu, gdy:

  • jeden z procesów zleci jądru usunięcie obiektu z pamięci,
  • nastąpi zamknięcie systemu.

(4.2) Operacje

Wszystkie trzy mechanizmy korzystają z podobnych funkcji systemowych, zestawionych w tablicy 9.2

Tablica 9.2 Funkcje systemowe operujące na obiektach IPC</span></caption>
Obiekt
Typ operacji</td> zbiór
semaforów
</td> kolejka
komunikatów
</td> SEGMENT
pamięci dzielonej
</td> </tr> tworzenie i otwieranie </td>
`semget()`
</td>
`msgget()`
</td>
`shmget()`
</td> </tr> operacje sterujące</td>
`semctl()`
</td>
`msgctl()`
</td>
`shmctl()`
</td> </tr> operacje specyficzne</td>
`semop()`
</td>
`msgsnd()
msgrcv()`
</td>
`shmat()
shmdt()`
</td> </tr> </table>

(4.3) Polecenia systemowe

Polecenie ipcs wyświetla informacje o wszystkich obiektach IPC istniejących w systemie, dokonując przy tym podziału na poszczególne mechanizmy. Wyświetlane informacje obejmują m.in. klucz, identyfikator obiektu, nazwę właściciela, prawa dostępu.

ipcs [ -asmq ] [ -tclup ]
ipcs [ -smq ] -i id

Wybór konkretnego mechanizmu umożliwiają opcje:

-s - semafory,
-m - pamięć dzielona,
-q - kolejki komunikatów,
-a - wszystkie mechanizmy (ustawienie domyślne).

Dodatkowo można podać identyfikator pojedyńczego obiektu -i id, aby otrzymać informacje tylko o nim.

Pozostale opcje specyfikują format wyświetlanych informacji.

Dowolny obiekt IPC można usunąć posługując się poleceniem:

ipcrm [ shm | msg | sem ] id
gdzie:
shm, msg, sem - specyfikacja mechanizmu, kolejno: pamięć dzielona, kolejka komunikatów, semafory,
id - identyfikator obiektu.

5. Semafory

Utworzenie nowego zestawu semaforów lub dostęp do już istniejącej zapewnia funkcja systemowa semget().

int semget(key_t key, int nsems, int semflg);
gdzie:
key - klucz,
nsems - liczba semaforów w zbiorze,
semflg - flagi.

Funkcja zwraca identyfikator zbioru semaforów związanego z podaną wartością klucza key. Szczegółowy sposób działania wynika z flag ustawionych w argumencie semflg:

0 - funkcja udostępnia isniejący zbiór semaforów z podanym kluczem lub zwraca błąd, jeśli zbiór nie istnieje,
IPC_CREAT - funkcja tworzy nowy zbiór semaforów lub udostępnia isniejący zbiór z podanym kluczem,
IPC_EXCL | IPC_CREAT - funkcja tworzy nowy zbiór semaforów lub zwraca błąd, jeśli zbiór z podanym kluczem już istnieje.

Argument semflg może również opcjonalnie zawierać maskę praw dostępu do zbioru semaforów. Podawany jest wtedy jako suma bitowa stałych symbolicznych określających flagi oraz maski praw dostępu w kodzie ósemkowym np.:

IPC_CREAT | 0660

Operacje na semaforach umożliwia funkcja semop():

int semop (int semid, struct sembuf *sops, unsigned nsops);
gdzie:
semid - identyfikator zbioru semaforów,
sops - wskaźnik do tablicy operacji, które mają być wykonane na zbiorze semaforów,
nsops - liczba operacji w tablicy.

Funkcja udostępnia trzy rodzaje operacji:

  1. zajęcie zasobu chronionego semaforem,
  2. zwolnienie zasobu,
  3. oczekiwanie na 100% zużycie zasobu.
<dl> <dt>W jednym wywołaniu funkcji można zrealizować kilka operacji na semaforach ze zbioru. Jądro zapewnia niepodzielność realizacji wszystkich operacji, zarówno każdej z osobna, jak i zestawu operacji jako całości. Każda operacja dotyczy tylko jednego semafora wybranego ze zbioru i jest opisana w oddzielnej strukturze sembuf:
  </dl>
`struct sembuf {`
` ` `short sem_num;` # indeks semafora w tablicy
` ` `short sem_op;` # operacja na semaforze
` ` `short sem_flg;` # flagi
`};`

Dla sem_op < 0 podana wartość bezwzględna zostanie odjęta od wartości semafora. Ponieważ wartość semafora nie może być ujemna, to proces może zostać zablokowany (uśpiony) do momentu uzyskania przez semafor odpowiedniej wartości, która umożliwi wykonanie operacji. Odpowiada to zajęciu zasobu.
Dla sem_op > 0 podana wartość zostanie dodana do wartości semafora. Tę operację można zawsze wykonać. Odpowiada to zwolnieniu zasobu.
Przy sem_op = 0 proces zostanie uśpiony do momenu, gdy semafor osiągnie wartość zerową. Oznacza to oczekiwanie na 100% zużycie zasobu.

Dla każdej operacji można ustawić dwie flagi:

IPC_NOWAIT - powoduje, że operacja wykonywana jest bez blokowania procesu,
SEM_UNDO - powoduje, że operacja zostanie odwrócona, jeśli proces się zakończy.

Funkcja semctl() umożliwia wykonywanie różnorodnych operacji sterujących na zbiorze semaforów obejmujących m.in. usuwanie całego zbioru, pobieranie informacji o semaforach oraz ustawianie ich wartości.

int semctl(int semid, int semnum, int cmd, union semun arg);
gdzie:
semid - identyfikator zbioru semaforów,
semnum - indeks semafora w tablicy,
scmd - operacja sterująca,
arg - unia zawierająca różne typy danych dla różnych operacji sterujących.

Składniki unii semun są następujące:
 

`union semun {`
` ` `int val;` # wartość dla SETVAL
` ` `struct semid_ds *buf;` # bufor dla IPC_STAT i IPC_SET
` ` `unsigned short int *array;` # tablica dla GETALL i SETALL
` ` `struct seminfo *__buf;` # bufor dla IPC_INFO
`};`

Funkcja semctl() oferuje następujące operacje sterujące:

IPC_STAT - zapis struktury semid_ds do bufora buf,
IPC_SET - ustawienie w strukturze ipc_perm maski praw dostępu do zbioru semaforów,
IPC_RMID - usunięcie zbioru semaforów,
GETALL - pobranie wartości wszystkich semaforów ze zbioru do tablicy array,
GETVAL - pobranie wartości pojedyńczego semafora,
GETNCNT - pobranie liczby procesów oczekujących na zasoby,
GETPID - pobranie identyfikatora PID procesy, który ostatni wykonał ostatnią operację na zbiorze semaforów,
GETZCNT - pobranie liczby procesów oczekujących na zerową wartość semafora,
SETALL - ustawienie wartości wszystkich semaforów ze zbioru na podstawie tablicy array,
SETVAL - ustawienie wartości pojedyńczego semafora na podstawie wartości zmiennej val.

6. Kolejki komunikatów

Utworzenie nowej kolejki komunikatów lub otwarcie dostępu do już istniejącej umożliwia funkcja systemowa msgget().

int msgget (key_t key, int msgflg);
gdzie:
key - klucz,
msgflg - flagi.

Funkcja zwraca identyfikator kolejki związanej z podaną wartością klucza key. Szczegółowy sposób działania wynika z flag ustawionych w argumencie msgflg:

0 - funkcja otwiera isniejącą kolejkę z podanym kluczem lub zwraca błąd, jeśli kolejka nie istnieje,
IPC_CREAT - funkcja tworzy nową kolejkę lub otwiera isniejącą kolejkę z podanym kluczem,
IPC_EXCL | IPC_CREAT - funkcja tworzy nową kolejkę lub zwraca błąd, jeśli kolejka z podanym kluczem już istnieje.

Argument msgflg może również opcjonalnie zawierać maskę praw dostępu do kolejki. Podawany jest wtedy jako suma bitowa stałych symbolicznych określających flagi oraz maski praw dostępu w kodzie ósemkowym.

Operacje przesyłania komunikatów wymagają posłużenia się buforem komunikatu zdefiniowanym w następujacy sposób:

`struct msgbuf {`
` ` `long mtype;`  # typ komunikatu (wartość > 0)
` ` `char mtext[1];`  # tekst komunikatu
`};`

Pole mtype określa typ komunikatu w postaci dodatniej liczby całkowitej. Tablica mtext[] przechowuje treść komunikatu, którą mogą stanowić dowolne dane. Rozmiar tablicy podany w definicji nie stanowi rzeczywistego ograniczenia, ponieważ bufor komunikatu można dowolnie przedefiniować w programie pod warunkiem zachowania typu na początku.

Funkcja msgsnd() umożliwia przesłanie komunikatu do kolejki:

int msgsnd (int msqid, struct msgbuf *msgp, int msgsz, int msgflg);
gdzie:
msqid - identyfikator kolejki komunikatów,
msgp - wskaźnik do bufora zawierającego komunikat do wysłania,
msgsz - rozmiar bufora komunikatu z wyłączeniem typu (rozmiar treści komunikatu),
msgflg - flagi.

Funkcja msgrcv() pobiera z kolejki jeden komunikat wskazanego typu. Pobrany komunikat jest usuwaney z kolejki.

int msgrcv (int msqid, struct msgbuf *msgp, int msgsz, long msgtype, int msgflg);
gdzie:
msqid - identyfikator kolejki komunikatów,
msgp - wskaźnik do bufora, do którego ma być zapisany komunikat, pobrany z kolejki,
msgsz - rozmiar bufora komunikatu z wyłączeniem typu (rozmiar treści komunikatu),
msgtype - typ komunikatu do pobrania z kolejki,
msgflg - flagi.

Argument msgtype specyfikuje typ komunikatu do pobrania w następujący sposób:

msgtype > 0 - pobiera najstarszy komunikat danego typu,
msgtype = 0 - pobiera najstarszy komunikat w kolejce.

Obydwie opisane operacje są domyślnie operacjami blokującymi proces do momentu pomyślnego zakończenia. Ustawienie flagi IPC_NOWAIT w wywołaniu funkcji zmienia jej działanie na nieblokujące.

Funkcja sterująca msgctl() umożliwia pobranie lub ustawienie atrybutów kolejki, jak również usunięcie kolejki ze struktur danych jądra.

int msgctl ( int msqid, int cmd, struct msqid_ds *buf );
gdzie:
msqmid - identyfikator kolejki,
cmd - operacja sterująca,
buf - wskaźnik do bufora przeznaczonego na strukturę msqid_ds kolejki.

Argument cmd decyduje o rodzaju operacji sterującej wykonywanej na kolejce:

IPC_STAT - powoduje zapisanie zawartości struktury msqid_ds kolejki do bufora buf,
IPC_SET - powoduje ustawienie w strukturze ipc_perm praw dostępu do kolejki pobranych z bufora,
IPC_RMID - usuwa kolejkę komunikatów z jądra.

7. Pamięć dzielona

Utworzenie nowego SEGMENTu pamięci dzielonej lub uzyskanie dostępu do już istniejącego umożliwia funkcja systemowa shmget().

int shmget(key_t key, int size, int shmflg);
gdzie:
key - klucz,
size - rozmiar SEGMENTu pamięci,
shmflg - flagi.

Funkcja zwraca identyfikator SEGMENTu pamięci związanego z podaną wartością klucza key. Szczegółowy sposób działania wynika z flag ustawionych w argumencie shmflg:

0 - funkcja udostępnia isniejący SEGMENT z podanym kluczem lub zwraca błąd, jeśli SEGMENT nie istnieje,
IPC_CREAT - funkcja tworzy nowy SEGMENT lub udostępnia isniejący SEGMENT z podanym kluczem,
IPC_EXCL | IPC_CREAT - funkcja tworzy nowy SEGMENT lub zwraca błąd, jeśli SEGMENT z podanym kluczem już istnieje.

Argument shmflg może również opcjonalnie zawierać maskę praw dostępu do SEGMENTu pamięci. Podawany jest wtedy jako suma bitowa stałych symbolicznych określających flagi oraz maski praw dostępu w kodzie ósemkowym.

W wynika wywołania funkcji shmget() proces uzyskuje identyfikator SEGMENTu pamięci dzielonej. Aby można było z niego korzystać, SEGMENT musi zostać jeszcze przyłączony do wirtualnej przestrzeni adresowej procesu za pomocą funkcji shmat().

void *shmat(int shmid, const void *shmaddr, int shmflg);
gdzie:
shmid - identyfikator SEGMENTu pamięci dzielonej,
shmaddr - adres w przestrzeni adresowj procesu, od którego ma być dołączony SEGMENT,
shmflg - flagi.

Funkcja zwraca adres początkowy dołączonego SEGMENTu w wirtualnej przestrzeni adresowej procesu. Adres ten może być wyspecyfikowany w argumencie shmaddr. Jądro systemu próbuje wtedy dołączyć SEGMENT od podanego adresu pod warunkiem, że jest on wielokrotnością rozmiaru strony pamięci. Zaokrąglonego adresu w dół do granicy strony może być dokonane przez jądro, jeśli ustawiona jest flaga SHM_RND. Zalecane jest jednak ustawienie shmaddr = 0 w wywołaniu funkcji, aby pozwolić na wybór adresu przez jądro.

Domyślnie SEGMENT dołączany jest do czytania i pisania przez proces. Ustawienie flagi SHM_RDONLY powoduje dołączenie SEGMENTu wyłącznie do czytania. W obydwu przypadkach proces musi posiadać odpowiednie uprawnienia do wykonywania wspomnianych operacji.

Po zakończeniu korzystania z SEGMENTu pamięci dzielonej, proces powinien odłączyć go za pomocą funkcji systemowej shmdt().

int shmdt(const void *shmaddr);
gdzie:
shmaddr - adres początkowy SEGMENTu w przestrzeni adresowej procesu.

Odłączenie SEGMENTu nie oznacza automatycznie usunięcia z jądra systemu. Segment pozostaje w pamięci i może być ponownie dołączany przez procesy. W celu usunięcia SEGMENTu trzeba posłużyć się funkcją systemową shmctl().

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
gdzie:
shmid - identyfikator SEGMENTu,
cmd - operacja sterująca,
buf - wskaźnik do bufora przeznaczonego na strukturę shmid_ds SEGMENTu.

Argument cmd decyduje o rodzaju operacji sterującej wykonywanej na segmencie pamięci:

IPC_STAT - powoduje zapisanie zawartości struktury shmid_ds SEGMENTu do bufora buf,
IPC_SET - powoduje ustawienie w strukturze ipc_perm praw dostępu do SEGMENTu pobranych z bufora,
IPC_RMID - zaznacza SEGMENT do usunięcia z pamięci.

Polecenie IPC_RMID powoduje jedynie zaznaczenie, że SEGMENT ma być usunięty z pamięci, gdy przestanie być używany. Usunięcie SEGMENTu nastąpi dopiero wtedy, gdy wszystkie procesy odłączą go od swoich przestrzeni adresowych.

Autorami powyższego artykułu są mgr inż. A. Wielgus i dr Z. Jaworski. Drobnych zmian i poprawek dokonał Ł. Fronczyk
W dziale źródła/C++ znajdują się przykładowe programy do tego i pozostałych artykułów z tego cyklu.
Wszelkie pytania proszę kierować na adres L.Fronczyk@elka.pw.edu.pl

2 komentarzy

przy semaforach zabrakło mi opisu struktur semid_ds oraz sem i wytlumaczenia jak to jest przechowywane w jadrze , ale tak ogolnie to bardzo przydatny
artykulik ,
Pozdrawiam .

hmmm.. coś rozwala stronę :| niestety nie mogę namierzyć co.