Come utilizzare la memoria condivisa con Linux in C


117

Ho qualche problema con uno dei miei progetti.

Ho cercato di trovare un esempio ben documentato di utilizzo della memoria condivisa con fork()ma senza successo.

Fondamentalmente lo scenario è che quando l'utente avvia il programma, ho bisogno di memorizzare due valori nella memoria condivisa: current_path che è un char * e un file_name che è anche char * .

A seconda degli argomenti del comando, viene avviato un nuovo processo fork()e tale processo deve leggere e modificare la variabile current_path archiviata nella memoria condivisa mentre la variabile file_name è di sola lettura.

C'è un buon tutorial sulla memoria condivisa con codice di esempio (se possibile) a cui puoi indirizzarmi?


1
Potresti prendere in considerazione l'utilizzo di thread anziché processi. Quindi l'intera memoria viene condivisa senza ulteriori trucchi.
elomaggio

Le risposte seguenti discutono sia il meccanismo IPC di System V, sia shmget()altri. e anche l' mmap()approccio puro con MAP_ANON(aka MAP_ANONYMOUS) - sebbene MAP_ANONnon sia definito da POSIX. C'è anche POSIX shm_open()e shm_close()per la gestione degli oggetti di memoria condivisa. [… Continua…]
Jonathan Leffler

[... continuation ...] Questi hanno lo stesso vantaggio che ha la memoria condivisa IPC di System V: l'oggetto di memoria condivisa può persistere oltre la durata del processo che lo crea (fino a quando non viene eseguito un processo shm_unlink()), mentre i meccanismi che utilizzano mmap()richiedono un file e MAP_SHAREDpersistono i dati (e MAP_ANONpreclude la persistenza). C'è un esempio completo nella sezione Rationale della specifica di shm_open().
Jonathan Leffler

Risposte:


164

Esistono due approcci: shmgete mmap. Ne parlerò mmap, visto che è più moderno e flessibile, ma puoi dare un'occhiata a man shmget( oa questo tutorial ) se preferisci usare gli strumenti vecchio stile.

Il mmap() funzione può essere utilizzata per allocare buffer di memoria con parametri altamente personalizzabili per controllare l'accesso e le autorizzazioni e per supportarli con l'archiviazione del file system, se necessario.

La funzione seguente crea un buffer in memoria che un processo può condividere con i suoi figli:

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

Il seguente è un programma di esempio che utilizza la funzione definita sopra per allocare un buffer. Il processo padre scriverà un messaggio, eseguirà il fork e quindi attenderà che il suo figlio modifichi il buffer. Entrambi i processi possono leggere e scrivere nella memoria condivisa.

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

52
Questo è il motivo per cui Linux è così frustrante per gli sviluppatori inesperti. La pagina man non spiega come usarlo effettivamente e non esiste un codice di esempio. :(
bleepzter

47
Haha, so cosa intendi, ma in realtà è perché non siamo abituati a leggere le manpage. Quando ho imparato a leggerli e mi ci sono abituato, sono diventati ancora più utili di schifosi tutorial con dimostrazioni particolari. Ricordo di aver ottenuto un 10/10 nel corso sui sistemi operativi utilizzando nient'altro che le manpage come riferimento durante l'esame.
slezica

18
shmgetè un modo davvero antiquato, e alcuni direbbero deprecato, di utilizzare la memoria condivisa ... Meglio usare mmape shm_open, file semplici o semplicemente MAP_ANONYMOUS.
R .. GitHub SMETTA DI AIUTARE IL GHIACCIO

4
@Mark @R Avete ragione, lo farò notare nella risposta per riferimento futuro.
slezica

4
Bene, questa risposta è diventata popolare per qualche motivo, quindi ho deciso di farne valere la pena. Ci sono voluti solo 4 anni
slezica

26

Ecco un esempio per la memoria condivisa:

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

Passaggi:

  1. Utilizzare ftok per convertire un percorso e un identificatore di progetto in una chiave IPC System V

  2. Usa shmget che alloca un segmento di memoria condivisa

  3. Utilizzare shmat per allegare il segmento di memoria condivisa identificato da shmid allo spazio degli indirizzi del processo chiamante

  4. Eseguire le operazioni sull'area di memoria

  5. Scollega usando shmdt


6
Perché lanci 0 in un vuoto * invece di usare NULL?
Clément Péau

Tuttavia questo codice non gestisce l'eliminazione della memoria condivisa. Dopo l'uscita dal programma, è necessario eliminarlo manualmente tramite ipcrm -m 0.
bumfo

12

Questi includono per l'utilizzo della memoria condivisa

#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

prova questo esempio di codice, l'ho testato, fonte: 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
Questo è un buon codice, tranne che non penso che mostri come accedere al segmento di memoria condivisa da un client (utilizzando shmgete shmatda un processo diverso), che è un po 'il punto centrale della memoria condivisa ... = (
étale-cohomology

7

Ecco un esempio di 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;                                                                                        
}                                                                                                     

openaggiunge l'overhead di I / O del file. Usa shm_openinvece.
Osvein

1
@Spookbuster, in alcune implementazioni di shm_open, open () viene chiamata sotto le coperte, quindi dovrò non essere d'accordo con la tua valutazione; ecco un esempio: code.woboq.org/userspace/glibc/sysdeps/posix/shm_open.c.html
Leo

mentre alcune implementazioni di shm_open () usano open () sotto il cofano, POSIX ha requisiti inferiori per i descrittori di file prodotti da shm_open (). Ad esempio, le implementazioni non sono necessarie per supportare le funzioni di I / O come read () e write () per i descrittori di file shm_open (), consentendo ad alcune implementazioni di effettuare ottimizzazioni per shm_open () che non possono essere fatte per open (). Se tutto ciò che vuoi fare con esso è mmap (), dovresti usare shm_open ().
osvein

La maggior parte delle configurazioni Linux-glibc effettua una tale ottimizzazione utilizzando tmpfs per eseguire il backup di shm_open (). Sebbene lo stesso tmpfs sia normalmente accessibile tramite open (), non esiste un modo portabile per conoscerne il percorso. shm_open () ti consente di utilizzare tale ottimizzazione in modo portatile. POSIX offre a shm_open () il potenziale per funzionare meglio di open (). Non tutte le implementazioni useranno questo potenziale, ma non avrà prestazioni peggiori di open (). Ma sono d'accordo che la mia affermazione secondo cui open () aggiunge sempre overhead è troppo ampia.
osvein
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.