Sehr Umfangreiche Webseite zum Programmieren in C Perl CGI, Skripting, Linux, Systemprogrammierung C C++ C/C++ ANSI C Linux Linuxsystemprogrammierung Memory Mapped I/O mmap Linuxsystemprogrammieren Memory Mapped I/O mmap 9. Fortgeschrittene Ein/Ausgabe

9.1. Memory Mapped I/O - mmap           zurück Ein Kapitel tiefer zum Inhaltsverzeichnis

Mit Memory Mapped können Sie z.B. eine Verbindung zwischen einer Datei auf der Festplatte und einem Puffer im Arbeitsspeicher (RAM) herstellen. Und aus diesem Puffer im Arbeitsspeicher können Sie diese Daten lesen und schreiben ohne den Funktionen read() und write(). Genauer kann auch man sagen, dass in unserem Arbeitsspeicher ein Speicherabbild von der Datei auf der Festplatte gemacht wurde. Hier nun einige Vorteile dieser Methode Daten zu verarbeiten ...

Mit folgender Funktion können Sie ein Memory Mapped einrichten (SVR4 und BSD) ...

#include <sys/types>
#include <sys/mman.h>

caddr_t mmap(addr_t adresse, size_t laenge, int protect, int flag, int fd, off_t offset);

Was sich auf dem ersten Blick etwas Aufwändig betrachten lässt, stellt sich als recht einfach da. Die Funktion mmap() gibt die Anfangsadresse des zugeordneten Speicherbereiches zurück oder im Fehlerfall -1.

In adresse legen Sie die Anfangsadresse des mapped-Speicherbereiches fest. Geben Sie hier 0 an, überlassen Sie die Anfangsadresse dem System. Mit laenge geben Sie die Anzahl Bytes an, die der mapped-Speicherberreich umfassen soll. protect legt die Schutzart an, mit den Sie den mapped-Speicherbereich festlegen. flag übergibt eine zusätzliche Forderung an den mapped-Speicherbereich. Und fd ist der Filedeskriptor, der dem ausgwählten mapped-Speicherbereich zugeordnet wurde. Mit offset legen Sie das Offset des mapped-Speicherbereiches fest.

Hier das ganze Bildlich dargestellt ...

mmap() - Geliehen aus der QNX-Dokumentation

Nun benötigen Sie noch ein paar Konstanten, die Sie an protect und flag übergeben können. Zuerst die Angaben für protect, welche Schutzart Sie für einen Speicherbereich festlegen wollen ...

Und nun die Angaben für flag. Einer der folgende zwei Forderungen an den mapped-Speicherbereich müssen immer angegeben werden ...

Folgende Operationen können Sie zu einen der beiden Optionen (MAP_SHARED und MAP_PRIVATE) hinzufügen ...

Für Linux gibt es noch die folgenden Optionen ...

Kommen wir nun zu einem Beispiel, wie Sie die Funktion mmap() einsetzen können. Wir wollen alle als Argumente in der Kommandozeile übergebene Dateien die Sie angeben, im mapped-Speicher abbilden und diese mit der Funktion write() auf dem Bildschirm ausgeben ...

#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

extern int errno;

int main(int argc, char **argv)
{
  int fd, n=0;
  void *speicherberreich;
  struct stat attr;

  while(argv[++n] != NULL)
    {
      if(( fd=open(argv[n], O_RDWR)) < 0)
       {
          fprintf(stderr, "Konnte %s nicht öffnen!!!\n",argv[n]);
          continue; /*Wieder hoch zu while*/
        }
      if(fstat(fd, &attr) == -1)
        {
          fprintf(stderr,"Fehler bei fstat.......\n");
          continue; /*Wieder hoch zu while*/
         }

/*Achtung jetzt legen wir die Datei die wir mit fd */
/*spezifiert haben in den Arbeitsspeicher*/
     speicherberreich=mmap(0, attr.st_size, PROT_READ, MAP_SHARED, fd, 0);
     if(speicherberreich == ((caddr_t) -1))
      {
        fprintf(stderr, "%s: Fehler bei mmap mit der Datei %s\n"
                        ,strerror(errno), argv[n]);
        continue; /*Hoch zu while*/
      }
 /*Jetzt ist die Datei die mit fd geöffnet wurde im Arbeitsspeicher */
    close(fd);
    if(write(STDOUT_FILENO, speicherberreich, attr.st_size) != attr.st_size)
          fprintf(stderr, "Fehler bei write\n");
    }/*Ende while*/
  exit(0);
}

Wenn Sie Memory Mapped I/O verwenden, sollten Sie immer Signalhandler für folgende zwei Signale einrichten ...

Wir wollen unser Programm von oben erweitern. Wir geben in der Kommandozeile eine Datei ein, die wir zum lesen öffnen und in den mapped-Speicherbereich, mit dem Schutz PROT_READ (nur lesbar), legen. Wir versuchen dennoch in diesen mapped-Speicherbereich zu schreiben. Dafür richten wir einen Signalhandler ein, der diesen Versuch abfängt und eine entsprechende Fehlermeldung auf dem Bildschirm ausgibt ...

#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

extern int errno;
static volatile sig_atomic_t sflag;
static sigset_t signal_neu, signal_alt, signal_leer;
void sigfunc1(int);
void sigfunc2(int);

void signale_mmap(void)
{
  if(signal(SIGSEGV, sigfunc1) == SIG_ERR)
   {
     fprintf(stderr, "Konnte signalhandler für SIGSEGV nicht einrichten\n");
     exit(0);
   }
  if(signal(SIGBUS, sigfunc2) == SIG_ERR)
   {
     fprintf(stderr, "Konnte signalhandler für SIGBUS nicht einrichten\n");
     exit(0);
   }

/*Wir entfernen alle Signale aus der Signalmenge*/
  sigemptyset(&signal_leer);
  sigemptyset(&signal_neu);
/*Wir fürgen die zwei Signal SIGSEGV und SIGBUS zur Signalmenge hinzu*/
  sigaddset(&signal_neu, SIGSEGV);
  sigaddset(&signal_neu, SIGBUS);
/*Jetzt setzen wir signal_neu und sichern die noch aktuelle*/
/* Signalmaske in signal_alt*/
  if(sigprocmask(SIG_BLOCK, &signal_neu, &signal_alt) < 0)
     exit(0);
}

void sigfunc1(int sig)
{
  printf("SIGSEGV: Versuch in unerlaubten Speicherbereich zu schreiben\n");
  exit(0);
}

void sigfunc2(int sig)
{
  printf("SIGBUS: Der Speicherbereich ist nicht mehr gültig\n");
  exit(0);
}

int main(int argc, char **argv)
{
  int fd, n=0;
  void *speicherberreich;
  struct stat attr;

  signale_mmap();

  while(argv[++n] != NULL)
    {
       if(( fd=open(argv[n], O_RDONLY)) < 0)
         {
          fprintf(stderr, "Konnte %s nicht öffnen!!!\n",argv[n]);
          continue; /*Wieder hoch zu while*/
         }
       if(fstat(fd, &attr) == -1)
         {
           fprintf(stderr,"Fehler bei fstat.......\n");
           continue; /*Wieder hoch zu while*/
         }

/*Achtung jetzt legen wir die Datei die wir mit fd speziviert */
/*haben in den Arbeitsspeicher*/
     speicherberreich=mmap(0, attr.st_size+5, PROT_READ, MAP_SHARED, fd, 0);
     if(speicherberreich == ((caddr_t) -1))
       {
         fprintf(stderr, "%s: Fehler bei mmap mit der Datei %s\n"
                          ,strerror(errno), argv[n]);
         continue; /*Hoch zu while*/
       }
      close(fd);
/*Jetzt versuchen wir mit Absicht in den mapped-Speicher */
/*zu schreiben trotzt PROT_READ*/
     strcat((char *)speicherberreich, "test");
   }/*Ende while*/
  exit(0);
}

Was Sie an diesem Beispiel etwas verwirren dürfte, ist die Tatsache, dass ich hier eine einfache Stringfunktion ...

strcat((char *)speicherberreich, "test");

...verwendet habe! Das ist einer der Vorteile, die man mit Memory Mapped hat. Man kann einfach mit Zeigern auf den mapped-Speicherbereich zugreifen. Man darf sich das Ganze als ein Array vorstellen im mapped-Speicherbereich. Zum besseren Verständnis noch ein Beispiel. Wir wollen mit mmap einen Kopie einer Datei erstellen (ohne write und read) ...

#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

#ifndef MAPFILE
/*für nicht BSD-Systeme*/
#define MAPFILE 0
#endif

extern int errno;
static volatile sig_atomic_t sflag;
static sigset_t signal_neu, signal_alt, signal_leer;
void sigfunc1(int);
void sigfunc2(int);

void signale_mmap(void)
{
  if(signal(SIGSEGV, sigfunc1) == SIG_ERR)
   {
     fprintf(stderr, "Konnte signalhandler für SIGSEGV nicht einrichten\n");
     exit(0);
    }
  if(signal(SIGBUS, sigfunc2) == SIG_ERR)
    {
      fprintf(stderr, "Konnte signalhandler für SIGBUS nicht einrichten\n");
      exit(0);
    }

/*Wir entfernen alle Signale aus der Signalmenge*/
  sigemptyset(&signal_leer);
  sigemptyset(&signal_neu);
/*Wir fürgen die zwei Signal SIGSEGV und SIGBUS zur Signalmenge hinzu*/
  sigaddset(&signal_neu, SIGSEGV);
  sigaddset(&signal_neu, SIGBUS);
/*Jetzt setzen wir signal_neu und sichern die noch aktuelle */
/*Signalmaske in signal_alt*/
  if(sigprocmask(SIG_BLOCK, &signal_neu, &signal_alt) < 0)
     exit(0);
}

void sigfunc1(int sig)
{
  printf("SIGSEGV: Versuch auf einen unerlaubten Speicherbereich zu schreiben\n");
  exit(0);
}

void sigfunc2(int sig)
{
  printf("SIGBUS: Der Speicherbereich ist nicht mehr gültig\n");
  exit(0);
}

int main(int argc, char **argv)
{
  int fd,fd1;
  void *speicherquelle, *speicherziel;
  struct stat attr;

  signale_mmap();

  if((fd1=open(argv[1],O_RDONLY)) < 0)
    {
      fprintf(stderr,"%s : Konnte %s nicht öffnen\n",strerror(errno),argv[2]);
      exit(0);
    }

  if(( fd=open(argv[2], O_RDWR|O_CREAT,0777)) < 0)
    {
      fprintf(stderr,"%s : Konnte %s nicht erstellen\n",strerror(errno),argv[2]);
      exit(0);
    }

  if(fstat(fd1, &attr) == -1)
    {
      fprintf(stderr,"Fehler bei fstat.......\n");
      exit(0);
    }

/*Größe der Zieldatei festlegen*/
  lseek(fd, attr.st_size-1, SEEK_SET);
  write(fd, "", 1);

  speicherquelle=mmap(0, attr.st_size, PROT_READ, MAPFILE|MAP_SHARED, fd1, 0);
  speicherziel=mmap(0, attr.st_size, PROT_READ|PROT_WRITE, MAPFILE|MAP_SHARED, fd, 0);
  if((speicherquelle == ((caddr_t) -1)) || (speicherziel == ((caddr_t)-1)) )
    {
      fprintf(stderr, "%s: Fehler bei mmap ...........\n",strerror(errno));
      exit(0);
    }
/*Datei die mit fd und fd1 geöffnet wurden sind jetzt im Arbeitsspeicher*/
  close(fd);
  close(fd1);

  memcpy(/*(char *)*/speicherziel, /*(char *)*/speicherquelle, attr.st_size);

  exit(0);
}

Das Programm erstellt eine Kopie von der Datei die Sie mit argv[1] angeben, mit dem Namen vom Argument argv[2]. Ich habe in diesem Beispiel noch die Konstante MAPFILE definiert, da diese für mmap erforderlich ist und auf manchen Systemen nicht vorhanden ist. Sie können ja mal einen Zeitvergleich zwischen dem Programm oben, mit Memory Mapped und mit den Funktionen read/write zum Kopieren von Dateien schreiben. Memory Mapped dürft fast doppelt (!) so schnell sein. Je nach System.

9.2. Memory Mapped I/O - Synchronisieren           zurück Ein Kapitel tiefer zum Inhaltsverzeichnis

Mit der Funktion ...

#include <sys/types.h>
#include <sys/mman.h>

int munmap(caddr_t adresse, size_t laenge);

... heben Sie das Memory Mapped I/O für den spezifisierten Bereich adresse mit laenge Bytes auf. Alle Daten die Sie in diesem mapped-Speicherbereich verändert haben, werden nicht automatisch in die dazugehörige Datei geschrieben. Damit die Daten der zugeordneten Datei im mapped-Speicherbereich aktualisiert werden, benötigen Sie die Funktion ...

#include <sys/types.h>
#include <sys/mman.h>

int msync(caddr_t adresse, size_t laenge, int flags);

Die Funktion gibt bei Erfolg 0 und bei Fehler -1 zurück. Folgende Konstanten können dabei für flags angegeben werden ...

Die Funktion msync() benötigen Sie, wenn Änderungen im mapped-Speicherbereich sofort in die dazugehörenden Datei (auf der Festplatte) geschrieben werden sollen.

Wir wollen nun ein Programm schreiben, womit Sie Daten von der Standardeingabe lesen und die Eingabe in eine Datei schreiben, die Sie als erstes Argument in der Kommandozeile angeben. Die Datei auf die geschrieben wird, bilden wir in unserem Hauptspeicher ab ...

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <time.h>
#include <signal.h>

#include <string.h>

#define USAGE "usage: %s \n"

#ifndef MAPFILE
#define MAPFILE 0 /*für nicht BSD-Systeme*/
#endif

#define MAPSIZE 64

extern int errno;
static volatile sig_atomic_t sflag;
static sigset_t signal_neu, signal_alt, signal_leer;
char *mapped;
void sigfunc1(int);
void sigfunc2(int);


void signale_mmap(void)
{
  if(signal(SIGSEGV, sigfunc1) == SIG_ERR)
   {
     fprintf(stderr, "Konnte signalhandler für SIGSEGV nicht einrichten\n");
     exit(0);
   }
  if(signal(SIGBUS, sigfunc2) == SIG_ERR)
   {
     fprintf(stderr, "Konnte signalhandler für SIGBUS nicht einrichten\n");
     exit(0);
   }

/*Wir entfernen alle Signale aus der Signalmenge*/
  sigemptyset(&signal_leer);
  sigemptyset(&signal_neu);
/*Wir fürgen die zwei Signal SIGSEGV und SIGBUS zur Signalmenge hinzu*/
  sigaddset(&signal_neu, SIGSEGV);
  sigaddset(&signal_neu, SIGBUS);

/*Jetzt setzen wir signal_neu und sichern die noch */
/*aktuelle Signalmaske in signal_alt*/
if(sigprocmask(SIG_BLOCK, &signal_neu, &signal_alt) < 0)
exit(0);
}

void sigfunc1(int sig)
{
  printf("SIGSEGV: Versuch auf einen unerlaubten Speicherbereich zu schreiben\n");
  exit(0);
}

void sigfunc2(int sig)
{
  printf("SIGBUS: Der Speicherbereich ist nicht mehr gültig\n");
  exit(0);
}


int main(int argc, char *argv[])
{
  int fd;
  int n,lines=1;
  char string[MAPSIZE];

  if(argc!=2)
   {
     fprintf(stderr,USAGE,*argv);
     exit(1);
   }

  signale_mmap() ;

  if((fd=open(argv[1], O_RDWR|O_CREAT|O_TRUNC, 0777)) < 0)
   {
     perror("open");
     exit(1);
   }

/*Größe der Datei festlegen*/
  lseek(fd, 2*MAPSIZE-1, SEEK_SET);
  write(fd,"",1);

/*mapped-Speicherberreich festlegen*/
  if((mapped=(char*)mmap(0,2*MAPSIZE,PROT_WRITE|PROT_READ,MAPFILE|MAP_SHARED,fd,0))
                                                                    ==(caddr_t )-1)
  {
    perror("mmap: ");
    exit(1);
  }

  close(fd);

  while(1)
    {
      n=read(STDIN_FILENO,string,2*MAPSIZE);
      mapped+=n; /*aktuelle Position im Speicherbereich*/
      memcpy(mapped, string, n);
      msync(mapped,2*MAPSIZE,MS_SYNC);
    }
  exit(0);
}

Sie können ja mal das Programm ohne der Funktion msync() testen. Wenn Sie sich dann die Datei ansehen, in der Sie geschrieben haben, werden Sie merken, dass das Programm auch ohne msync() funktioniert. Dies liegt am Flag MAP_SHARED. Mit diesem Flag wird garantiert, dass automatisch Aktualisiert wird.

Wollen wir uns nun ein Programm ansehen, womit wir Synchronisiert mit dem Elternprozess in den mapped-Speicherbereich schreiben, um gleich darauf mit dem Kindprozess daraus zu lesen, was unser Elternprozess in den Hauptspeicher geschrieben hat ...

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <time.h>
#include <signal.h>
#include <string.h>

#define USAGE "usage: %s \n"

#define MAPSIZE 1024

extern int errno;
static volatile sig_atomic_t sflag;
static sigset_t signal_neu, signal_alt, signal_leer;
void sigfunc1(int);
void sigfunc2(int);

void signale_mmap(void)
{
  if(signal(SIGSEGV, sigfunc1) == SIG_ERR)
   {
     fprintf(stderr, "Konnte signalhandler für SIGSEGV nicht einrichten\n");
     exit(0);
   }
  if(signal(SIGBUS, sigfunc2) == SIG_ERR)
   {
    fprintf(stderr, "Konnte signalhandler für SIGBUS nicht einrichten\n");
    exit(0);
   }

/*Wir entfernen alle Signale aus der Signalmenge*/
  sigemptyset(&signal_leer);
  sigemptyset(&signal_neu);
/*Wir fügen die zwei Signal SIGSEGV und SIGBUS zur Signalmenge hinzu*/
  sigaddset(&signal_neu, SIGSEGV);
  sigaddset(&signal_neu, SIGBUS);
/*Jetzt setzen wir signal_neu und sichern die noch */
/*aktuelle Signalmaske in signal_alt*/
  if(sigprocmask(SIG_BLOCK, &signal_neu, &signal_alt) < 0)
     exit(0);
}

void sigfunc1(int sig)
{
  printf("SIGSEGV: Versuch auf einen unerlaubten Speicherbereich zu schreiben\n");
  exit(0);
}

void sigfunc2(int sig)
{
  printf("SIGBUS: Der Speicherbereich ist nicht mehr gültig\n");
  exit(0);
}

int main(int argc, char *argv[])
{
  int fd;
  pid_t pid;
  time_t now;
  char buf[MAPSIZE];
  char *mapped, *omap;
  char *timestring;

  if(argc!=2)
   {
     fprintf(stderr,USAGE,*argv);
     exit(1);
   }

  signale_mmap() ;

  if((fd=open(argv[1], O_RDWR|O_CREAT|O_TRUNC, 0777)) < 0)
    {
      perror("open");
      exit(1);
    }

  pid=fork();
  if(pid==-1)
   {
     perror("fork");
     exit(1);
   }
  if(pid==0)
   { /* father, writing*/
  if((mapped=(char*)mmap(NULL,2*MAPSIZE,PROT_WRITE,MAP_SHARED,fd,0)) ==(void*)-1)
     {
       perror("mmap: ");
       exit(1);
     }
  omap=mapped;
  lseek(fd, MAPSIZE, SEEK_CUR);
  write(fd,"hallo",6);
  while(1)
   {
     now=time(NULL);
     timestring=ctime(&now);
     strcpy(mapped, timestring);
     mapped+=strlen(timestring);
     msync(omap,2*MAPSIZE,MS_SYNC);
     puts("Eltern schreiben in Hauptspeicher\n");
     sleep(1);
    }
  exit(0);
   }
  else
    { /* child, reading */
  if((mapped=(char*)mmap(NULL,2*MAPSIZE,PROT_READ,MAP_SHARED,fd,0))==(void*)-1)
    {
      perror("mmap: ");
      exit(1);
    }
  while(1)
    {
      sleep(1);
      strcpy(buf, mapped);
      mapped+=strlen(buf);
      printf("Kind ließt aus dem Hauptspeicher : %s\n",buf);
    }
    exit(0);
   }
 exit(0); /*Hier kommen wir niemals an*/
}

Weiter mit 9.3. Memory Mapped I/O - Weiter Funktionen