I nostri amati UNIX e Linux ci forniscono varie forme di crearne uno (siamo nella categoria IPC, che è abbastanza vasta), e, visto che le interfacce disponibili non sono proprio immediatissime ho pensato di scrivere una piccola libreria ad-hoc, la libmmap, così spostiamo la complicazione nella libreria (ecco a cosa servono le librerie!) e possiamo, a livello applicativo, fare e disfare i nostri Memory Mapped File come, quando e quanto vogliamo, con grande facilità.
Da qui in avanti seguirò lo stesso approccio che ho usato in un mio vecchio post, così approfitto anche per riciclare un po' di frasi (si, lo so, è un auto-plagio, ma non credo che mi arrabbierò per questo...).
Vai con la descrizione!
Ma cosa ci facciamo in un blog di programmazione? |
Vediamo l'header file, libmmap.h:
#ifndef LIBMMAP_H #define LIBMMAP_H #include <sys/mman.h> // definizione struttura messaggio typedef struct { int type; // tipo di messaggio int data_a; // un dato (esempio) int data_b; // un altro dato (esempio) char text[1024]; // testo del messaggio } Message; // prototipi globali Message *memMapOpen(const char *memname); int memMapClose(Message *ptr, const char *memname); int memMapFlush(Message *ptr); #endif /* LIBMMAP_H */Semplice e auto-esplicativo, no? La nostra libreria è formata da due funzioni canoniche (apri, chiudi), che ci permettono di aprire un Memory Mapped File dandogli un nome e un size, e di chiuderlo usando gli stessi dati più un pointer che ci ha restituito la funzione di apertura. Per mappare il file definiamo un tipo Message che contiene dei dati e un campo testo: ho usato la forma messaggio perché la libreria ci serve per comunicare tra processi, ma, in realtà, possiamo definire il tipo come vogliamo. La terza funzione (flush) ci permette di aggiornare il file sul disco (ma di questo parleremo meglio nella prossima puntata...).
Notare l'uso degli include guard per il nostro header file: questa è una libreria che possiamo usare anche in un grosso progetto, e bisogna premunirsi contro eventuali/erronee inclusioni multiple.
Vediamo ora la prima parte del doppio esempio d'uso, msgwriter.c:
#include <stdio.h> #include <string.h> #include "libmmap.h" // main del programma di test int main(int argc, char *argv[]) { // apre memory mapped file const char *memname = "message"; Message *msg = memMapOpen(memname); // loop infinito di scrittura for (;;) { // compone messaggio per il reader printf("Scrivi un messaggio per il reader: "); scanf("%s", msg->text); // flush dati nella memoria condivisa. N.B.: OPZIONALE //memMapFlush(msg); // loop sleep mySleep(100); } // chiude memory mapped file memMapClose(msg, memname); }Semplice, no? Apro il file condiviso in memoria e lo uso per inviare messaggi (in loop infinito) all'altra applicazione di test , msgreader.c:
#include <stdio.h> #include <string.h> #include "libmmap.h" // main del programma di test int main(int argc, char *argv[]) { // apre memory mapped file const char *memname = "message"; Message *msg = memMapOpen(memname); // loop infinito di lettura for (;;) { // test presenza messaggio in memoria condivisa if (strlen(msg->text)) { // legge e reset msg dalla memoria condivisa printf("mi hai scritto: %s\n", msg->text); msg->text[0] = 0; } // loop sleep mySleep(100); } // chiude memory mapped file memMapClose(msg, memname); }Il reader è, come si vede, una applicazione speculare del writer, la prima legge e la seconda scrive. Il meccanismo di sincronizzazione scelto è semplice: il reader legge solo quando si accorge (con una strlen())che sono cambiati i dati nel file. Per un uso semplice questo metodo va più che bene, ma, per applicazioni pìu complesse (magari multithread) bisognerebbe usare un sistema più evoluto, e, solitamente si inserisce nel tipo Message stesso un mutex o un semaforo (ma questa è un altra storia, magari in futuro farò un post sull'argomento).
Usando le due applicazioni in due terminali differenti cosa vedremo? in quella del writer avremo:
Scrivi un messaggio per il reader: pippo Scrivi un messaggio per il reader: paperino Scrivi un messaggio per il reader:e in quelle del reader vedremo:
mi hai scritto: pippo mi hai scritto: paperinoPer mandare a dormire i loop ho usato la funzione mySleep(), che è una nostra vecchia conoscenza: si pùo inserire in una libreria a parte o nella stessa libreria libmmap. Ah, dimenticavo: i loop sono infiniti, quindi il file, in realtà, non viene mai chiuso: ricordarsi che, in una applicazione reale, la funzione di chiusura si deve usare quando si smette di usare il file.
Per oggi abbiamo finito. I più volenterosi potranno, nell'attesa della seconda parte, scrivere una propria implementazione, e poi confrontarla con la mia, ma la mia sarà sicuramente meglio... (si allontana, un altra volta, sghignazzando).
Ciao e al prossimo post!
Nessun commento:
Posta un commento