Nuestros queridos UNIX y Linux nos proporcionan diversas formas para crear uno (estamos en la categoría de IPC, que es bastante extensa), y ya que las interfaces disponibles no son clarísimas he pensado en escribir una pequeña librería ad-hoc, la libmmap, así movemos las complicaciones en la librería (¡a eso sirven las librerias!) y podemos, a nivel de aplicación, hacer y deshacer nuestro Memory Mapped File cómo, cuándo y cómo queremos, con facilidad.
A partir de aquí, seguiré el mismo enfoque que utilicé en un mio antiguo post, así aprovecho también para reciclar un poco de frases (sí, lo sé, es un auto-plagio, pero no creo que voy a enfadarme por esto...).
¡Vamos con la descripción!
¿Pero que hacemos en un blog de programación? |
Vemos el header file, libmmap.h:
#ifndef LIBMMAP_H #define LIBMMAP_H #include <sys/mman.h> // definición estructura mensaje typedef struct { int type; // tipo de mensaje int data_a; // un dato (ejemplo) int data_b; // un otro dato (ejemplo) char text[1024]; // texto del mensaje } Message; // prototipos globales Message *memMapOpen(const char *memname); int memMapClose(Message *ptr, const char *memname); int memMapFlush(Message *ptr); #endif /* LIBMMAP_H */Simple y que se explica por sí mismo, ¿no? Nuestro producto se compone de dos funciones canónicas (abre, cierra) que nos permiten abrir Memory Mapped File, dándole un nombre y un size, y de cerrarle con los mismos datos mas un pointer que nos devuelve la función de apertura. Para mapear el file definimos un tipo Message que contiene datos y un campo de texto: he utilizado el formado mensaje porque la librería nos sirve para la comunicación entre procesos, pero, de hecho, podemos definir el tipo que queremos. La tercera función (flush) nos permite poner al día el file en el disco (pero de esto hablaremos mejor en el próximo episodio...).
Observe el uso de los include guard para el nuestro header fileg: esta es una librería que podemos utilizar en un gran proyecto, y debemos evitar eventuales/erróneas inclusiones múltiplas.
Ahora vamos a ver la primera parte del doble ejemplo de uso, msgwriter.c:
#include <stdio.h> #include <string.h> #include "libmmap.h" // main del programa de test int main(int argc, char *argv[]) { // abre memory mapped file const char *memname = "message"; Message *msg = memMapOpen(memname); // loop infinito de escritura for (;;) { // compone mensaje para el reader printf("Escribe un mensaje para el reader: "); scanf("%s", msg->text); // flush datos en la memoria compartida. NOTA: OPCIONAL //memMapFlush(msg); // loop sleep mySleep(100); } // cierra memory mapped file memMapClose(msg, memname); }Sencillo, ¿no? Abro el file mapeado en memoria y lo uso para enviar mensajes (en loop infinito) a la otra aplicación de test , msgreader.c:
#include <stdio.h> #include <string.h> #include "libmmap.h" // main del programa de test int main(int argc, char *argv[]) { // abre memory mapped file const char *memname = "message"; Message *msg = memMapOpen(memname); // loop infinito de lectura for (;;) { // test presencia mensaje en memoria compartida if (strlen(msg->text)) { // lee y reset msg de la memoria compartida printf("me has escrito: %s\n", msg->text); msg->text[0] = 0; } // loop sleep mySleep(100); } // cierra memory mapped file memMapClose(msg, memname); }El reader es, como se nota, una aplicación especular del writer, la primera lee y la segunda escribe. El mecanismo de sincronización elegido es simple: el reader lee sólo cuando se da cuenta (con strlen()) que han cambiado los datos en el file. Para un uso sencillo este método está muy bien, pero para aplicaciones complejas (posiblemente multithread) se debería utilizar un sistema más avanzado, y, normalmente, se añade en el mismo tipo Message un mutex o un semáforo (pero eso es otra historia, quizás en el futuro voy a hacer un post sobre el tema).
¿Usando las dos aplicaciones en dos terminales diferentes qué vamos a ver? en la del writer tendremos:
Escribe un mensaje para el reader: hola Escribe un mensaje para el reader: que tal Escribe un mensaje para el reader:y en la del reader veremos:
me has escrito: hola me has escrito: que talPara enviar a dormir los loop he utilizado la función mySleep(), que es una nuestra vieja conocida: se puede añadir en una librería a parte o en la misma librería libmmap. Ah, me olvidaba: los loop son infinitos, entonces el file, de hecho, nunca se cierra: recordar que, en una aplicación real, la función de cierre se tiene que utilizar cuando se deja de usar el file.
Para hoy terminamos. Los mas trabajadores podrán, esperando la segunda parte, escribir su propia implementación, y luego compararla con la mía, pero la mía será seguramente mejor... (se aleja, otra vez, riendo).
¡Hasta el próximo post!