Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

martedì 8 marzo 2016

Irrational File
come creare un Memory Mapped File in C - pt.2

Rieccoci col nostro Irrational Man (oops... Irrational File), ossia un file condiviso in memoria. Dopo aver descritto l'header file (libmmap.h) e un doppio esempio di uso (msgreader.c e msgwriter.c) è venuto il momento di descrivere la vera e propria implementazione. Ovviamente, chi non ha letto la prima parte, deve vergognarsi e andare subito a leggerla, e poi tornare qui.
ti spiego: fai finta che questo sia un piatto condiviso...
I lettori più attenti si saranno accorti, rileggendolo, che ho modificato leggermente il post precedente (magia del blogger...) aggiungendo (e non-usando) una funzione di flush che prima non c'era e togliendo il parametro size dalle funzioni (vedremo più avanti il perché).

Vediamo ora cosa contiene la libreria (libmmap.c)... vai col codice!
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include "libmmap.h"

// memMapOpen() - apre un memory mapped file
Message *memMapOpen(
    const char *memname)
{
    // apre un memory mapped file (il file "memname" è creato in /dev/shm)
    int fd;
    if ((fd = shm_open(memname, O_CREAT | O_RDWR, 0666)) < 0) {
        fprintf(stderr, "shm_open error: %s\n", strerror(errno));
        return NULL;
    }

    // tronca un memory mapped file
    if (ftruncate(fd, sizeof(Message)) < 0) {
        fprintf(stderr, "ftruncate error: %s\n", strerror(errno));
        return NULL;
    }

    // mappa un memory mapped file
    Message *ptr;
    if ((ptr = mmap(NULL, sizeof(Message), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) < 0) {
        fprintf(stderr, "mmap error: %s\n", strerror(errno));
        return NULL;
    }

    return ptr;
}

// memMapClose() - chiude un memory mapped file
int memMapClose(
    Message    *ptr,
    const char *memname)
{
    // un-mappa un memory mapped file
    if (munmap(ptr, sizeof(Message)) < 0) {
        fprintf(stderr, "munmap error: %s\n", strerror(errno));
        return -1;
    }

    // cancella un memory mapped file
    if (shm_unlink(memname) < 0) {
        fprintf(stderr, "shm_unlink error: %s\n", strerror(errno));
        return -1;
    }
}

// memMapFlush() - flush di un memory mapped file
int memMapFlush(
    Message *ptr)
{
    // sync su disco di un memory mapped file
    return msync(ptr, sizeof(Message), MS_SYNC);
}
Come potete vedere, poca roba ma densa. Come sempre il codice è auto-esplicativo, ampiamente commentato e con commenti che parlano da soli.

Le funzioni di open, close e flush usano le opportune system call Linux/POSIX per trattare il nostro Memory Mapped File. La funzione di flush è solo un wrapper per la chiamata msync() e, normalmente, non è necessario usarla: con questa libreria noi vogliamo trattare file mappati in memoria per condividere dati tra processi, per cui, non solo ci interessa poco che il file abbia una immagine reale sul disco, ma, per una semplice questione di prestazioni dovremmo evitare di scaricare realmente sul disco tutti i cambi effettuati in memoria, se no tanto varrebbe far comunicare i processi con dei file veri. Quindi a cosa serve il flush? Serve solo ad avere una eventuale versione reale e aggiornata del file condiviso, nel caso volessimo trattarlo anche con le classiche funzioni open(), close(), read(), ecc. Per questo nel file msgwriter.c descritto nel post precedente la chiamata memMapFlush() è commentata e descritta come opzionale.

Ed il classico parametro size che fine ha fatto? Beh, questa è una libreria specializzata per un tipo specifico che abbiamo definito (il tipo Message), quindi internamente alla libreria possiamo usare sizeof(Message) come e quando vogliamo. Il size, invece, serve per funzioni di librerie più generiche (che trattano tipi void*), dove il campo dati può contenere svariate cose e la funzione può disporre del size dei dati solo attraverso un apposito parametro.

Compito delle vacanze di Pasqua: fare una versione generica della libreria usando void *ptr al posto di Message *ptr in tutte le funzioni (ricordatevi di aggiungere anche il size!). Vi accorgerete che sono quattro modifiche in croce, una cosa semplicissima. Fidatevi del vostro C-Blogger favorito!

Ciao e al prossimo post!