Comment utiliser la mémoire partagée avec Linux en C


117

J'ai un petit problème avec l'un de mes projets.

J'ai essayé de trouver un exemple bien documenté d'utilisation de la mémoire partagée avec fork()mais sans succès.

Fondamentalement, le scénario est que lorsque l'utilisateur démarre le programme, je dois stocker deux valeurs dans la mémoire partagée: current_path qui est un char * et un file_name qui est également char * .

En fonction des arguments de la commande, un nouveau processus est lancé avec fork()et ce processus doit lire et modifier la variable current_path stockée dans la mémoire partagée tandis que la variable file_name est en lecture seule.

Existe-t-il un bon tutoriel sur la mémoire partagée avec un exemple de code (si possible) auquel vous pouvez me diriger?


1
Vous pouvez envisager d'utiliser des threads au lieu de processus. Ensuite, toute la mémoire est partagée sans autre astuce.
elomage

Les réponses ci-dessous traitent à la fois du mécanisme IPC System V shmget()et al. et aussi l' mmap()approche pure avec MAP_ANON(aka MAP_ANONYMOUS) - bien qu'elle MAP_ANONne soit pas définie par POSIX. Il existe également POSIX shm_open()et shm_close()pour la gestion des objets de mémoire partagée. […
Suite

[… Suite…] Ceux-ci ont le même avantage que la mémoire partagée System V IPC - l'objet de mémoire partagée peut persister au-delà de la durée de vie du processus qui le crée (jusqu'à ce qu'un processus s'exécute shm_unlink()), alors que les mécanismes utilisant mmap()nécessitent un fichier et MAP_SHAREDpersistent les données (et MAP_ANONempêche la persistance). Il y a un exemple complet dans la section Justification de la spécification de shm_open().
Jonathan Leffler

Réponses:


164

Il existe deux approches: shmgetet mmap. Je vais en parler mmap, car c'est plus moderne et flexible, mais vous pouvez jeter un coup d'œil à man shmget( ou à ce tutoriel ) si vous préférez utiliser les outils à l'ancienne.

La mmap()fonction peut être utilisée pour allouer des tampons de mémoire avec des paramètres hautement personnalisables pour contrôler l'accès et les autorisations, et pour les sauvegarder avec le stockage du système de fichiers si nécessaire.

La fonction suivante crée un tampon en mémoire qu'un processus peut partager avec ses enfants:

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

Voici un exemple de programme qui utilise la fonction définie ci-dessus pour allouer un tampon. Le processus parent écrira un message, un fork, puis attendra que son enfant modifie le tampon. Les deux processus peuvent lire et écrire la mémoire partagée.

#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
C'est pourquoi Linux est si frustrant pour les développeurs inexpérimentés. La page de manuel n'explique pas comment l'utiliser et il n'y a pas d'exemple de code. :(
bleepzter

47
Haha je sais ce que tu veux dire, mais c'est en fait parce que nous n'avons pas l'habitude de lire les pages de manuel. Quand j'ai appris à les lire et que je me suis habitué à eux, ils sont devenus encore plus utiles que de mauvais tutoriels avec des démonstrations particulières. Je me souviens que j'ai obtenu un 10/10 dans mon cours sur les systèmes d'exploitation en utilisant uniquement des pages de manuel pour référence pendant l'examen.
slezica

18
shmgetest une manière vraiment démodée, et certains diraient obsolète, de faire de la mémoire partagée ... Mieux vaut utiliser mmapet shm_open, des fichiers simples, ou simplement MAP_ANONYMOUS.
R .. GitHub STOP AIDER ICE

4
@Mark @R Vous avez raison, je le soulignerai dans la réponse pour référence future.
slezica

4
Eh bien, cette réponse est devenue populaire pour une raison quelconque, alors j'ai décidé de la faire valoir. Cela n'a pris que 4 ans
slezica

26

Voici un exemple de mémoire partagée:

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

Pas :

  1. Utilisez ftok pour convertir un chemin et un identifiant de projet en clé IPC System V

  2. Utilisez shmget qui alloue un segment de mémoire partagée

  3. Utilisez shmat pour attacher le segment de mémoire partagée identifié par shmid à l'espace d'adressage du processus appelant

  4. Effectuer les opérations sur la zone mémoire

  5. Détacher à l'aide de shmdt


6
Pourquoi lancez-vous 0 dans un vide * au lieu d'utiliser NULL?
Clément Péau

Cependant, ce code ne gère pas la suppression de la mémoire partagée. Après la sortie du programme, il faut le supprimer manuellement via ipcrm -m 0.
bumfo

12

Ceux-ci sont inclus pour l'utilisation de la mémoire partagée

#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

essayez cet exemple de code, je l'ai testé, source: 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
C'est un bon code, sauf que je ne pense pas qu'il montre comment accéder au segment de mémoire partagée par un client (en utilisant shmgetet à shmatpartir d'un processus différent), qui est en quelque sorte le point entier de la mémoire partagée ... = (
étale-cohomology

7

Voici un exemple 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;                                                                                        
}                                                                                                     

openajoute une surcharge d'E / S de fichier. Utilisez shm_openplutôt.
osvein

1
@Spookbuster, dans certaines implémentations de shm_open, open () est appelé sous les couvertures, donc je vais devoir être en désaccord avec votre évaluation; voici un exemple: code.woboq.org/userspace/glibc/sysdeps/posix/shm_open.c.html
Leo

alors que certaines implémentations de shm_open () utilisent open () sous le capot, POSIX a des exigences moindres pour les descripteurs de fichiers produits par shm_open (). Par exemple, les implémentations ne sont pas nécessaires pour prendre en charge les fonctions d'E / S comme read () et write () pour les descripteurs de fichier shm_open (), permettant à certaines implémentations de faire des optimisations pour shm_open () qui ne peuvent pas être faites pour open (). Si tout ce que vous allez faire avec mmap (), vous devriez utiliser shm_open ().
osvein

La plupart des configurations Linux-glibc font une telle optimisation en utilisant tmpfs pour sauvegarder shm_open (). Bien que les mêmes tmpfs soient généralement accessibles via open (), il n'y a pas de moyen portable de connaître son chemin. shm_open () vous permet d'utiliser cette optimisation de manière portable. POSIX donne à shm_open () le potentiel de fonctionner mieux que open (). Toutes les implémentations n'utiliseront pas ce potentiel, mais il ne fonctionnera pas moins bien qu'open (). Mais je conviens que mon affirmation selon laquelle open () ajoute toujours des frais généraux est trop large.
osvein
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.