Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Linux-UNIX-Programmierung von Jürgen Wolf
Das umfassende Handbuch – 2., aktualisierte und erweiterte Auflage 2006
Buch: Linux-UNIX-Programmierung

Linux-UNIX-Programmierung
1216 S., mit CD, 49,90 Euro
Rheinwerk Computing
ISBN 3-89842-749-8
gp Kapitel A Funktionsreferenz
  gp B.1 ANSI C
  gp B.2 ANSI C99
  gp B.3 Elementare E/A-Funktionen
  gp B.4 Fortgeschrittene Ein-/Ausgabe-Funktionen
  gp B.5 Verzeichnisse
  gp B.6 Attribute von Dateien und Verzeichnissen
  gp B.7 Links
  gp B.8 Prozess und Prozessverwaltungsfunktionen
  gp B.9 Signale – Das neue Signalkonzept
  gp B.10 Interprozesskommunikationen
  gp B.11 Sys-V-Interprozesskommnunikationen
  gp B.12 Threadprogrammierung
  gp B.13 Netzwerkprogrammierung
  gp B.14 MySQL C-API
  gp B.15 PostgreSQL C-API
  gp B.16 Weitere Funktionsreferenzen auf der Buch-CD


Rheinwerk Computing

B.4 Fortgeschrittene Ein-/Ausgabe-Funktionen  downtop


Rheinwerk Computing

B.4.1 Multiplexing Ein-/Ausgabe – select(downtop


Konform ANSI C POSIX.1 XPG SVR4 BSD
  nein nein nein ja ja

Das Lesen und Schreiben von Filedeskriptoren erfolgt meistens über ein einfaches read() und write() wie im folgenden Code-Auschnitt demonstriert:

while(  (n = read(fd_r, buf, BUFSIZ) ) > 0 )
     if( write( fd_w, buf, n ) != n)
         /* Fehler bei write ... */

Sobald allerdings aus mehr als einem Filedeskriptor auf einmal gelesen werden soll, lässt sich diese Form nicht mehr sinnvoll einsetzen. Kommen hier bspw. Daten an einem Filedeskriptor an – aber es wird gerade von einem anderen gelesen, bei dem keine Daten anstehen – blockiert read(). Somit muss der Filedeskriptor, an dem Daten angekommen sind, so lange warten, bis bei dem anderen Filedeskriptor Daten anstehen. Das Blockieren ist die Standardeinstellung von read().

Um dieses Problem zu umgehen, steht Ihnen in SVR4 und BSD (und somit auch unter Linux) eine Multiplexing Ein-/Ausgabe mit der Funktion select() zur Verfügung. Das Prinzip ist einfach: Man erstellt eine Liste (Menge) von Filedeskriptoren, die für den Prozess interessant sind und ruft die Funktion select() auf. Diese Funktion kehrt erst dann zurück, wenn einer der Filedeskriptoren in der Liste (Menge) etwas zu tun hat (spricht die Ein-/Ausgabe bereit ist). Als Rückgabewert erhält man von select() den Filedeskriptor, der für die Ein-/Ausgabe bereit ist.

int select(
  int maxfd,               /* Nr. des größten fd aus der Menge */
  fd_set *fd_read,         /* Deskriptor-Menge zum Lesen       */
  fd_set *fd_write,        /* Deskriptor-Menge zum Schreiben   */
  fd_set *fd_except,       /* Deskriptor-Menge für Exceptions  */
  struct timeval *timeout  /* wie lange soll select() warten   */
  );

Um auf die Deskriptoren-Menge (den einzelnen Bits) verschiedene Operationen auszufühen, stehen Ihnen folgende Makros zu Verfügung.

FD_ZERO(fd_set *fdptr);          /* Alle Bits in fdptr löschen */
FD_SET(int fd, fd_set *fdptr);   /* Bit fd in fdptr setzen     */  
FD_CLR(int fd, fd_set *fdptr);   /* Bit fd in fdptr löschen    */
FD_ISSET(int fd, fd_set *fdptr); /* Testen, ob Bit fd gesetzt  */

Wie lange auf select() gewartet werden soll, geben Sie mit dem letzten Parameter an. Ein timeout von NULL bedeutet ewiges Warten. Werden den beiden Strukturvariablen timeout->tv_sec und timeout->tv_usec jeweils der Wert 0 übergeben, findet kein Warten statt und select() kehrt sofort wieder zurück. In der Praxis wird damit, in einer Schleife, das so genannte Polling (dauerndes Abfragen ohne blockieren) realisiert. Geben Sie hingegen für die Strukturvariablen timeout->tv_sec und timeout->tv_usec einen positiven Wert ungleich 0 an, wird entsprechend der Angabe(n) eine gewisse Zeit gewartet.

Der Rückgabewert von select() ist entweder die Anzahl der Filedeskriptoren, die bereit sind, oder 0, wenn die Zeitschaltuhr abgelaufen ist. Ansonsten wird bei einem Fehler –1 zurückgegeben.


Rheinwerk Computing

B.4.2 Memory Mapped Ein-/Ausgabe  toptop


Konform ANSI C POSIX.1 XPG SVR4 BSD
  nein nein nein ja ja

Neben den Standard-E/A und elementaren E/A-Funktionen stellt Ihnen SVR4 und BSD (somit auch Linux) das Memory Mapped zur Verfügung. Dabei handelt es sich um eine fortgeschrittene Methode, welche Ihnen das Abbilden von Dateien in dem Hauptspeicher ermöglicht. Der Hauptvorteil dieser Methode ist, dass hierbei weniger Kernel-Aktionen wie bei den Systemaufrufen gemacht werden müssen – es muss viel weniger vom User- in den Kernel-Level gewechselt werden. Und das wirkt sich (richtig eingesetzt) positiv auf die Performance aus.

Natürlich sollte man mit Memory Mapped E/A im Auge behalten, dass man hierbei wirklich den Hauptspeicher verwendet (RAM) – was auch bedeutet, dass man diesen mit Memory Mapped falsch eingesetzt, komplett zumüllen kann. Es empfiehlt sich daher, eine sinnvolle Größe des Speicherbereichs zu verwenden. Bspw. beim Kopieren in ein oder zwei Megabyte-Schritten läuft Memory Mapped E/A erheblich schneller ab als bspw. read() und write().

Mit Memory Mapped hat man quasi einen kompletten Dateiinhalt im Hauptspeicher und kann diesen bequem mittels Zeiger modifizieren – ähnlich wie mit den Funktionen fseek()/lseek().

Das Einrichten eines solchen Memory Mapped Speicherbereichs wird mit der Funktion mmap() erledigt.

#include <sys/types.h>
#include <sys/mman.h>
void *mmap( void *addr, size_t len, int prot,
            int flags, int fd, off_t offset);

Mit addr wird der Anfangsbereich (Adresse) des gemappten Speicherbereichs festgelegt. Gewöhnlich wird hierbei 0 angegeben, was somit bedeutet, dass sich dass System für uns nach einem geeigneten Platz umschauen soll. Als Rückgabewert der Funktion mmap() erhalten Sie dann die Anfangsadresse dieses Bereiches. Mit len geben Sie die Anzahl von Bytes an, die der mapped-Speicherbereich umfassen soll (bspw. die komplette Größe (Vorsicht bei zu großen Dateien) einer Datei oder nur Stücke davon).

Mit prot legen Sie die Schutzart des mapped-Speicherbereichs fest. Hierzu können Sie einen oder mehrere (mit dem bitweisen ODER verknüpft) der folgenden Angaben verwenden:


Tabelle B.29    Schutzarten für einen Memory Mapped-Bereich

Konstante Bedeutung
PROT_READ Bereich darf gelesen werden.
PROT_WRITE Bereich darf beschrieben werden.
PROT_EXEC Bereich darf ausgeführt werden.
PROT_NONE Auf diesen Bereich darf nicht zugegriffen werden (nicht BSD).

Natürlich muss auch hierbei die Schutzart mit den entsprechenden Optionen übereinstimmten, die beim Öffnen mit open() verwendet wurde – sprich, wurde die Datei zum Lesen geöffnet, kann kein Schreibschutz verwendet werden!

Mit den flags können Sie zusätzlich Anforderungen an den mapped-Speicherbereich angeben. Mögliche flags wären hierfür (wobei hier die Flags MAP_SHARED oder MAP_PRIVATE immer gesetzt sein müssen):

MAP_FIXED – Die Anfangsadresse, die mmap() zurückgibt, muss mit der von addr übereinstimmen. Ohne diesem Flag gilt addr nur als Vorschlag und darf vom System auch ignoriert werden. MAP_SHARED – Jede Modifikation im mapped-Bereich wirkt sich auf die Orginal-Datei aus. Ohne diesem Flag würde erst an einer Kopie gearbeitet. Natürlich bedeutet dies auch, dass alle anderen Prozesse, die denselben mapped-Speicherbereich verwenden, ebenfalls diese Veränderung erkennen. MAP_PRIVATE – Das Gegenteil von MAP_SHARED. Alle Arbeiten finden auf einer Kopie statt und die anderen Prozesse erfahren nichts davon. MAP_ANONYMOUS – Damit kann ein anonymer Speicherbereich angelegt werden, der für keinen anderen Prozess sichtbar ist. Wenn Sie schon mal STRACE verwendet haben, wird Ihnen sicherlich aufgefallen sein, dass beim Reservieren eines Speichers mittels malloc() die Funktion mmap() und zum Freigeben mittels free(), munmap() verwendet wurde. Daraus können Sie schließen, dass malloc() mithilfe von mmap() realisiert wurde. Sie können damit auch Ihr eigenes malloc() implementieren. Das Argument fd wird mit dem Setzen dieses Flags ignoriert. MAP_DENYWRITE – Haben Sie bspw. beim Speicherschutz PROT_EXEC verwendet, so dass dieser Speicherbereich ausgeführt werden darf, können Sie mit diesem Flag dafür sorgen, dass dies weiterhin möglich ist – aber ein Schreiben von außerhalb in den mapped-Bereich wird untersagt (nur Linux). MAP_GROWSDOWN – Findet ein Zugriff außerhalb eines gemappten Speicherbereichs statt, wird durch das Setzen von diesem Flag nicht das Signal SIGSEGV gesendet und der Prozess somit auch nicht beendet. Stattdessen wird ein neuer anonymer Bereich alloziiert, worin der entsprechende Prozess seine Ausführung fortsetzt. Zwangsläufig wächst mit dieser Methode allerdings auch der Stack an! (nur Linux) MAP_LOCKED – Der mapped-Speicherbereich kann nicht ausgelagert werden. Dies wird unbedingt für Echtzeitanwendungen benötigt. Dieses Flag ist nur dem Superuser vorbehalten.

Mit dem Argument fd der Funktion mmap() geben Sie den Filedeskriptor der Datei an, die im mapped-Speicherbereich abgebildet werden soll. Logischerweise haben Sie diese Datei bis dato schon geöffnet! Mit offset geben Sie das Offset des Datenbereichs an. Das Offset ist der Bereich, der vom Anfang der Datei ausgelassen werden soll. Ist MAP_FIXED angegeben, müssen die Werte für offset und addr das Vielfache einer Page-Größe sein. Die Page-Größe können Sie mit der Funktion getpagesize(), welche in der Headerdatei <unistd. h.> definiert ist, ermitteln.


Hinweis   Der mapped-Bereich wird beim Erzeugen eines neuen Prozesses mittels fork() weitervererbt, da dieser Bereich teil vom Adressraum des Prozesses ist.


Freigeben können Sie einen Speicherbereich wieder mit der Funktion munmap():

#include <sys/types.h>
#include <sys/mman.h>
int munmap(void *addr, size_t len);

Damit heben Sie den von mmap() zurückgegebenen Speicherbereich addr mit len Bytes wieder auf.

Jetzt könnten Sie bspw. mit der Funktion memcpy() einen zum Lesen und einen zum Schreiben angelegten mapped-Speicherbereich kopieren. Damit eine Veränderung im mapped-Speicherbereich jetzt auch in die dazugehörende Datei übertragen wird, benötigten Sie die Funktion msync().

#include <sys/types.h>
#include <sys/mman.h>
int msync(void *addr, size_t len, int flags);

Die ersten beiden Argumente legen wieder den Speicherbereich fest, auf den die Aktion ausgeführt werden soll (Sie können also auch nur Teile synchronisieren). Die Art der Synchronisation legen Sie mit flags fest. Folgende Angaben, welche sich auch mit dem bitweisen ODER verknüpfen lassen, sind hierfür möglich:

gp  MS_ASYNC – Geänderter mapped-Bereich soll sobald wie möglich synchronisiert werden.
gp  MS_SYNC – Geänderter mapped-Bereich soll sofort synchronisiert werden (somit schließen sich MS_ASYNC und MS_SYNC gegenseitig aus).
gp  MS_INVALIDATE – Der Kernel soll selbst entscheiden, wann und ob überhaupt synchronisiert wird – dies eignet sich für Änderungen, die nicht zwingend in die Datei übernommen werden müssen.

Hierzu ein Beispiel, welches Ihnen mit der selbst geschriebenen write3()-Funktion zeigen soll, wie Sie das Kopieren einer ganzen Datei mithilfe von zwei eingerichteten mapped-Speicherbereichen realisieren können:

/* cpy_file_mmap.c */
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <unistd. h.>
#include <stdlib.h>
#include <string.h>
static void write3 (int fd_quelle, int fd_ziel) {
   struct stat statptr;
   char *quelle, *ziel;
   /* Größe der Quelldatei ermitteln */
   if (fstat (fd_quelle, &statptr) < 0) {
      perror ("stat");
      exit (EXIT_FAILURE);
   }
   /* Dateigröße für Ziel */
   if (lseek (fd_ziel, statptr.st_size - 1, SEEK_SET) == -1) {
      perror ("lseek");
      exit (EXIT_FAILURE);
   }
    /* Am Ende der neuen Datei(größe) ein Byte schreiben */
   write (fd_ziel, " ", 1);
    /* mapped-Bereich für Quelldatei einrichten  */
   if ((quelle =
        mmap (0, statptr.st_size, PROT_READ, MAP_SHARED,
              fd_quelle, 0)) == -1) {
      perror ("mmap");
      exit (EXIT_FAILURE);
   }
    /* mapped-Bereich für Zieldatei einrichten */
   if ((ziel =
        mmap (0, statptr.st_size, PROT_READ | PROT_WRITE,
              MAP_SHARED, fd_ziel, 0)) == -1) {
      perror ("mmap");
      exit (EXIT_FAILURE);
   }
   /* Daten vom Hauptspeicherbereich quelle in den */
   /* Hauptspeicherbereich ziel kopieren           */
   memcpy (ziel, quelle, statptr.st_size);
   /* Denn neu kopierten mapped-Bereich auch für   */
   /* die Datei aktuallisieren                     */
   if ((msync (ziel, statptr.st_size, MS_ASYNC)) == -1) {
      perror ("msync");
      exit (EXIT_FAILURE);
   }
    /* mapped-Bereich wieder freigeben */
   munmap (quelle, statptr.st_size);
   munmap (ziel, statptr.st_size);
}
int main (int argc, char **argv) {
   int fd_r, fd_w;      /* Filedeskriptoren */
   /* Zugriffsrechte für die neue Datei: -rw-rw-r-- */
   mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
   char *read_file, *write_file;
   /* Alle Zugriffsrechte der Dateikreierungsmaske erlauben */
   umask (0);
   if (argc < 3) {
      fprintf (stderr, "Usage: %s quelle ziel\n", argv[0]);
      return EXIT_FAILURE;
   }
   read_file = argv[1];
   write_file = argv[2];
   fd_r = open (read_file, O_RDONLY);
   fd_w = open (write_file, O_RDWR | O_EXCL | O_CREAT, mode);
   if ((fd_r == -1) || (fd_w == -1)) {
      perror ("Fehler bei open : ");
      return EXIT_FAILURE;
   }    
   write3 (fd_r, fd_w);    
   close(fd_r);
   close(fd_w);
   return EXIT_SUCCESS;
}

Neben den eben gezeigten Funktionen sind dem Superuser auch mapped-Funktionen an die Hand gegeben, womit das Auslagern von Speicherbereichen, bei Ermangelung des Hauptspeichers, in einen so genannten Swap-Bereich auf die Festplatte verhindert werden kann. Damit können Sie bis max. RLIMIT_MEMLOCK Bytes (siehe Ressourcen-Limits) sperren. Folgende Funktionen sind dabei für das Sperren und wieder Aufheben von Speicherbereichen vorhanden.

#include <sys/mman.h>
int mlock(void *addr, size_t len);
int memlockall(int flags);
int munlock(void *addr, size_t len);
int munlockall(void);

Mit mlock() können Sie einen Speicherbereich der Adresse addr mit len Bytes Größe sperren. Wobei immer nur ganze Pages (Speicherseiten) gesperrt werden können. Bei Dateien unter der Page-Größe wird automatisch die komplette Datei gesperrt.

Mit mlockall() wird der gesamte Adressraum des Prozesses gesperrt. Als Flags können Sie zusätzlich MCL_CURRENT angeben, womit alle Seiten (Pages) gesperrt werden, die sich im Augenblick im Hauptspeicher befinden. Mit MCL_FUTURE werden alle Pages gesperrt, die in Zukunft noch zum Adressraum hinzugefügt werden. Beide Flags mit dem bitweisen ODER verknüpft garantieren Ihnen, dass die komplette Datei (auch bei Größenänderung) sich immer im Hauptspeicher befindet und niemals ausgelagert wird. Mit den beiden Funktionen munlockall() und munlock() heben Sie die jeweils mit mlockall() und mlock() gesetzten Sperren wieder auf.

 << zurück
  
  Zum Rheinwerk-Shop
Neuauflage: Linux-UNIX-Programmierung
Neuauflage:
Linux-UNIX-
Programmierung

bestellen
 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: Linux-Server






 Linux-Server


Zum Rheinwerk-Shop: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Rheinwerk-Shop: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Rheinwerk-Shop: Shell-Programmierung






 Shell-
 Programmierung


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
Info





Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern