Як використовувати спільну пам'ять з Linux в C


117

У мене є проблема з одним із моїх проектів.

Я намагався знайти добре задокументований приклад використання спільної пам’яті, fork()але не мав успіху.

В основному сценарій полягає в тому, що коли користувач запускає програму, мені потрібно зберігати два значення в спільній пам'яті: current_path, який є char *, і ім'я файлу, яке також є char * .

Залежно від аргументів команди, починається новий процес, fork()і цей процес повинен читати та змінювати змінну current_path, що зберігається у спільній пам'яті, тоді як змінна file_name читається лише для читання.

Чи є хороший підручник із спільної пам'яті з прикладом коду (якщо можливо), до якого ви можете направити мене?


1
Ви можете розглянути можливість використання потоків замість процесів. Тоді вся пам'ять ділиться без подальших хитрощів.
еломаг

Відповіді нижче обговорюють механізм IPC System V shmget()та ін. а також чистий mmap()підхід з MAP_ANON(aka MAP_ANONYMOUS) - хоча MAP_ANONце не визначено POSIX. Існує також POSIX shm_open()і shm_close()для управління об'єктами спільної пам'яті. [… Продовження…]
Джонатан Леффлер

[… Продовження…] Вони мають ту саму перевагу, що і спільна пам'ять IP V System V - об'єкт спільної пам’яті може зберігатись протягом усього життя процесу, що створює його (доки певний процес не буде виконаний shm_unlink()), тоді як механізми, що використовують mmap()файл, вимагають MAP_SHAREDзбереження та збереження. дані (і MAP_ANONвиключає стійкість). У розділі Обгрунтування специфікації специфіки є повний приклад shm_open().
Джонатан Леффлер

Відповіді:


164

Є два підходи: shmgetі mmap. Я поговорю про те mmap, оскільки він сучасніший і гнучкіший, але ви можете поглянути man shmget( або цей підручник ), якщо хочете скористатися інструментами старого стилю.

mmap()Функція може бути використана для виділення буферів пам'яті з високим налаштованим параметрами для контролю доступу та дозволів, а також для резервних їх хранений файлової системи при необхідності.

Наступна функція створює буфер в пам'яті, яким процес може ділитися зі своїми дітьми:

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

Далі наведено приклад програми, яка використовує визначену вище функцію для виділення буфера. Батьківський процес записує повідомлення, розсилається, а потім чекає, коли його дитина змінить буфер. Обидва процеси можуть читати та записувати спільну пам'ять.

#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
Ось чому Linux настільки засмучує недосвідчених розробників. Сторінка man не пояснює, як насправді ним користуватися, і немає зразкового коду. :(
bleepzter

47
Ха-ха, я знаю, що ти маєш на увазі, але насправді це тому, що ми не звикли читати рукописи. Коли я навчився їх читати і звикав, вони стали навіть кориснішими, ніж хитрі підручники з певними демонстраціями. Я пам’ятаю, що я отримав 10/10 в курсі «Операційні системи», не використовуючи нічого, крім керівництва для довідки під час іспиту.
slezica

18
shmgetце по-справжньому старомодний, і дехто сказав би застарілий спосіб зробити спільну пам’ять ... Краще використовувати mmapі shm_openпрості файли, або просто MAP_ANONYMOUS.
R .. GitHub СТОП ДОПОМОГАЙТЕ

4
@Mark @R Хлопці, ви праві, я зазначу це у відповіді для подальшого використання.
slezica

4
Що ж, ця відповідь чомусь стала популярною, тому я вирішив зробити її вартим прочитаного. Минуло лише 4 роки
slezica

26

Ось приклад спільної пам'яті:

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

Кроки:

  1. Використовуйте ftok для перетворення імені шляху та ідентифікатора проекту в IPC-ключ System V

  2. Використовуйте shmget, який виділяє сегмент спільної пам'яті

  3. Використовуйте shmat, щоб приєднати сегмент спільної пам'яті, ідентифікований shmid, до адресного простору процесу виклику

  4. Виконайте операції над зоною пам'яті

  5. Від'єднати за допомогою shmdt


6
Чому ви вводите 0 в порожнечу * замість того, щоб використовувати NULL?
Clément Péau

Однак цей код не обробляє видалення спільної пам'яті. Після закінчення програми потрібно видалити її вручну через ipcrm -m 0.
bumfo

12

Сюди входить використання спільної пам'яті

#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

спробуйте цей зразок коду, я перевірив його, джерело: 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
Це хороший код, за винятком того, що я не думаю, що він показує, як отримати доступ до сегменту спільної пам'яті клієнтом (за допомогою shmgetта за допомогою shmatіншого процесу), який є суттєвою точкою спільної пам'яті ... = (
étale-cohomology

7

Ось приклад 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додає накладні витрати вводу / виводу файлів. Використовуйте shm_openзамість цього.
osvein

1
@Spookbuster, у деяких реалізаціях shm_open, open () викликається під кришками, тому мені доведеться не погодитися з вашою оцінкою; ось приклад: code.woboq.org/userspace/glibc/sysdeps/posix/shm_open.c.html
Лев

хоча деякі реалізації shm_open () використовують open () під кришкою, POSIX має нижчі вимоги до дескрипторів файлів, вироблених shm_open (). Наприклад, для виконання не потрібно підтримувати такі функції вводу / виводу, як read () і write () для дескрипторів файлів shm_open (), що дозволяє деяким реалізаціям робити оптимізацію для shm_open (), яку не можна зробити для open (). Якщо все, що ви збираєтеся з цим зробити, це mmap (), ви повинні використовувати shm_open ().
osvein

Більшість налаштувань Linux-glibc роблять одну таку оптимізацію, використовуючи tmpfs для резервного копіювання shm_open (). Хоча до тих самих tmpfs зазвичай можна отримати доступ через open (), немає портативного способу пізнати його шлях. shm_open () давайте використовувати цю оптимізацію на портативний спосіб. POSIX дає shm_open () потенціал для кращої роботи, ніж open (). Не всі реалізації використовуватимуть цей потенціал, але це буде не гірше, ніж open (). Але я згоден, що моє твердження про те, що open () завжди додає накладні витрати, занадто широке.
osvein
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.