Jak korzystać z pamięci współdzielonej w systemie Linux w C

117

Mam mały problem z jednym z moich projektów.

Próbowałem znaleźć dobrze udokumentowany przykład użycia pamięci współdzielonej, fork()ale bezskutecznie.

Zasadniczo scenariusz jest taki, że kiedy użytkownik uruchamia program, muszę przechowywać dwie wartości w pamięci współdzielonej: bieżąca_ścieżka, która jest znakiem * i nazwa_pliku, która jest również znakem * .

W zależności od argumentów polecenia, nowy proces jest uruchamiany za pomocą fork()i ten proces musi odczytać i zmodyfikować zmienną current_path przechowywaną w pamięci współdzielonej, podczas gdy zmienna nazwa_pliku jest tylko do odczytu.

Czy istnieje dobry poradnik na temat pamięci współdzielonej z przykładowym kodem (jeśli to możliwe), do którego możesz mnie skierować?

bleepzter
źródło
1
Możesz rozważyć użycie wątków zamiast procesów. Wtedy cała pamięć jest dzielona bez dalszych sztuczek.
elomage,
Poniższe odpowiedzi dotyczą zarówno mechanizmu IPC Systemu V, jak shmget()i wsp. a także czyste mmap()podejście z MAP_ANON(aka MAP_ANONYMOUS) - chociaż MAP_ANONnie jest zdefiniowane przez POSIX. Istnieje również POSIX shm_open()i shm_close()do zarządzania obiektami pamięci współdzielonej. [… Ciąg dalszy…]
Jonathan Leffler
[… Kontynuacja…] Mają tę samą zaletę, co pamięć współdzielona IPC Systemu V - obiekt pamięci współdzielonej może trwać dłużej niż czas życia procesu, który go tworzy (do czasu wykonania jakiegoś procesu shm_unlink()), podczas gdy mechanizmy używające mmap()wymagają pliku i MAP_SHAREDutrzymują się dane (i MAP_ANONwyklucza trwałość). Pełny przykład znajduje się w sekcji Uzasadnienie specyfikacji shm_open().
Jonathan Leffler

Odpowiedzi:

164

Istnieją dwa podejścia: shmgeti mmap. Porozmawiam o tym mmap, ponieważ jest bardziej nowoczesny i elastyczny, ale możesz rzucić okiem na man shmget( lub ten samouczek ), jeśli wolisz używać narzędzi w starym stylu.

mmap()Funkcja ta może być wykorzystywana do alokacji buforów pamięci z wysoce konfigurowalny parametry do kontroli dostępu i uprawnień, a także ich kopii z pamięci systemu plików, jeśli jest to konieczne.

Poniższa funkcja tworzy bufor w pamięci, który proces może udostępniać swoim dzieciom:

#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);
}

Poniżej znajduje się przykładowy program, który używa funkcji zdefiniowanej powyżej do przydzielania bufora. Proces nadrzędny zapisze wiadomość, rozwidli, a następnie zaczeka, aż jego dziecko zmodyfikuje bufor. Oba procesy mogą odczytywać i zapisywać w pamięci współdzielonej.

#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);
  }
}
Slezica
źródło
52
Właśnie dlatego Linux jest tak frustrujący dla niedoświadczonych programistów. Strona podręcznika nie wyjaśnia, jak go właściwie używać i nie ma przykładowego kodu. :(
bleepzter
47
Haha, wiem, co masz na myśli, ale tak naprawdę to dlatego, że nie jesteśmy przyzwyczajeni do czytania podręczników. Kiedy nauczyłem się je czytać i przyzwyczaiłem się do nich, stały się nawet bardziej przydatne niż kiepskie samouczki z konkretnymi pokazami. Pamiętam, że na moim kursie dotyczącym systemów operacyjnych uzyskałem 10/10, używając tylko podręczników podręcznika podczas egzaminu.
slezica,
18
shmgetjest naprawdę staromodnym, a niektórzy powiedzieliby, że przestarzały, sposób na współdzieloną pamięć ... Lepszy w użyciu mmapi shm_open, zwykłe pliki lub po prostu MAP_ANONYMOUS.
R .. GitHub STOP HELPING ICE
4
@Mark @R Macie rację, wskażę to w odpowiedzi na przyszłość.
slezica,
4
Cóż, ta odpowiedź z jakiegoś powodu stała się popularna, więc postanowiłem, że warto ją przeczytać. Zajęło to tylko 4 lata
Slezica
26

Oto przykład pamięci współdzielonej:

#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;
}

Kroki :

  1. Użyj ftok, aby przekonwertować nazwę ścieżki i identyfikator projektu na klucz IPC Systemu V.

  2. Użyj programu shmget, który przydziela segment pamięci współdzielonej

  3. Użyj shmat, aby dołączyć segment pamięci współdzielonej zidentyfikowany przez shmid do przestrzeni adresowej procesu wywołującego

  4. Wykonaj operacje w obszarze pamięci

  5. Odłącz za pomocą shmdt

Mayank
źródło
6
Dlaczego rzucasz 0 w pustkę * zamiast używać NULL?
Clément Péau
Jednak ten kod nie obsługuje usuwania pamięci współdzielonej. Po wyjściu z programu trzeba go ręcznie usunąć przez ipcrm -m 0.
bumfo
12

Obejmuje to korzystanie z pamięci współdzielonej

#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);
Bharat
źródło
8

wypróbuj ten przykładowy kod, przetestowałem go, źródło: 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; 
} 
shakram02
źródło
2
To dobry kod, z wyjątkiem tego, że nie sądzę, że pokazuje, jak uzyskać dostęp do segmentu pamięci współdzielonej przez klienta (przy użyciu shmgeti shmatz innego procesu), który jest czymś w rodzaju całego punktu pamięci współdzielonej ... = (
étale-cohomology
7

Oto przykład mmap:

#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;                                                                                        
}                                                                                                     
Lew
źródło
opendodaje narzut plików we / wy. Użyj shm_openzamiast tego.
osvein
1
@Spookbuster, w niektórych implementacjach shm_open, open () jest wywoływana pod okładkami, więc będę musiał się nie zgodzić z twoją oceną; oto przykład: code.woboq.org/userspace/glibc/sysdeps/posix/shm_open.c.html
Leo
Podczas gdy niektóre implementacje shm_open () używają open () pod maską, POSIX ma niższe wymagania dla deskryptorów plików tworzonych przez shm_open (). Na przykład implementacje nie są wymagane do obsługi funkcji I / O, takich jak read () i write () dla deskryptorów plików shm_open (), pozwalając niektórym implementacjom na dokonywanie optymalizacji dla shm_open (), których nie można wykonać dla open (). Jeśli wszystko, co zamierzasz z tym zrobić, to mmap (), powinieneś użyć shm_open ().
osvein
Większość konfiguracji Linux-glibc przeprowadza jedną z takich optymalizacji, używając tmpfs do powrotu shm_open (). Chociaż dostęp do tych samych plików tmpfs można zwykle uzyskać za pomocą metody open (), nie ma przenośnego sposobu na poznanie ścieżki. shm_open () pozwala na użycie tej optymalizacji w przenośny sposób. POSIX daje shm_open () potencjał do lepszego działania niż open (). Nie wszystkie implementacje wykorzystają ten potencjał, ale nie będzie działać gorzej niż open (). Ale zgadzam się, że moje twierdzenie, że open () zawsze dodaje narzut, jest zbyt szerokie.
osvein