En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

lunes, 14 de marzo de 2016

Irrational File
cómo crear un Memory Mapped File en C - pt.2

Estamos de vuelta con nuestro Irrational Man (oops... Irrational File), o sea, un file conpartido en memoria. Después de describir el header file (libmmap.h) y un doble ejemplo del uso (msgreader.c y msgwriter.c) es hora de describir la implementación real. Obviamente, los que no han leído la primera parte, deberían avergonzarse y ir en seguida a leerla, y luego volver aquí.
te explico: supongamos que este es un plato compartido...
Los lectores más atentos se habrán dado cuenta, releyéndolo, que he modificado ligeramente el post anterior (magia del blogger ...) añadiendo (y no-usando) una función de flush que no estaba allí antes, y quitando el parámetro size de las funciones (veremos más adelante por qué).

Vamos a ver lo que contiene la librería (libmmap.c)... ¡vamos con el codigo!
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include "libmmap.h"

// memMapOpen() - abre un memory mapped file
Message *memMapOpen(
    const char *memname)
{
    // abre un memory mapped file (el file "memname" se crea en /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;
    }

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

    // mapea 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() - cierra un memory mapped file
int memMapClose(
    Message    *ptr,
    const char *memname)
{
    // un-mapea un memory mapped file
    if (munmap(ptr, sizeof(Message)) < 0) {
        fprintf(stderr, "munmap error: %s\n", strerror(errno));
        return -1;
    }

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

// memMapFlush() - flush de un memory mapped file
int memMapFlush(
    Message *ptr)
{
    // sync en disco de un memory mapped file
    return msync(ptr, sizeof(Message), MS_SYNC);
}
Como se puede ver, no es mucha cosa pero densa. Como siempre, el código es auto-explicativo, ampliamente comentado y los comentarios hablan por sí mismos.

Las funciones de open, close y flush usan las oportunas system call Linux/POSIX para tratar el nuestro Memory Mapped File. La función de flush es sólo un wrapper para la llamada msync() y, normalmente, no es necesario utilizarla: con esta librería queremos tratar a los archivos mapeados en memoria para compartir datos entre procesos, por lo que, no sólo nos interesa poco que el archivo tenga una imagen real en el disco, sino que, por una simple cuestión de rendimiento, deberíamos evitar de descargar en el disco todos los cambios realizados en la memoria, si no seria como usar files reales. Entonces, ¿de que sirve el flush? Sirve sólo para mantener una eventual versión real y actualizada del file compartido, en el caso que queremos tratarlo, también, con la clásicas funciones open(), close(), read(), etc. Por esto en el file msgwriter.c descritos en el post anterior la llamada memMapFlush() está comentada y descrita como opcional.

¿Y el clásico parámetro size donde está? Bueno, esta es una librería especializada para un tipo específico que hemos definido (el tipo Message), entonces dentro de la librería podemos usar sizeof(Message) como y cuando queremos. El size, sin embargo, se utiliza para funciones de librerías más genéricas (que tratan tipos void*), en las cuales el campo de datos puede contener varias cosas y la función puede disponer del size de los datos sólo a través de un parámetro apósito.

Deberes para las vacaciones de la Semana Santa: hacer una versión genérica de la librería utilizando void *ptr en lugar de Mensaje *ptr en todas las funciones (¡acordarse de añadir también el size!). Os daréis cuenta que hay que hacer muy pocos cambios, una cosa muy simple. ¡Confiar en el vuestro C-Blogger favorito!

¡Hasta el próximo post!