Cómo usar la memoria compartida con Linux en C

11 minutos de lectura

avatar de usuario
pitido

Tengo un pequeño problema con uno de mis proyectos.

He estado tratando de encontrar un ejemplo bien documentado del uso de memoria compartida con fork() pero sin éxito.

Básicamente, el escenario es que cuando el usuario inicia el programa, necesito almacenar dos valores en la memoria compartida: trayectoria de corriente el cual es un carbonizarse* y un nombre del archivo cual es también carbonizarse*.

Dependiendo de los argumentos del comando, se inicia un nuevo proceso con fork() y ese proceso necesita leer y modificar el trayectoria de corriente variable almacenada en la memoria compartida mientras que la nombre del archivo variable es de solo lectura.

¿Hay algún buen tutorial sobre memoria compartida con código de ejemplo (si es posible) al que me pueda dirigir?

  • Puede considerar usar subprocesos en lugar de procesos. Entonces toda la memoria se comparte sin más trucos.

    – elomage

    4 de febrero de 2014 a las 12:22

  • Las respuestas a continuación analizan tanto el mecanismo System V IPC, shmget() et al. y tambien el puro mmap() acercarse con MAP_ANON (también conocido como MAP_ANONYMOUS) – aunque MAP_ANON no está definido por POSIX. También hay POSIX shm_open() y shm_close() para la gestión de objetos de memoria compartida. […continued…]

    –Jonathan Leffler

    15 de marzo de 2020 a las 3:59


  • […continuation…] Estos tienen la misma ventaja que la memoria compartida System V IPC: el objeto de memoria compartida puede persistir más allá de la vida útil del proceso que lo crea (hasta que algún proceso se ejecute). shm_unlink()), mientras que los mecanismos que utilizan mmap() requiere un archivo y MAP_SHARED persistir los datos (y MAP_ANON impide la persistencia). Hay un ejemplo completo en la sección Justificación de la especificación de shm_open().

    –Jonathan Leffler

    15 de marzo de 2020 a las 4:01


avatar de usuario
slezica

Hay dos enfoques: shmget y mmap. Hablaré de mmapya que es más moderno y flexible, pero puedes echarle un vistazo man shmget (o este tutorial) si prefiere usar las herramientas de estilo antiguo.

Él mmap() La función se puede utilizar para asignar búferes de memoria con parámetros altamente personalizables para controlar el acceso y los permisos, y para respaldarlos con el almacenamiento del sistema de archivos si es necesario.

La siguiente función crea un búfer en memoria que un proceso puede compartir con sus hijos:

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

El siguiente es un programa de ejemplo que utiliza la función definida anteriormente para asignar un búfer. El proceso principal escribirá un mensaje, se bifurcará y luego esperará a que su hijo modifique el búfer. Ambos procesos pueden leer y escribir en la memoria compartida.

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

  • Esta es la razón por la que Linux es tan frustrante para los desarrolladores sin experiencia. La página de manual no explica cómo usarlo realmente, y no hay un código de muestra. 🙁

    – bipzter

    13 de abril de 2011 a las 22:46

  • Jaja, sé lo que quieres decir, pero en realidad es porque no estamos acostumbrados a leer páginas de manual. Cuando aprendí a leerlos y me acostumbré a ellos, se volvieron aún más útiles que los pésimos tutoriales con demostraciones particulares. Recuerdo que obtuve un 10/10 en mi curso de Sistemas Operativos usando nada más que páginas de manual como referencia durante el examen.

    – slezica

    13 de abril de 2011 a las 22:51


  • shmget es una forma realmente anticuada, y algunos dirían obsoleta, de hacer memoria compartida… Es mejor usar mmap y shm_openarchivos simples o simplemente MAP_ANONYMOUS.

    – R.. GitHub DEJA DE AYUDAR A ICE

    13 de abril de 2011 a las 23:29

  • @Mark @R Tienen razón, lo señalaré en la respuesta para futuras referencias.

    – slezica

    14/04/2011 a las 21:16

  • Bueno, esta respuesta se hizo popular por alguna razón, así que decidí que valiera la pena leerla. Solo tomó 4 años

    – slezica

    26 mayo 2017 a las 20:16

avatar de usuario
Mayak

Aquí hay un ejemplo de memoria compartida:

#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: */
    if ((data = shmat(shmid, NULL, 0)) == (void *)-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;
}

Pasos :

  1. Use ftok para convertir un nombre de ruta y un identificador de proyecto en una clave System V IPC

  2. Use shmget que asigna un segmento de memoria compartida

  3. Use shmat para adjuntar el segmento de memoria compartida identificado por shmid al espacio de direcciones del proceso de llamada

  4. Hacer las operaciones en el área de memoria

  5. Separar usando shmdt

  • ¿Por qué estás lanzando 0 en un vacío * en lugar de usar NULL?

    – Clement Peau

    28 de marzo de 2017 a las 15:45

  • Sin embargo, este código no maneja la eliminación de la memoria compartida. Después de que el programa sale, hay que eliminarlo manualmente a través de ipcrm -m 0.

    – bumfo

    16 de septiembre de 2019 a las 7:15


avatar de usuario
Bharat

Estos son incluidos para usar la memoria compartida

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

prueba este ejemplo de código, lo probé, fuente: 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; 
} 

Aquí hay un ejemplo de 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;                                                                                        
}                                                                                                     

  • open agrega sobrecarga de E/S de archivo. Usar shm_open en cambio.

    – osvein

    7 abr 2018 a las 21:02

  • @Spookbuster, en algunas implementaciones de shm_open, open() se llama encubiertamente, por lo que tendré que estar en desacuerdo con su evaluación; aquí hay un ejemplo: code.woboq.org/userspace/glibc/sysdeps/posix/shm_open.c.html

    – León

    09/04/2018 a las 19:29

  • mientras que algunas implementaciones de shm_open() usan open() bajo el capó, POSIX tiene requisitos más bajos para los descriptores de archivo producidos por shm_open(). Por ejemplo, no se requieren implementaciones para admitir funciones de E/S como read() y write() para los descriptores de archivo shm_open(), lo que permite que ciertas implementaciones realicen optimizaciones para shm_open() que no se pueden realizar para open(). Si todo lo que va a hacer con él es mmap(), debe usar shm_open().

    – osvein

    09/04/2018 a las 20:46


  • La mayoría de las configuraciones de Linux-glibc realizan una optimización de este tipo mediante el uso de tmpfs para respaldar shm_open(). Si bien normalmente se puede acceder a los mismos tmpfs a través de open(), no existe una forma portátil de conocer su ruta. shm_open() te permite usar esa optimización de manera portátil. POSIX le da a shm_open() potencial para funcionar mejor que open(). No todas las implementaciones harán uso de ese potencial, pero no funcionará peor que open(). Pero estoy de acuerdo en que mi afirmación de que open() siempre agrega gastos generales es demasiado amplia.

    – osvein

    09/04/2018 a las 20:50


  • open agrega sobrecarga de E/S de archivo. Usar shm_open en cambio.

    – osvein

    7 abr 2018 a las 21:02

  • @Spookbuster, en algunas implementaciones de shm_open, open() se llama encubiertamente, por lo que tendré que estar en desacuerdo con su evaluación; aquí hay un ejemplo: code.woboq.org/userspace/glibc/sysdeps/posix/shm_open.c.html

    – León

    09/04/2018 a las 19:29

  • mientras que algunas implementaciones de shm_open() usan open() bajo el capó, POSIX tiene requisitos más bajos para los descriptores de archivos producidos por shm_open(). Por ejemplo, no se requieren implementaciones para admitir funciones de E/S como read() y write() para los descriptores de archivo shm_open(), lo que permite que ciertas implementaciones realicen optimizaciones para shm_open() que no se pueden realizar para open(). Si todo lo que va a hacer con él es mmap(), debe usar shm_open().

    – osvein

    09/04/2018 a las 20:46


  • La mayoría de las configuraciones de Linux-glibc realizan una optimización de este tipo mediante el uso de tmpfs para respaldar shm_open(). Si bien normalmente se puede acceder a los mismos tmpfs a través de open(), no existe una forma portátil de conocer su ruta. shm_open() te permite usar esa optimización de manera portátil. POSIX le da a shm_open() potencial para funcionar mejor que open(). No todas las implementaciones harán uso de ese potencial, pero no funcionará peor que open(). Pero estoy de acuerdo en que mi afirmación de que open() siempre agrega gastos generales es demasiado amplia.

    – osvein

    09/04/2018 a las 20:50


¿Ha sido útil esta solución?