Como usar memória compartilhada com Linux em C

117

Eu tenho um pequeno problema com um dos meus projetos.

Tenho tentado encontrar um exemplo bem documentado de uso de memória compartilhada, fork()mas sem sucesso.

Basicamente, o cenário é que quando o usuário inicia o programa, eu preciso armazenar dois valores na memória compartilhada: current_path que é um char * e um file_name que também é char * .

Dependendo dos argumentos do comando, um novo processo é iniciado fork()e esse processo precisa ler e modificar a variável current_path armazenada na memória compartilhada enquanto a variável file_name é somente leitura.

Existe um bom tutorial sobre memória compartilhada com código de exemplo (se possível) que você pode me indicar?

bleepzter
fonte
1
Você pode considerar o uso de threads em vez de processos. Então, toda a memória é compartilhada sem mais truques.
elomage
As respostas abaixo discutem o mecanismo de IPC do System V, shmget()et al. e também a mmap()abordagem pura com MAP_ANON(aka MAP_ANONYMOUS) - embora MAP_ANONnão seja definida pelo POSIX. Há também POSIX shm_open()e shm_close()para gerenciar objetos de memória compartilhada. [... continuou ...]
Jonathan Leffler
[... continuação ...] Estes têm a mesma vantagem que a memória compartilhada IPC System V tem - o objeto de memória compartilhada pode persistir além do tempo de vida do processo que o cria (até que algum processo seja executado shm_unlink()), enquanto os mecanismos que usam mmap()requerem um arquivo e MAP_SHAREDpersistir os dados (e MAP_ANONimpede a persistência). Há um exemplo completo na seção Racional da especificação de shm_open().
Jonathan Leffler

Respostas:

164

Existem duas abordagens: shmgete mmap. Vou falar sobre mmap, já que é mais moderno e flexível, mas você pode dar uma olhada man shmget( ou neste tutorial ) se preferir usar as ferramentas de estilo antigo.

A mmap()função pode ser usada para alocar buffers de memória com parâmetros altamente personalizáveis ​​para controlar o acesso e as permissões, e para apoiá-los com o armazenamento do sistema de arquivos, se necessário.

A função a seguir cria um buffer na memória que um processo pode compartilhar com seus filhos:

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

A seguir está um programa de exemplo que usa a função definida acima para alocar um buffer. O processo pai escreverá uma mensagem, bifurcará e então aguardará que seu filho modifique o buffer. Ambos os processos podem ler e gravar na memória compartilhada.

#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
fonte
52
É por isso que o Linux é tão frustrante para desenvolvedores inexperientes. A página do manual não explica como realmente usá-lo e não existe um código de amostra. :(
bleepzter
47
Haha, eu sei o que você quer dizer, mas na verdade é porque não estamos acostumados a ler páginas de manual. Quando aprendi a lê-los e me acostumei com eles, eles se tornaram ainda mais úteis do que tutoriais ruins com demonstrações específicas. Lembro que tirei 10/10 no meu curso de Sistemas Operacionais usando nada além de páginas de manual para referência durante o exame.
slezica
18
shmgeté uma maneira realmente antiquada, e alguns diriam obsoleta, de fazer memória compartilhada ... Melhor usar mmape shm_open, arquivos simples ou simplesmente MAP_ANONYMOUS.
R .. GitHub PARAR DE AJUDAR O ICE
4
@Mark @R Vocês estão certos, irei apontar isso na resposta para referência futura.
slezica
4
Bem, essa resposta se tornou popular por algum motivo, então decidi fazer valer a pena ler. Demorou apenas 4 anos
slezica
26

Aqui está um exemplo de memória compartilhada:

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

Passos :

  1. Use ftok para converter um nome de caminho e um identificador de projeto em uma chave IPC do System V

  2. Use shmget que aloca um segmento de memória compartilhada

  3. Use shmat para anexar o segmento de memória compartilhada identificado por shmid ao espaço de endereço do processo de chamada

  4. Faça as operações na área de memória

  5. Desanexar usando shmdt

Mayank
fonte
6
Por que você está lançando 0 em um vazio * em vez de usar NULL?
Clément Péau
No entanto, este código não trata da exclusão da memória compartilhada. Após a saída do programa, é necessário excluí-lo manualmente via ipcrm -m 0.
bumfo
12

Isso inclui o uso de memória compartilhada

#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
fonte
8

experimente este exemplo de código, eu testei, 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; 
} 
shakram02
fonte
2
Este é um bom código, exceto que não acho que mostra como acessar o segmento de memória compartilhada por um cliente (usando shmgete a shmatpartir de um processo diferente), que é o ponto principal da memória compartilhada ... = (
étale-cohomology
7

Aqui está um exemplo 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;                                                                                        
}                                                                                                     
Leo
fonte
openadiciona sobrecarga de E / S de arquivo. Use em seu shm_openlugar.
osvein
1
@Spookbuster, em algumas implementações de shm_open, open () é chamado nos bastidores, então terei que discordar de sua avaliação; aqui está um exemplo: code.woboq.org/userspace/glibc/sysdeps/posix/shm_open.c.html
Leo,
enquanto algumas implementações de shm_open () usam open () sob o capô, POSIX tem requisitos mais baixos para os descritores de arquivo produzidos por shm_open (). Por exemplo, as implementações não são necessárias para suportar funções de I / O como read () e write () para descritores de arquivo shm_open (), permitindo que certas implementações façam otimizações para shm_open () que não podem ser feitas para open (). Se tudo o que você vai fazer com ele for mmap (), você deve usar shm_open ().
osvein,
A maioria das configurações do Linux-glibc faz uma otimização usando tmpfs para fazer o backup de shm_open (). Embora o mesmo tmpfs geralmente possa ser acessado por meio de open (), não há uma maneira portátil de saber seu caminho. shm_open () permite que você use essa otimização de uma maneira portável. POSIX dá potencial a shm_open () para um desempenho melhor do que open (). Nem todas as implementações farão uso desse potencial, mas não terá um desempenho pior do que open (). Mas eu concordo que minha afirmação de que open () sempre adiciona sobrecarga é muito ampla.
osvein,