Ritorna indietro

Lezione 5 - Memoria condivisa e coda di messaggi

Pubblicato il 3/29/22 da SeekBytes – 2638 parole

Memoria condivisa

Concetti fondamentali

Una memoria condivisa è un segmento di memoria fisica gestito dal Kernel, che permette a due o più processi di scambiarsi dati. Una volta collegata, anche più di una volta, la memoria condivisa fa parte dello spazio di indirizzamento virtuale del processo, e non è richiesto alcun intervento del kernel. I dati scritti in una memoria condivisa sono immediatamente disponibili a tutti gli altri processo che condividono lo stesso segmento. Tipicamente, qualche metodo di sincronizzazione è richiesto in modo che i processi non accedano simultaneamente alla memoria condivisa (per esempio, utilizzare i semafori!).

Creazione ed apertura (shmget)

La chiamata di sistema shmget crea un nuovo segmento di memoria condivisa o ottiene l’identificatore di uno esistente. Il contenuto di un segmento di memoria condivisa appena creato è inizializzato a 0.

#include <sys/shm.h>

// Returns a shared memory segment identifier on success, or -1 on error
int shmget(key_t key, size_t size, int shmflg);

Gli argomenti chiave sono:

shmflg è una maschera di bit che specifica i permessi (vedere la chiamata di sistema open(...) di sistema, argomento mode) da porre su un nuovo segmento di memoria condivisa o controllati su un segmento esistente. In aggiunta, i seguenti flag possono essere ORed (|) in shmflg:

Esempio di creazione di un segmento di memoria condiviso
int shmid;
ket_t key = //... (generate a key in some way, i.e. with ftok)
size_t size = //... (compute size value in some way)
// A) delegate the problem of finding a unique key to the kernel
shmid = shmget(IPC_PRIVATE, size, S_IRUSR | S_IWUSR);
// B) create a shared memory with identifier key, if it doesn’t already exist
shmid = shmget(key, size, IPC_CREAT | S_IRUSR | S_IWUSR);
// C) create a shared memory with identifier key, but fail if it exists already
shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);

Attaccare un segmento (shmat)

La chiamata di sistema shmat attacca il segmento di memoria condivisa identificato da shmid allo spazio degli indirizzi virtuali del processo chiamante.

#include <sys/shm.h>
// Returns address at which shared memory is attached on success
// or (void *)-1 on error
void *shmat(int shmid, const void *shmaddr, int shmflg);

Normalmente, shmaddr è NULL, per le seguenti ragioni:

Un processo figlio eredita i segmenti di memoria condivisa del suo genitore. La memoria condivisa fornisce un facile metodo di IPC tra genitore e figlio!

Esempio di come attaccare un segmento di memoria condivisa
// attach the shared memory in read/write mode
int *ptr_1 = (int *)shmat(shmid, NULL, 0);
// attach the shared memory in read only mode
int *ptr_2 = (int *)shmat(shmid, NULL, SHM_RDONLY);

// N.B. ptr_1 and ptr_2 are different!
// But they refer to the same shared memory!
// write 10 integers to shared memory segment
for (int i = 0; i < 10; ++i)
	ptr_1[i] = i;

// read 10 integers from shared memory segment
for (int i = 0; i < 10; ++i)
	printf("integer: %d\n", ptr_2[i]);

Che cosa stamperà il programma? Possiamo utilizzare ptr_2 per scrivere sul segmento della memoria? (Spoiler: no).

Staccare il segmento (shmdt)

Quando un processo non ha più bisogno di accedere ad un segmento di memoria condivisa, può chiamare shmdt per staccare il segmento dal suo spazio di indirizzo virtuale. L’argomento shmaddr identifica il segmento da staccare, ed è un valore restituito da una precedente chiamata a shmat.

#include <sys/shm.h>
// Returns 0 on success, or -1 on error
int shmdt(const void *shmaddr);

Durante un exec, tutti i segmenti di memoria condivisa collegati sono staccati. I segmenti di memoria condivisa sono anche distaccati automaticamente alla terminare.

Esempio di come staccare un segmento di memoria condivisa
// attach the shared memory in read/write mode
int *ptr_1 = (int *)shmat(shmid, NULL, 0);
if (ptr_1 == (void *)-1)
errExit("first shmat failed");
// attach the shared memory in read only mode
int *ptr_2 = (int *)shmat(shmid, NULL, SHM_RDONLY);
if (ptr_2 == (void *)-1)
errExit("second shmat failed");
//...
// detach the shared memory segments
if (shmdt(ptr_1) == -1 || shmdt(ptr_2) == -1)
errExit("shmdt failed");

shmctl

La chiamata di sistema shmctl esegue operazioni di controllo su un segmento di memoria condivisa.

#include <sys/msg.h>
// Returns 0 on success, or -1 error
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

L’argomento shmid è un identificatore di memoria condivisa. L’argomento cmd specifica l’operazione da eseguire sulla memoria condivisa:

Rimuovere un segmento di memoria condivisa
if (shmctl(shmid, IPC_RMID, NULL) == -1)
	errExit("shmctl failed");
else
	printf("shared memory segment removed successfully\n");

Operazioni di controllo

Per ogni segmento di memoria condivisa il kernel ha una struttura dati associata shmid_ds della seguente forma:

struct shmid_ds {
	struct ipc_perm shm_perm; /* Ownership and permissions */
	size_t shm_segsz; /* Size of segment in bytes */
	time_t shm_atime; /* Time of last shmat() */
	time_t shm_dtime; /* Time of last shmdt() */
	time_t shm_ctime; /* Time of last change */
	pid_t shm_cpid; /* PID of creator */
	pid_t shm_lpid; /* PID of last shmat() / shmdt() */
	shmatt_t shm_nattch; // Number of currently attached
}; // processes

Con IPC_STAT e IPC_SET possiamo rispettivamente ottenere e aggiornare questa struttura di dati.

Coda messaggi

Creazione e apertura

La system call msgget crea una coda di messaggi oppure ottiene l’identificatore di una coda già esistente.

#include <sys/msg.h>
// Returns message queue identifier on success, or -1 error
int msgget(key_t key, int msgflg);

L’argomento key è una chiave IPC, msgflg è una maschera di bit che specifica i permessi (vedi la chiamata di sistema open(…), argomento mode) da inserire in una nuova coda di messaggi, o da controllare in una coda esistente. In aggiunta, i seguenti flag possono essere ORed (|) in msgflg:

Esempio per creare una coda di messaggi int msqid; ket_t key = //… (generate a key in some way, i.e. with ftok) // A) delegate the problem of finding a unique key to the kernel msqid = msgget(IPC_PRIVATE, S_IRUSR | S_IWUSR); // B) create a queue with identifier key, if it doesn’t already exist msqid = msgget(key, IPC_CREAT | S_IRUSR | S_IWUSR); // C) create a queue with identifier key, but fail if it exists already msqid = msgget(key, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);

Struttura dei messaggi

Un messaggio in una coda di messaggi segue sempre la seguente struttura:

struct mymsg {
	long mtype; /* Tipo di messaggio */
	char mtext[]; /* corpo del messaggio */
};

La prima parte di un messaggio contiene il tipo di messaggio, specificato come un intero lungo maggiore di 0. Il resto del messaggio è una struttura definita dal programmatore di lunghezza e contenuto arbitrari (non è necessariamente un array di char). Infatti, può essere omesso se non è necessario.

Inviare un messaggio (msgsnd)

La chiamata di sistema msgsnd scrive un messaggio in una coda di messaggi.

#include <sys/msg.h>
// Returns 0 on success, or -1 error
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
Esempio di invio messaggio
// Message structure
struct mymsg {
	long mtype;
	char mtext[100]; /* array of chars as message body */
} m;
// message has type 1
m.mtype = 1;
// message contains the following string
char *text = "Ciao mondo!";
memcpy(m.mtext, text, strlen(text) + 1); // why +1 here?
// size of m is only the size of its mtext attribute!
size_t mSize = sizeof(struct mymsg) - sizeof(long);
// sending the message in the queue
if (msgsnd(msqid, &m, mSize, 0) == -1)
	errExit("msgsnd failed");
Esempio di invio messaggio 2
// Message structure
struct mymsg {
	long mtype;
	int num1, num2; /* two integers as message body */
} m;
// message has type 2
m.mtype = 2;
// message contains the following numbers
m.num1 = 34;
m.num2 = 43;
// size of m is only the size of its mtext attribute!
size_t mSize = sizeof(struct mymsg) - sizeof(long);
// sending the message in the queue
if (msgsnd(msqid, &m, mSize, 0) == -1)
	errExit("msgsnd failed");
Esempio di invio messaggio senza body
// Message structure
struct mymsg {
	long mtype;
	/* The message has not got body. It has just a type!*/
} m;
// message has type 3
m.mtype = 3;
// size of m is only the size of its mtext attribute!
size_t mSize = sizeof(struct mymsg) - sizeof(long); // 0!
// sending the message in the queue
if (msgsnd(msqid, &m, mSize, IPC_NOWAIT) == -1) {
if (errno == EAGAIN) {
printf("The queue was full!\n");
} else {
errExit("msgsnd failed");
}
}

Ricevere un messaggio (msgrcv)

La chiamata di sistema msgrcv legge e rimuove un messaggio da una coda.

#include <sys/msg.h>
// Returns number of bytes copied into msgp on success, or -1 error
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtype, int msgflg);

L’argomento msqid è un identificatore di coda di messaggi. Lo spazio massimo disponibile nel campo mtext del buffer msgp è specificato dall’argomento msgsz.

Il valore nel campo msgtype seleziona il messaggio recuperato come segue:

Data la definizione del messaggio: (mtype, char) e la seguente coda: {(300,'a'); (100,'b'); (200,'c'); (400,'d'); (100,'e')} Una serie di chiamate msgrcv con msgtype=-300 recupera i messaggi: (100,'b'), (100,'e'), (200,'c'), (300,'a')

L’argomento msgflg è una maschera di bit formata da OR insieme a zero o più dei seguenti flag:

Esempio 1: recuperare un messaggio
// message structure definition
struct mymsg {
	long mtype;
	char mtext[100]; /* array of chars as message body */
} m;
// Get the size of the mtext field.
size_t mSize = sizeof(struct mymsg) - sizeof(long);
// Wait for a message having type equals to 1
if (msgrcv(msqid, &m, mSize, 1, 0) == -1)
	errExit("msgrcv failed");
Esempio 2: copiare i primi N byte di un messaggio
// message structure definition
struct mymsg {
	long mtype;
	char mtext[100]; /* array of chars as message body */
} m;
// Set an arbitrary size for the size.
size_t mSize = sizeof(char) * 50;
// Wait for a message having type equals to 1, but copy its first 50 bytes only
if (msgrcv(msqid, &m, mSize, 1, MSG_NOERROR) == -1)
	errExit("msgrcv failed");
Esempio 3: selezione di un messaggio in base al mtype
// Message structure
struct mymsg {
	long mtype;
} m;
// In polling mode, try to get a message every SEC seconds.
while (1) {
	sleep(SEC);
	// Performing a nonblocking msgrcv.
	if (msgrcv(msqid, &m, 0, 3, IPC_NOWAIT) == -1) {
		if (errno == ENOMSG) {
			printf("No message with type 3 in the queue\n");
		} else {
			errExit("msgrcv failed");
		}
	} else {
	printf("I found a message with type 3\n");
	}
}

Operazioni di controllo msgctl

La chiamata di sistema msgctl esegue operazioni di controllo sulla coda dei messaggi.

#include <sys/msg.h>
// Returns 0 on success, or -1 error
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
Esempio su come rimuovere una coda di messaggi
if (msgctl(msqid, IPC_RMID, NULL) == -1)
	errExit("msgctl failed");
else
	printf("message queue removed successfully\n");

Struttura msqid_ds

Per ogni coda di messaggi, il kernel ha associato una struttura chiamata msqid_ds della seguente forma:

struct msqid_ds {
	struct ipc_perm msg_perm; /* Ownership and permissions */
	time_t msg_stime; /* Time of last msgsnd() */
	time_t msg_rtime; /* Time of last msgrcv() */
	time_t msg_ctime; /* Time of last change */
	unsigned long __msg_cbytes; /* Number of bytes in queue */
	msgqnum_t msg_qnum; /* Number of messages in queue */
	msglen_t msg_qbytes; /* Maximum bytes in queue */
	pid_t msg_lspid; /* PID of last msgsnd() */
	pid_t msg_lrpid; /* PID of last msgrcv() */
};

Con IPC_STAT e IPC_SET possiamo rispettivamente ottenere e aggiornare questa struttura di dati.

Esempio su come cambiare i limiti di dimensione di una message queue

struct msqid_ds ds; // Get the data structure of a message queue if (msgctl(msqid, IPC_STAT, &ds) == -1) errExit(“msgctl”);

// Change the upper limit on the number of bytes in the mtext // fields of all messages in the message queue to 1 Kbyte ds.msg_qbytes = 1024;

// Update associated data structure in kernel if (msgctl(msqid, IPC_SET, &ds) == -1) errExit(“msgctl”);

Visione conclusiva delle interfacce System V

InterfacciaCoda di messaggiSemaforiMemoria condivisa
File header<sys/msg.h><sys/sem.h><sys/shm.h>
Struttura datimsqid_dssemid_dsshmid_ds
Creazione/Aperturamsgget(..)semget(..)shmget(...)
Chiusuranessunanessunashmdt(..)
Operazioni di controllomsgctl(..)semctl(..)shmctl(..)
Eseguire operazioni di IPCmsgsnd(..) msgrcv(...)semop() per aggiustare i valoriaccesso diretto alla memoria condivisa