Verwendung von Shared Memory unter Linux in C.


117

Ich habe ein Problem mit einem meiner Projekte.

Ich habe versucht, ein gut dokumentiertes Beispiel für die Verwendung von Shared Memory zu finden, fork()aber ohne Erfolg.

Grundsätzlich ist das Szenario ist , dass , wenn der Benutzer das Programm startet, muss ich im gemeinsam genutzten Speicher zwei Werte speichern: current_path die einen ist char * und ein datei_name die auch char * .

In Abhängigkeit von den Befehlsargumenten, wird ein neuer Prozess beginnt mit fork()und dieser Prozess lesen muss und ändern Sie die current_path Variable im gemeinsam genutzten Speicher gespeichert , während die datei_name Variable nur gelesen wird.

Gibt es ein gutes Tutorial zum gemeinsamen Speicher mit Beispielcode (falls möglich), zu dem Sie mich weiterleiten können?


1
Sie können auch Threads anstelle von Prozessen verwenden. Dann wird der gesamte Speicher ohne weitere Tricks geteilt.
Elomage

In den folgenden Antworten werden sowohl der System V IPC-Mechanismus shmget()et al. und auch der reine mmap()Ansatz mit MAP_ANON(aka MAP_ANONYMOUS) - obwohl MAP_ANONnicht von POSIX definiert. Es gibt auch POSIX shm_open()und shm_close()zum Verwalten von gemeinsam genutzten Speicherobjekten. [… Fortsetzung…]
Jonathan Leffler

[… Fortsetzung…] Diese haben den gleichen Vorteil wie der gemeinsam genutzte IPC-Speicher von System V: Das gemeinsam genutzte Speicherobjekt kann über die Lebensdauer des Prozesses, der es erstellt, bestehen bleiben (bis einige Prozesse ausgeführt werden shm_unlink()), wohingegen Mechanismen, mmap()die eine Datei verwenden , eine Datei erfordern und MAP_SHAREDbestehen bleiben die Daten (und MAP_ANONschließt die Persistenz aus). Es gibt ein vollständiges Beispiel im Abschnitt "Begründung" der Spezifikation von shm_open().
Jonathan Leffler

Antworten:


164

Es gibt zwei Ansätze: shmgetund mmap. Ich werde darüber sprechen mmap, da es moderner und flexibler ist, aber Sie können sich man shmget( oder dieses Tutorial ) ansehen, wenn Sie lieber die Tools im alten Stil verwenden möchten.

Die mmap()Funktion kann verwendet werden, um Speicherpuffer mit hochgradig anpassbaren Parametern zuzuweisen, um den Zugriff und die Berechtigungen zu steuern, und sie bei Bedarf mit Dateisystemspeicher zu sichern.

Die folgende Funktion erstellt einen speicherinternen Puffer, den ein Prozess für seine untergeordneten Elemente freigeben kann:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>

void* create_shared_memory(size_t size) {
  // Our memory buffer will be readable and writable:
  int protection = PROT_READ | PROT_WRITE;

  // The buffer will be shared (meaning other processes can access it), but
  // anonymous (meaning third-party processes cannot obtain an address for it),
  // so only this process and its children will be able to use it:
  int visibility = MAP_SHARED | MAP_ANONYMOUS;

  // The remaining parameters to `mmap()` are not important for this use case,
  // but the manpage for `mmap` explains their purpose.
  return mmap(NULL, size, protection, visibility, -1, 0);
}

Das folgende Beispielprogramm verwendet die oben definierte Funktion, um einen Puffer zuzuweisen. Der übergeordnete Prozess schreibt eine Nachricht, gabelt und wartet dann darauf, dass sein untergeordnetes Element den Puffer ändert. Beide Prozesse können den gemeinsam genutzten Speicher lesen und schreiben.

#include <string.h>
#include <unistd.h>

int main() {
  char parent_message[] = "hello";  // parent process will write this message
  char child_message[] = "goodbye"; // child process will then write this one

  void* shmem = create_shared_memory(128);

  memcpy(shmem, parent_message, sizeof(parent_message));

  int pid = fork();

  if (pid == 0) {
    printf("Child read: %s\n", shmem);
    memcpy(shmem, child_message, sizeof(child_message));
    printf("Child wrote: %s\n", shmem);

  } else {
    printf("Parent read: %s\n", shmem);
    sleep(1);
    printf("After 1s, parent read: %s\n", shmem);
  }
}

51
Aus diesem Grund ist Linux für unerfahrene Entwickler so frustrierend. Die Manpage erklärt nicht, wie man es tatsächlich benutzt, und es gibt keinen Beispielcode. :(
bleepzter

46
Haha, ich weiß, was du meinst, aber eigentlich, weil wir es nicht gewohnt sind, Manpages zu lesen. Als ich sie lesen lernte und mich an sie gewöhnte, wurden sie noch nützlicher als miese Tutorials mit bestimmten Demonstrationen. Ich erinnere mich, dass ich in meinem Betriebssystemkurs eine 10/10 erhalten habe, bei der während der Prüfung nur Manpages als Referenz verwendet wurden.
Slezica

18
shmgetist eine wirklich altmodische und einige würden sagen veraltete Art und Weise, Shared Memory zu machen ... Besser zu verwenden mmapundshm_open , einfache Dateien oder einfach MAP_ANONYMOUS.
R .. GitHub STOP HELPING ICE

4
@Mark @R Ihr habt Recht, darauf werde ich in der Antwort als zukünftige Referenz hinweisen.
Slezica

4
Nun, diese Antwort wurde aus irgendeinem Grund populär, deshalb habe ich beschlossen, sie lesenswert zu machen. Es dauerte nur 4 Jahre
Slezica

26

Hier ist ein Beispiel für Shared Memory:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SHM_SIZE 1024  /* make it a 1K shared memory segment */

int main(int argc, char *argv[])
{
    key_t key;
    int shmid;
    char *data;
    int mode;

    if (argc > 2) {
        fprintf(stderr, "usage: shmdemo [data_to_write]\n");
        exit(1);
    }

    /* make the key: */
    if ((key = ftok("hello.txt", 'R')) == -1) /*Here the file must exist */ 
{
        perror("ftok");
        exit(1);
    }

    /*  create the segment: */
    if ((shmid = shmget(key, SHM_SIZE, 0644 | IPC_CREAT)) == -1) {
        perror("shmget");
        exit(1);
    }

    /* attach to the segment to get a pointer to it: */
    data = shmat(shmid, NULL, 0);
    if (data == (char *)(-1)) {
        perror("shmat");
        exit(1);
    }

    /* read or modify the segment, based on the command line: */
    if (argc == 2) {
        printf("writing to segment: \"%s\"\n", argv[1]);
        strncpy(data, argv[1], SHM_SIZE);
    } else
        printf("segment contains: \"%s\"\n", data);

    /* detach from the segment: */
    if (shmdt(data) == -1) {
        perror("shmdt");
        exit(1);
    }

    return 0;
}

Schritte :

  1. Verwenden Sie ftok, um einen Pfadnamen und eine Projektkennung in einen System V IPC-Schlüssel zu konvertieren

  2. Verwenden Sie shmget, das ein gemeinsam genutztes Speichersegment zuweist

  3. Verwenden Sie shmat, um das von shmid identifizierte gemeinsam genutzte Speichersegment an den Adressraum des aufrufenden Prozesses anzuhängen

  4. Führen Sie die Operationen im Speicherbereich aus

  5. Mit shmdt abnehmen


6
Warum wirfst du 0 in eine Leere *, anstatt NULL zu verwenden?
Clément Péau

Dieser Code behandelt jedoch nicht das Löschen des gemeinsam genutzten Speichers. Nach dem Beenden des Programms muss es manuell über ipcrm -m 0 gelöscht werden.
bumfo

12

Diese sind für die Verwendung von Shared Memory enthalten

#include<sys/ipc.h>
#include<sys/shm.h>

int shmid;
int shmkey = 12222;//u can choose it as your choice

int main()
{
  //now your main starting
  shmid = shmget(shmkey,1024,IPC_CREAT);
  // 1024 = your preferred size for share memory
  // IPC_CREAT  its a flag to create shared memory

  //now attach a memory to this share memory
  char *shmpointer = shmat(shmid,NULL);

  //do your work with the shared memory 
  //read -write will be done with the *shmppointer
  //after your work is done deattach the pointer

  shmdt(&shmpointer, NULL);

8

Probieren Sie dieses Codebeispiel aus, ich habe es getestet, Quelle: http://www.makelinux.net/alp/035

#include <stdio.h> 
#include <sys/shm.h> 
#include <sys/stat.h> 

int main () 
{
  int segment_id; 
  char* shared_memory; 
  struct shmid_ds shmbuffer; 
  int segment_size; 
  const int shared_segment_size = 0x6400; 

  /* Allocate a shared memory segment.  */ 
  segment_id = shmget (IPC_PRIVATE, shared_segment_size, 
                 IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR); 
  /* Attach the shared memory segment.  */ 
  shared_memory = (char*) shmat (segment_id, 0, 0); 
  printf ("shared memory attached at address %p\n", shared_memory); 
  /* Determine the segment's size. */ 
  shmctl (segment_id, IPC_STAT, &shmbuffer); 
  segment_size  =               shmbuffer.shm_segsz; 
  printf ("segment size: %d\n", segment_size); 
  /* Write a string to the shared memory segment.  */ 
  sprintf (shared_memory, "Hello, world."); 
  /* Detach the shared memory segment.  */ 
  shmdt (shared_memory); 

  /* Reattach the shared memory segment, at a different address.  */ 
  shared_memory = (char*) shmat (segment_id, (void*) 0x5000000, 0); 
  printf ("shared memory reattached at address %p\n", shared_memory); 
  /* Print out the string from shared memory.  */ 
  printf ("%s\n", shared_memory); 
  /* Detach the shared memory segment.  */ 
  shmdt (shared_memory); 

  /* Deallocate the shared memory segment.  */ 
  shmctl (segment_id, IPC_RMID, 0); 

  return 0; 
} 

2
Dies ist ein gute Code, außer ich glaube nicht , es zeigt , wie von einem Client des Shared-Memory - Segment zuzugreifen (durch Verwendung shmgetund shmataus einem anderen Verfahren), die Art des ganzen Punktes des gemeinsam genutzten Speichers ist ... = (
étale-kohomology

7

Hier ist ein mmap-Beispiel:

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

/*
 * pvtmMmapAlloc - creates a memory mapped file area.  
 * The return value is a page-aligned memory value, or NULL if there is a failure.
 * Here's the list of arguments:
 * @mmapFileName - the name of the memory mapped file
 * @size - the size of the memory mapped file (should be a multiple of the system page for best performance)
 * @create - determines whether or not the area should be created.
 */
void* pvtmMmapAlloc (char * mmapFileName, size_t size, char create)  
{      
  void * retv = NULL;                                                                                              
  if (create)                                                                                         
  {                                                                                                   
    mode_t origMask = umask(0);                                                                       
    int mmapFd = open(mmapFileName, O_CREAT|O_RDWR, 00666);                                           
    umask(origMask);                                                                                  
    if (mmapFd < 0)                                                                                   
    {                                                                                                 
      perror("open mmapFd failed");                                                                   
      return NULL;                                                                                    
    }                                                                                                 
    if ((ftruncate(mmapFd, size) == 0))               
    {                                                                                                 
      int result = lseek(mmapFd, size - 1, SEEK_SET);               
      if (result == -1)                                                                               
      {                                                                                               
        perror("lseek mmapFd failed");                                                                
        close(mmapFd);                                                                                
        return NULL;                                                                                  
      }                                                                                               

      /* Something needs to be written at the end of the file to                                      
       * have the file actually have the new size.                                                    
       * Just writing an empty string at the current file position will do.                           
       * Note:                                                                                        
       *  - The current position in the file is at the end of the stretched                           
       *    file due to the call to lseek().  
              *  - The current position in the file is at the end of the stretched                    
       *    file due to the call to lseek().                                                          
       *  - An empty string is actually a single '\0' character, so a zero-byte                       
       *    will be written at the last byte of the file.                                             
       */                                                                                             
      result = write(mmapFd, "", 1);                                                                  
      if (result != 1)                                                                                
      {                                                                                               
        perror("write mmapFd failed");                                                                
        close(mmapFd);                                                                                
        return NULL;                                                                                  
      }                                                                                               
      retv  =  mmap(NULL, size,   
                  PROT_READ | PROT_WRITE, MAP_SHARED, mmapFd, 0);                                     

      if (retv == MAP_FAILED || retv == NULL)                                                         
      {                                                                                               
        perror("mmap");                                                                               
        close(mmapFd);                                                                                
        return NULL;                                                                                  
      }                                                                                               
    }                                                                                                 
  }                                                                                                   
  else                                                                                                
  {                                                                                                   
    int mmapFd = open(mmapFileName, O_RDWR, 00666);                                                   
    if (mmapFd < 0)                                                                                   
    {                                                                                                 
      return NULL;                                                                                    
    }                                                                                                 
    int result = lseek(mmapFd, 0, SEEK_END);                                                          
    if (result == -1)                                                                                 
    {                                                                                                 
      perror("lseek mmapFd failed");                  
      close(mmapFd);                                                                                  
      return NULL;                                                                                    
    }                                                                                                 
    if (result == 0)                                                                                  
    {                                                                                                 
      perror("The file has 0 bytes");                           
      close(mmapFd);                                                                                  
      return NULL;                                                                                    
    }                                                                                              
    retv  =  mmap(NULL, size,     
                PROT_READ | PROT_WRITE, MAP_SHARED, mmapFd, 0);                                       

    if (retv == MAP_FAILED || retv == NULL)                                                           
    {                                                                                                 
      perror("mmap");                                                                                 
      close(mmapFd);                                                                                  
      return NULL;                                                                                    
    }                                                                                                 

    close(mmapFd);                                                                                    

  }                                                                                                   
  return retv;                                                                                        
}                                                                                                     

openFügt Datei-E / A-Overhead hinzu. Verwenden Sie shm_openstattdessen.
Osvein

1
@Spookbuster, in einigen Implementierungen von shm_open wird open () unter dem Deckmantel aufgerufen, daher muss ich Ihrer Einschätzung nicht zustimmen. Hier ist ein Beispiel: code.woboq.org/userspace/glibc/sysdeps/posix/shm_open.c.html
Leo

Während einige shm_open () -Implementierungen open () unter der Haube verwenden, stellt POSIX geringere Anforderungen an die von shm_open () erstellten Dateideskriptoren. Beispielsweise müssen Implementierungen keine E / A-Funktionen wie read () und write () für shm_open () -Dateideskriptoren unterstützen, sodass bestimmte Implementierungen Optimierungen für shm_open () vornehmen können, die für open () nicht möglich sind. Wenn Sie damit nur mmap () tun möchten, sollten Sie shm_open () verwenden.
Osvein

Die meisten Linux-glibc-Setups führen eine solche Optimierung durch, indem sie tmpfs verwenden, um shm_open () zu sichern. Während auf dieselben tmpfs normalerweise über open () zugegriffen werden kann, gibt es keine tragbare Möglichkeit, ihren Pfad zu kennen. Mit shm_open () können Sie diese Optimierung auf tragbare Weise verwenden. POSIX bietet shm_open () das Potenzial, eine bessere Leistung als open () zu erzielen. Nicht alle Implementierungen werden dieses Potenzial nutzen, aber es wird nicht schlechter abschneiden als open (). Aber ich stimme zu, dass meine Behauptung, dass open () immer Overhead hinzufügt, zu weit gefasst ist.
Osvein
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.