ein Kapitel zurück                                           ein Kapitel weiter

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 wir die Daten lesen und schreiben ohne read und write. Genauer kann auch man sagen das in unserem Arbeitsspeicher ein Speicherabbild von der Datei auf der Festplatte gemacht wurde. Hier nun einige Vorteile dieser Methode.....

  • Schnellere Dateizugriffe da kein Zugriff auf externe Medien notwendig
  • Dynamisches Laden. Sie können ein Programm oder Programmteile in den Arbeitsspeicher legen und es anschließend dynamische Laden.
  • Einfacherer Dateizugriff mit Hilfe von Zeigern
  • Shared Memory für verwandte Prozesse (dazu kommen wir später)
  • Kommunikation zwischen Prozessen nicht gleichzeitig ablaufen

Mit folgender Funktion können sie 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 simpel da. mmap gibt die Anfangsadresse des zugeordneten Speicherbereiches zurück oder bei Fehler -1.

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

Hier das ganze noch Bildlich dargestellt.....


geliehen von der QNX-Dokumentation


Nun benötigen wir noch ein paar Konstanten die sie an protect und flag übergeben können. Zuerst die Angaben für protect......

  • PROT_READ (Bereich darf gelesen werden)
  • PROT_WRITE (Bereich darf beschrieben werden)
  • PROT_NONE (Bereich kann überhaupt nicht Zugegriffen werden)
  • PROT_EXEC (Bereich darf ausgeführt werden)

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

  • MAP_SHARED - Schreiben auf den mapped-Speicherbereich wird auf der Originaldatei ausgeführt. Andere Prozesse die diesen Speicherbereich ebenfalls nutzen nehmen eine durch Schreiben bedingte Veränderung zu Kenntnis.
  • MAP_PRIVATE - Schreiben auf den mapped-Speicherbereich erfolgt auf einen Kopie. Daher kriegen andere Prozesse davon nichts mit.

Folgende Operationen können zu einer der beiden Optionen (MAP_SHARED und MAP_PRIVATE) hinzugefügt werden....

  • MAP_ANONYMUS - Einen anonymen mapped-Speicherbereich kann man Einrichten wenn man für einen Prozess neuen Speicher allokieren will. Anonyme Prozesse können nicht von anderen Prozessen genutzt werden.

Für Linux gibt es noch folgende Optionen.......

  • MAP_LOCKED - Damit können sie einen ganzen Speicherbereich sperren damit dieser nicht Ausgelagert (SWAP) werden kann. Nur als root durchführbar
  • MAP_DENYWRITE - Wenn sie einen mapped-Speicherbereich eingerichtet haben mit protec==PROT_EXEC (Bereich darf ausgeführt werden) aber nicht wollen das in diesem Bereich geschrieben wird (write) so müssen sie dieses Flag setzen.
  • MAP_GROWSDOWN - Mit dieser Option verhindern sie das das Signal SIGSEGV (Zugriff auf unerlaubten Speicherbereich) generiert wird. Dafür wird automatisch neuer anonymer mapped-Speicherbereich allokiert. Der Prozess wird nicht abgebrochen.
  • MAP_EXECUTABLE - Damit machen sie den mapped-Speicherbereich ausführbar.

Kommen wir nun zu einem Beispiel wie sie mmap einsetzen können. Wir wollen alle als Argument übergebene Dateien die sie angeben in den mapped-Speicher abbilden und diese mittels write auf dem Bildschirm ausgeben........

/*Download:map1.c*/
#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 Signalhandler für folgende 2 Signale einrichten.......

  • SIGSEGV - Signal wird generiert wenn auf Unerlaubten Speicherbereich zugegriffen wird. Beispielsweise Sie wollen in einen mapped-Speicherbereich schreiben den sie mit PROT_READ eingerichtet haben.
  • SIGBUS - Signal wird generiert wenn versucht wurde auf einen nicht mehr gültigen mapped-Speicherbereich zuzugreifen da dieser von einem anderen Prozess verkleinert wurde.

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

/*Download:map2.c*/
#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 das ich hier einen Stringfunktion......

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

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

/*Download:map3.c*/
#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 ein Kopie von argv[1] mit dem Namen von argv[2]. Ich habe in diesem Beispiel noch die Konstante MAPFILE definiert da diese für mmap erforderlich ist aber auf manchen Systemen nicht vorhanden ist. Sie können ja mal wenn sie wollen einen Zeitvergleich zwischen dem Programm oben mit Memory Mapped und mit read/write zum Kopieren von Dateien schreiben. Memory Mapped dürft fast doppelt (!) so schnell sein. Je nach System.

ein Kapitel zurück          nach oben           ein Kapitel weiter


© 2001,2002 Jürgen Wolf