Comunicarea se poate realiza între procese de pe aceeași mașină sau de pe mașini diferite. Exemple de mecanisme IPC:
Pipe-urile au fost prezentate deja în laboratorul de procese. În acest laborator ne vom concentra pe primele trei categorii.
Mecanisme de sincronizare
Este un obiect de sincronizare care poate fi deținut (posedat, acaparat) doar de un singur proces/thread (în funcție de implementare) la un moment dat. Drept urmare, operațiile de bază cu mutex-uri sunt cele de obținere și de eliberare.
Odată obținut de un proces/thread, un mutex devine indisponibil pentru orice alt proces/thread. Orice proces/thread care încearcă să acapareze un mutex indisponibil, se va bloca (un timp definit sau nu) așteptând ca el să devină disponibil.
Mutex-urile sunt cel mai des folosite pentru a permite unui singur proces la un moment dat să acceseze o resursă.
Semafoarele sunt resurse IPC folosite pentru sincronizarea între procese/thread-uri (e.g., pentru controlul accesului la resurse). Un semafor poate fi privit ca un contor ce poate fi incrementat și decrementat, dar a cărui valoare nu poate scădea sub 0. Atât timp cât semaforul (contorul) are valori strict pozitive el este considerat disponibil. Când valoarea semaforului a ajuns la 0, el devine indisponibil și următoarea încercare de decrementare va duce la o blocare a threadului/procesului de pe care s-a făcut apelul până când semaforul devine disponibil.
Sunt folosite de procese pentru a comunica între ele prin mesaje. Aceste mesaje își păstrează ordinea în interiorul cozii de mesaje. Sunt mecanisme de comunicare unidirecționale atât pe Linux, cât și pe Windows.
Acest mecanism permite comunicarea între procese prin accesul direct și partajat la o zonă de memorie bine determinată. Este un mod mai rapid de comunicare între procese decât celelalte mijloace IPC, dar are un mare dezavantaj, procesele ce comunică trebuie să fie pe aceeași mașină (spre deosebire de sockeți, pipe-urile cu nume și cozile de mesaje din Windows)
Linux pune la dispoziție 2 seturi de API-uri referitoare la mecanismele de comunicare interprocese, ce țin de standarde diferite:
Ambele standarde specifică 3 mecanisme:
mesaje
(messages) - realizează schimbul de mesaje cu orice proces sau serversemafoare
(semaphores) - realizează sincronizarea execuțiilor proceselormemorie partajată
(shared memory) - realizează partajarea memoriei între procese API-ul studiat în acest laborator este cel POSIX.
Obiectele de tip IPC pe care se concentrează laboratorul de față sunt gestionate global de sistem și rămân în viață chiar dacă procesul creator moare. Faptul că aceste resurse sunt globale în sistem are implicații contradictorii:
Faptul că obiectele IPC sunt globale în sistem poate duce la apariția unor probleme: cum numărul de mesaje care se află în cozile de mesaje din sistem e limitat global, un proces care trimite multe asemenea mesaje poate bloca toate celelalte procese.
Atenție! Pentru folosirea API-ului trebuie să includeți la linking biblioteca rt
(-lrt).
Semafoarele sunt resurse IPC folosite pentru sincronizarea între procese (e.g., pentru controlul accesului la resurse). Operațiile asupra unui semafor pot fi de setare
, verificare
a valorii ( care poate fi >= 0 ), test and set
. Un semafor poate fi privit ca un contor ce poate fi incrementat și decrementat, dar a cărui valoare nu poate scădea sub 0.
Semafoarele POSIX sunt de 2 tipuri:
cu nume
- folosite în general pentru sincronizare între procese distincte;fără nume
- ce pot fi folosite pentru sincronizarea între firele de execuție ale aceluiași proces, sau între procese - cu condiția ca semaforul să fie într-o zonă de memorie partajată.
În continuare vor fi luate în discuție semafoarele cu nume
. Diferențele față de cele fără nume apar în funcțiile de creare și distrugere, celelalte funcții fiind identice.
sem_t
.”/nume”
.<fcntl.h>
, <sys/types.h>
și <semaphore.h>
.Un proces poate crea sau deschide un semafor existent cu funcția sem_open:
/* create semaphore */ sem_t* sem_open(const char *name, int oflag, mode_t mode, unsigned int value); /* open semaphore */ sem_t* sem_open(const char *name, int oflag);
Comportamentul este similar cu cel de la deschiderea fișierelor. Dacă flag-ul O_CREAT
este prezent, trebuie folosită prima formă a funcției, specificând permisiunile și valoarea inițială.
Singurele posibilități pentru al doilea argument sunt:
Valoarea unui semafor este decrementată cu funcția sem_wait:
int sem_wait(sem_t *sem);
Dacă semaforul are valoarea 0, funcția blochează până când un alt proces “deblochează” (incrementează) semaforul.
Pentru a încerca decrementarea unui semafor fără riscul de a rămâne blocat la acesta, un proces poate apela sem_trywait:
int sem_trywait(sem_t *sem);
În cazul în care semaforul are deja valoarea zero, funcția va întoarce -1, iar errno
va fi setat la EAGAIN
.
Un semafor este incrementat cu funcția sem_post:
int sem_post(sem_t *sem);
În cazul în care semaforul avea valoarea zero, un proces blocat în sem_wait pe acesta va fi deblocat.
Valoarea unui semafor (a contorului) se poate afla cu sem_getvalue:
int sem_getvalue(sem_t *sem, int *pvalue);
În cazul în care există procese blocate la semafor, implementarea apelului pe Linux va returna zero în valoarea referință de pvalue
.
Un proces închide (notifică faptul că nu mai folosește) un semafor printr-un apel sem_close:
int sem_close(sem_t *sem);
Un proces poate șterge un semafor printr-un apel sem_unlink:
int sem_unlink(const char *name);
Distrugerea efectivă a semaforului are loc după ce toate procesele care îl au deschis apelează sem_close sau se termină. Totuși, chiar și în acest caz, apelul sem_unlink nu se va bloca!
#include <fcntl.h> /* For O_* constants */ #include <sys/stat.h> /* For mode constants */ #include <semaphore.h> #include "utils.h" #define SEM_NAME "/my_semaphore" int main(void) { sem_t *my_sem; int rc, pvalue; /* create semaphore with initial value of 1 */ my_sem = sem_open(SEM_NAME, O_CREAT, 0644, 1); DIE(my_sem == SEM_FAILED, "sem_open failed"); /* get the semaphore */ sem_wait(my_sem); /* do important stuff protected by the semaphore */ rc = sem_getvalue(my_sem, &pvalue); DIE(rc == -1, "sem_getvalue"); printf("sem is %d\n", pvalue); /* release the lock */ sem_post(my_sem); rc = sem_close(my_sem); DIE(rc == -1, "sem_close"); rc = sem_unlink(SEM_NAME); DIE(rc == -1, "sem_unlink"); return 0; }
Semaforul va fi creat în ”/dev/shm” și va avea numele “sem.my_semaphore”
Acestea permit proceselor să interschimbe date sub formă de mesaje
/nume
”.mqd_t
.<fcntl.h>
, <sys/types.h>
și <mqueue.h>
.Funcțiile de creare și deschidere sunt similare ca formă și semantică celor de la semafoare (mq_open):
/* create */ mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr); /* open */ mqd_t mq_open(const char *name, int oflag);
În funcție de flag-uri (unul dintre cele de mai jos trebuie specificat), coada poate fi deschisă pentru:
O_RDONLY
)O_WRONLY
)O_RDWR
)
Daca attr
e NULL
, coada va fi creată cu atribute implicite. Structura mq_attr
arată astfel:
struct mq_attr { long mq_flags; /* 0 or O_NONBLOCK */ long mq_maxmsg; /* Max. number of messages on queue */ long mq_msgsize; /* Max. message size (bytes) */ long mq_curmsgs; /* number of messages currently in queue */ };
Pentru a trimite un mesaj (de lungime cunoscută, stocat într-un buffer) în coadă se apelează mq_send:
int mq_send(mqd_t mqdes, const char *buffer, size_t length, unsigned priority);
Mesajele sunt ținute în coadă în ordinea descrescătoare a priorității.
În cazul în care coada este plină, apelul blochează. Dacă este o coadă non-blocantă (O_NONBLOCK
), funcția va întoarce -1, iar errno
va fi setat la EAGAIN
.
Pentru a primi un mesaj dintr-o coadă (și anume: cel mai vechi mesaj cu cea mai mare prioritate) se folosește mq_receive:
ssize_t mq_receive(mqd_t mqdes, char *buffer, size_t length, unsigned *priority);
Dacă priority
este non-NULL, zona de memorie către care face referire va reține prioritatea mesajului extras.
În cazul în care coada este vidă, apelul blochează. Dacă este o coadă non-blocantă (O_NONBLOCK
), comportamentul este similar cu cel al mq_send.
Atenție! La primirea unui mesaj, lungimea buffer-ului trebuie să fie cel puțin egală cu dimensiunea maximă a mesajelor pentru coada respectivă, iar la trimitere cel mult egală. Dimensiunea maximă implicită se poate afla pe Linux din /proc/sys/kernel/msgmax
.
Închiderea (eliberarea “referinței”) unei cozi este posibilă prin apelul mq_close:
int mq_close(mqd_t mqdes);
Ștergerea se realizează cu un apel mq_unlink:
int mq_unlink(const char *name);
Semantica este similară cu cea de la semafoare: coada nu va fi ștearsă efectiv decât după ce restul proceselor implicate o închid sau se termină.
#include <mqueue.h> #include <string.h> #include <stdio.h> #include <errno.h> #include "utils.h" /* Set buffer size at least the default maxim size of the queue * found in/proc/sys/kernel/msgmax */ #define BUF_SIZE (1<<13) #define TEXT "test message" #define NAME "/test_queue" char buf[BUF_SIZE]; int main(int argc, char **argv) { unsigned int prio = 10; int rc; mqd_t m; m = mq_open(NAME, (argc>1 ? O_CREAT : 0) | O_RDWR, 0666, NULL); DIE(m == (mqd_t)-1, "mq_open"); if (argc > 1) { /* server sending message */ rc = mq_send(m, TEXT, strlen(TEXT), prio); DIE(rc == -1, "mq_send"); rc = mq_close(m); DIE(rc == -1, "mq_close"); } else { /* client receiving message */ rc = mq_receive(m, buf, BUF_SIZE, &prio); DIE(rc == -1, "mq_receive"); printf("received: %s\n", buf); rc = mq_close(m); DIE(rc == -1, "mq_close"); rc = mq_unlink(NAME); DIE(rc == -1, "mq_unlink"); } return 0; }
Acest mecanism permite comunicarea între procese prin accesul direct și partajat la o zonă de memorie bine determinată.
/nume
”;int
). <fcntl.h>
, <sys/types.h>
și <sys/mman.h>
.Apelul de creare/deschidere este similar cu semantica apelului open pentru fișiere “obișnuite”, și returnează un descriptor:
int shm_open(const char *name, int flags, mode_t mode);
Ca flag de acces trebuie specificat fie O_RDONLY
, fie O_RDWR
.
O zonă de memorie partajată nou creată are dimensiunea inițială zero. Pentru a o dimensiona se folosește ftruncate, unde fd
este descriptorul iar length
este dimensiunea în bytes:
int ftruncate(int fd, off_t length);
Pentru a putea utiliza o zonă de memorie partajată după deschidere, aceasta trebuie mapată în spațiul de memorie al procesului. Maparea se realizează printr-un apel mmap:
void *mmap(void *address, size_t length, int protection, int flags, int fd, off_t offset);
Valoarea întoarsă reprezintă un pointer către începutul zonei de memorie sau MAP_FAILED
în caz de eșec.
Acest apel are o largă aplicabilitate și va fi discutat în cadrul laboratorului de memorie virtuală. Momentan, pentru a mapa întregul conținut al unei zone (shm_fd
) de dimensiune cunoscută (shm_len
), recomandăm folosirea apelului:
mem = mmap(0, shm_len, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
Când maparea nu mai este necesară, prin apelul munmap se realizează demaparea:
int munmap(void *address, size_t length);
Închiderea unei zone de memorie partajată este identică cu închiderea unui fișier - apelul close, care primește ca parametru un descriptor.
Odată ce o zonă de memorie a fost demapată și închisă în toate procesele implicate, se poate șterge prin shm_unlink:
int shm_unlink(const char *name);
Semantica este identică cu cea de la funcțiile *_unlink
anterioare - ștergerea efectivă este amânată până ce toate procesele implicate închid zona în cauză sau se termină.
#include <stdio.h> #include <sys/mman.h> #include <sys/stat.h> /* For mode constants */ #include <fcntl.h> /* For O_* constants */ #include <unistd.h> #include "utils.h" #define SHM_NAME "my_shm" #define SHM_SIZE 1024 int main(void) { void *mem; /* map address */ int shm_fd; /* memory descriptor */ int rc; /* create shm */ shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0644); DIE(shm_fd == -1, "shm_open"); /* resize shm to fit our needs */ rc = ftruncate(shm_fd, SHM_SIZE); DIE(rc == -1, "ftruncate"); mem = mmap(0, SHM_SIZE, PROT_WRITE | PROT_READ, MAP_SHARED, shm_fd, 0); DIE(mem == MAP_FAILED, "mmap"); /* write number in shm */ ((int*)mem)[0] = 2011; printf("mem[0] = %d\n", ((int*)mem)[0]); /* unmap shm */ rc = munmap(mem, SHM_SIZE); DIE(rc == -1, "munmap"); /* close descriptor */ rc = close(shm_fd); DIE(rc == -1, "close"); rc = shm_unlink(SHM_NAME); DIE(rc == -1, "unlink"); return 0; }
În Linux, zonele pot fi regăsite în /dev/shm
, ca intrări formate din numele dat la creare + suffixul ”.shm”.
Conținutul cozilor (conținutul mesajelor) nu poate fi vizualizat, însă informații statistice pot fi obținute prin montarea unui pseudo-sistem de fișiere:
so@spook$ sudo mkdir /mnt/cozi/ so@spook$ sudo mount -t mqueue none /mnt/cozi/ so@spook$ cat /mnt/cozi/q_name QSIZE:12 NOTIFY:0 SIGNO:0 NOTIFY_PID:0
Sistemul de operare Windows pune la dispoziție o serie de mecanisme de comunicare și schimb de date între aplicații. Cazul de care ne vom ocupa este doar cel în care aceste aplicații sunt procese care rulează pe aceeași mașină.
Înainte de a fi prezentate mecanismele de comunicare în sine trebuie introduse mecanismele de sincronizare, care sunt folosite pentru controlul accesului la resurse.
Mecanismele de sincronizare oferite de sistemul de operare Windows sunt mai multe și mai complexe decât cele din Linux. Pentru sincronizare sunt necesare:
Aceste funcții așteaptă după un singur obiect de sincronizare. Execuția lor se termină când una dintre următoarele condiții este adevărată :
signaled
INFINITE
- timpul de așteptare nu expiră niciodată.WaitForSingleObject așteaptă după un singur obiect și are sintaxa:
DWORD WaitForSingleObject( HANDLE hHANDLE, DWORD dwMilliseconds );
Atenție! Obiectele de sincronizare nu pot fi folosite fără funcții de sincronizare.
Sunt operații prin care se obține un HANDLE
al unui obiect de tip mutex. Este necesar doar un singur apel, fie el de creare sau de deschidere (se presupune ca alt proces a creat deja mutex-ul).
Pentru a crea un mutex se folosește funcția CreateMutex cu sintaxa :
HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpAttributes, BOOL bInitialOwner, LPCTSTR lpName ); |
hMutex = CreateMutex( NULL, /* default security attributes */ FALSE, /* initially not owned */ NULL, /* unnamed mutex */ ); |
Pentru a deschide un mutex deja existent este definită funcția OpenMutex cu sintaxa :
HANDLE OpenMutex( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName ); |
hMutex = OpenMutex( MUTEX_ALL_ACCESS, /* request full access */ FALSE, /* handle not inheritable */ "MyMutex" /* object name */ ); |
Obținerea unui mutex se realizează folosind una din funcțiile de așteptare tratate anterior.
Încercarea de acaparare a unui mutex presupune următorii pași:
Încercarea de obținere se poate face cu sau fară timp de expirare (time-out) în funcție de parametrii dați funcțiilor de așteptare. Cea mai des folosită funcție de așteptare este WaitForSingleObject.
Folosind funcția ReleaseMutex se cedează posesia mutex-ului, el devenind iar disponibil. Funcția are următoarea sintaxa :
BOOL ReleaseMutex( HANDLE hMutex );
Funcția va eșua dacă procesul nu deține mutex-ul.
Atenție! pentru a putea folosi această funcție HANDLE
-ul trebuie să aibă cel puțin dreptul de acces MUTEX_MODIFY_STATE
.
Operația de distrugere a unui mutex este aceeași ca pentru orice HANDLE
. Se folosește funcția CloseHandle. După ce toate HANDLE
-urile unui mutex au fost închise, mutexul este distrus și resursele ocupate de acesta eliberate.
Atenție! La terminarea execuției unui program toate HANDLE
-urile folosite de acesta sunt automat închise. Deci, spre deosebire de semafoarele IPC din Linux, este imposibil ca un mutex (sau semafor) în Windows să mai existe în sistem după ce programele care l-au folosit/creat s-au terminat.
Un semafor este un obiect de sincronizare care are intern un contor ce ia doar valori pozitive. Atât timp cât semaforul (contorul) are valori strict pozitive el este considerat disponibil (signaled). Când valoarea semaforului a ajuns la zero el devine indisponibil (nonsignaled) și următoarea încercare de decrementare va duce la o blocare a threadului/procesului de pe care s-a făcut apelul până când semaforul devine disponibil.
Operația de decrementare se realizează doar cu o singură unitate (la fel ca în API-ul POSIX), în timp ce incrementarea se poate realiza cu orice valoare în limita maximă.
Funcția de creare a semafoarelor este CreateSemaphore și are sintaxa :
HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpNAME );
Această funcție se poate folosi și pentru deschiderea unui semafor deja existent. Alternativ, pentru a folosi un semafor deja existent, este necesar obținerea HANDLE
-ului semaforului, operație ce se realizează folosind funcția OpenSemaphore cu următoarea sintaxă :
HANDLE OpenSemaphore( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpNAME );
Operația de decrementare a semaforului cu sau fără așteptare se realizează folosind una din funcțiile de așteptare. Cea mai des folosită este funcția WaitForSingleObject.
Incrementarea semaforului se realizează folosind funcția ReleaseSemaphore cu sintaxa :
BOOL ReleaseSemaphore( HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount );
Operația de distrugere a unui semafor este similară cu cea de distrugere a unui mutex. Se folosește funcția CloseHandle. După ce toate HANDLE
-urile unui semafor au fost închise, semaforul este distrus și resursele ocupate de acesta eliberate.
Cozile de mesaje sunt un fel de pseudo-fișiere care rezidă în memorie. De aceea, pot fi gestionate prin intermediul funcțiilor standard de acces la fișiere. Fiind păstrate în memorie, toate aceste date au un caracter volatil, spre deosebire de fișiere, iar când toate handle-urile la un mailslot sunt distruse, acesta la rândul său, este distrus împreună cu datele, iar memoria este eliberată (spre deosebire de cozile de mesaje de pe Linux).
Au următoarele caracteristici:
Un exemplu tipic de folosire este următorul:
serverul mailslot
creează coada folosind CreateMailslot
, apoi așteaptă să primească un mesaj folosind un apel ReadFile
clientul mailslot
deschide coada folosind CreateFile
, apoi transmite un mesaj folosind un apel WriteFile
.Când un proces creează un mailslot, trebuie să-i atribuie o denumire de forma:
\\.\mailslot\[path]<nume>
Atenție! Prefixul “\\.\mailslot\” trebuie să existe exact în această formă, el fiind urmat de un nume, care eventual va fi precedat de o cale. Calea este asemănătoare cu cea a fișierelor. Un exemplu valid: “\\.\mailslot\test\commands”.
Pentru a crea o coadă de mesaje, se folosește funcția CreateMailslot care are următoarea sintaxă și întoarce un handle:
HANDLE CreateMailslot( LPCTSTR lpName, DWORD nMaxMessageSize, DWORD lReadTimeout, LPSECURITY_ATTRIBUTES lpSecurityAttributes );
În cazul în care se încearcă crearea unei cozi de mesaje cu o denumire deja existentă, se va întoarce INVALID_HANDLE_VALUE
.
Atenție! Handle-ul întors de această funcție poate fi folosit pentru a efectua doar operații de citire (nu și de scriere) cu coada de mesaje.
Pentru a deschide o coada de mesaje pentru scriere, se folosește funcția CreateFile care va primi în loc de numele fișierului denumirea cozii de mesaje care se dorește a fi deschisă și flagul FILE_SHARE_READ
. Pentru a permite accesul concomitent al mai multor clienți, trebuie adăugat și flagul FILE_SHARE_WRITE
.
Citirea, respectiv scrierea din/în cozile de mesaje sunt asemănătoare cu operațiile cu fișiere, folosindu-se aceleași funcții:
Pentru a obține informații despre o coadă de mesaje, se folosește funcția GetMailslotInfo ce are următoarea sintaxă:
BOOL GetMailslotInfo( HANDLE hMailslot, LPDWORD lpMaxMessageSize, LPDWORD lpNextSize, LPDWORD lpMessageCount, LPDWORD lpReadTimeout );
#include <windows.h> #include "utils.h" LPSTR lpszSlotName = "\\\\.\\mailslot\\sample_mailslot"; int main(void) { DWORD cbMessage, cMessage, cbRead, dwRet; HANDLE hMailslot; BOOL bRet; LPSTR lpszBuffer; /* Create Mailslot */ hMailslot = CreateMailslot( lpszSlotName, 0, /* no maximum message size */ MAILSLOT_WAIT_FOREVER, /* no expiration period */ NULL); /* no security attributes */ DIE(hMailslot == INVALID_HANDLE_VALUE, "CreateMailSlot"); /* Timeout - waiting for clients */ Sleep(5000); /* Get number of messages form Mailslot */ bRet = GetMailslotInfo( hMailslot, /* mailslot handle */ (LPDWORD) NULL, /* no maximum message size */ &cbMessage, /* size of next message */ &cMessage, /* number of messages */ (LPDWORD) NULL); /* no read time-out */ DIE(bRet == FALSE, "GetMailslotInfo"); /* Read all messages from Mailslot */ while (cMessage != 0) { lpszBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbMessage); DIE(lpszBuffer == NULL, "HeapAlloc"); bRet = ReadFile( hMailslot, lpszBuffer, cbMessage, &cbRead, (LPOVERLAPPED) NULL); DIE(bRet == FALSE, "ReadFile from Mailslot"); printf("Received: %s\n", lpszBuffer); HeapFree(GetProcessHeap(), 0, lpszBuffer); bRet = GetMailslotInfo( hMailslot, /* mailslot handle */ NULL, /* no maximum message size */ &cbMessage, /* size of next message */ &cMessage, /* number of messages */ NULL); /* no read time-out */ DIE(bRet == FALSE, "GetMailslotInfo"); } /* end while */ dwRet = CloseHandle(hMailslot); DIE (dwRet == FALSE, "CloseHandle"); return 0; }
#include <windows.h> #include "utils.h" LPSTR lpszSlotName = "\\\\.\\mailslot\\sample_mailslot"; int main(void) { HANDLE hMailslot; BOOL bRet; DWORD cbWritten, dwRet; LPSTR lpszBuffer = "Testing Mailslot"; /* Open Mailslot */ hMailslot = CreateFile( lpszSlotName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); DIE(hMailslot == INVALID_HANDLE_VALUE, "CreateFile"); /* Send message */ bRet = WriteFile( hMailslot, lpszBuffer, (DWORD) lstrlen(lpszBuffer) + 1, &cbWritten, NULL); DIE(bRet == FALSE, "Write file to Mailslot"); printf("Message successfully send\n"); /* Close Mailslot */ dwRet = CloseHandle(hMailslot); DIE (dwRet == FALSE, "CloseHandle"); return 0; }
Memoria partajată permite accesul mai multor procese la un fișier ca și când fișierul ar fi o zonă de memorie. Astfel se pot folosi toate operațiile aplicabile asupra memoriei, inclusiv pointeri.
O facilitate specială a FileMapping
este aceea de memorie partajată identificată printr-un nume (named shared memory
).
Atenție! Accesul la o zonă de memorie partajată trebuie reglementat folosind unul din mecanismele de sincronizare descrise mai sus!
Pentru crearea unei zone de memorie partajată se folosesc două funcții care trebuie apelate în această ordine :
File Mapping
, reprezentat de un HANDLE
.
CreateFileMapping creează o resursă (un obiect) de tipul FileMapping
și are următoarea sintaxă :
HANDLE CreateFileMapping( HANDLE hFile, LPSECURITY_ATTRIBUTES lpAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCTSTR lpName );
Dacă există un obiect cu același nume, dar de alt tip, funcția va eșua și va întoarce NULL
.
MapViewOfFile întoarce un pointer la zona de memorie partajată și are sintaxa :
LPVOID MapViewOfFile( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap );
Pentru a accesa o zonă de memorie partajată, creată de alt proces, se utilizează următoarele funcții (în ordinea specificată) :
OpenFileMapping
- o funcție pregătitoare care accesează (deschide) un obiect de tipul File Mapping
.MapViewOfFile
- pentru a mapa efectiv zona de memorie.
OpenFileMapping accesează o resursă/obiect deja existent de tipul FileMapping
și are sintaxa :
HANDLE OpenFileMapping( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName );
Pentru a demapa o zonă de memorie partajată, care a fost anterior mapată folosind funcția MapViewOfFile()
, se folosește funcția UnmapViewOfFile care are următoarea sintaxă :
BOOL UnmapViewOfFile( LPCVOID lpBaseAddress );
Serverul creează o zonă de memorie partajată, iar apoi așteaptă un interval de timp. Clientul deschide zona de memorie partajată și scrie un mesaj la începutul ei. Serverul termină așteptarea și afișează conținutul zonei de memorie.
#include <windows.h> #include "utils.h" #define BUF_SIZE 256 LPSTR szMapName = "MyFileMappingObject"; LPSTR szMsg = "Testing shared memory on windows"; int main(void) { HANDLE hMapFile; LPVOID lpMapAddress; BOOL bRet; hMapFile = CreateFileMapping( INVALID_HANDLE_VALUE, /* use swap, not a particular file */ NULL, /* default security */ PAGE_READWRITE, /* read/write access */ 0, /* maximum object size (high-order DWORD) */ 1024, /* maximum object size (low-order DWORD) */ szMapName); /* name of mapping object */ DIE(hMapFile == NULL, "CreateFileMapping"); lpMapAddress = MapViewOfFile( hMapFile, /* handle to map object */ FILE_MAP_ALL_ACCESS, /* read/write permission */ 0, /* offset (high-order) */ 0, /* offset (low-order) */ 0); DIE(lpMapAddress == NULL, "MapViewOfFile"); ZeroMemory(lpMapAddress, strlen(szMsg) + 1); CopyMemory(lpMapAddress, szMsg, strlen(szMsg)); Sleep(5000); bRet = UnmapViewOfFile(lpMapAddress); DIE(bRet == FALSE, "UnampViewOfFile"); bRet = CloseHandle(hMapFile); DIE(bRet == FALSE, "CloseHandle"); return 0; }
#include <windows.h> #include "utils.h" #define BUF_SIZE 256 LPSTR szMapName = "MyFileMappingObject"; int main(void) { HANDLE hMapFile; LPVOID lpMapAddress; BOOL bRet; hMapFile = OpenFileMapping( FILE_MAP_ALL_ACCESS, /* read/write access */ FALSE, /* do not inherit the name */ szMapName); /* name of mapping object */ DIE(hMapFile == NULL, "CreateFileMapping"); lpMapAddress = MapViewOfFile( hMapFile, /* handle to map object */ FILE_MAP_ALL_ACCESS, /* read/write permission */ 0, /* offset (high-order) */ 0, /* offset (low-order) */ 0); DIE(lpMapAddress == NULL, "MapViewOfFile"); printf("Message in shm is: <%s>\n", lpMapAddress); bRet = UnmapViewOfFile(lpMapAddress); DIE(bRet == FALSE, "UnampViewOfFile"); bRet = CloseHandle(hMapFile); DIE(bRet == FALSE, "CloseHandle"); return 0; }
În rezolvarea laboratorului folosiți arhiva de sarcini lab05-tasks.zip
Atenție!
make clean
care va șterge resursele alocate (și eventual prost eliberate)errno
cu valoarea de retur a unei funcții. Un apel de sistem va întoarce o anumită valoare de return în caz de eroare și va seta variabila globală errno
astfel încât să indice motivul erorii.Întrucât la toate exercițiile veți avea nevoie de două console deschise în paralel, pe Linux, este recomandat să vă instalați terminator.
sudo apt-get install terminator Ctrl+Shift+O -> open hOrizontal tab Ctrl+Shift+E -> open vErtical tab Ctrl+Shift+N -> move to Next tab Ctrl+Shift+P -> move to Previous tab Ctrl+Shift+W -> close current tab
1-fun/
:sem.c
creează un semafor și îl incrementează de fiecare dată când apăsați o tastă./dev/shm/sem.my_sem
la apăsarea unei taste. Ce se întâmplă cu fișierul /dev/shm/sem.my_sem
când terminați normal programul? Dar când îl terminați folosind Ctrl+C
?console1$ ./sem Press any key to continue (E/e to exit)
console2$ watch -n 1 -d 'cat /dev/shm/sem.my_sem | hexdump -d -n 1'
shm.c
creează o zonă de memorie partajată și scrie în ea un șir de caractere. Analizați conținutul fișierului /dev/shm/my_shm
. Observați ce se întâmplă cu zona de memorie partajată când programul shm.c
se încheie normal (apăsați orice tastă) sau când este întrerupt folosind Ctrl+C
.console1$ ./shm Press any key to continue...
console2$ cat /dev/shm/my_shm | hexdump -c
mq.c
creează o coadă de mesaje și pune un mesaj în coadă la fiecare apăsare a unei taste. În Linux cozile de mesaje sunt create într-un sistem de fișiere virtual. Acest sistem de fișiere poate fi montat în ierarhia voastră de fișiere astfel:$ sudo mkdir /dev/mqueue $ sudo mount -t mqueue none /dev/mqueue
mq
și observați cum crește dimensiunea cozii la fiecare mesaj pus în coadă.console1$ ./mq Press any key to continue (E/e exit)
console2$ watch -n 1 cat /dev/mqueue/my_mqueue
SO
Computer
→ Properties
Computer Name, domain and workgroup settings
→ Change settings
Computer Name
→ Change
Shut Down
(NU restart)win\lab05.sln
1-fun-client
și 1-fun-server
1-fun-server
1-fun-client
și va introduce numele stației colegului care a pornit serverulExercițiul următor e independent de platformă, iar platforma e la alegere.
Să se implementeze un protocol simplu client-server folosind mecanisme IPC. Serverul întreține o tabelă de dispersie (hashtable), conținând cuvinte, în care se fac inserări și ștergeri comandate de mesajele primite de la clienți. Inserarea într-un tablou (bucket) se face la finalul acestuia.
Clienții primesc operațiile prin argumentele primite în linia de comanda la lansarea în execuție. Exemplu:
./client a vincent c a test p
În acest caz, clientul va trimite serverului, în ordine, mesajele: a vincent pentru adăugarea în hashtable a cuvântul “vincent”, c pentru golirea tabelei, a test pentru adăugarea cuvântului “test” și p pentru afișarea conținutului tabelei.
Exercițiul se compune din 3 părți:
sever.c
- conține codul rulat de serverclient.c
- conține codul rulat de clientcommon.h
- conține structurile necesare protocoluluigeneric_queue.h
- header cu funcțiile generale pentru lucrul cu coada de mesajegeneric_shm.h
- header cu funcțiile generale pentru lucrul cu memoria partajatăgeneric_sem.h
- header cu funcțiile generale pentru lucrul cu semafoareunix_*.c
- conține implementarea unix a funcțiilor din generic_*.h
win_*.c
- conține implementarea windows a funcțiilor din generic_*.h
hashtable.h
, hashtable.c
- reprezintă interfața și implementarea funcțiilor de lucru cu tabela de dispersiehash.h
, hash.c
- reprezintă interfața și implementarea funcției de hashcommon.h
conține structurile necesare la următoarele exerciții'a S
': trimite serverului mesajul de adăugare în hashtable a cuvântului S (add);'c
': trimite serverului mesajul de golire a conținutului tabelei (clear);'p
': clientul afișează la standard output conținutul tabelei - formatul este precizat mai jos (print);'e
': clientul îi spune serverului să își încheie execuția (exit). msgq_send
și msgq_receive
trebuie să trimită/primească toată structura message_t
primită ca parametru (nu doar unul din câmpuri)./proc interfaces
)./server
./client a test
./client e
./server
./client a test1 a test2 ./client p ./client c ./client p ./client e
3-funny_sem
client.c
) vrea să trimită serverului o valoare magică, considerată a fi "the answer to the Ultimate Question of Life, the Universe, and Everything"