Cara menggunakan memori bersama dengan Linux di C

117

Saya memiliki sedikit masalah dengan salah satu proyek saya.

Saya telah mencoba untuk menemukan contoh yang terdokumentasi dengan baik tentang penggunaan memori bersama fork()tetapi tidak berhasil.

Pada dasarnya skenario adalah bahwa ketika pengguna memulai program, saya perlu menyimpan dua nilai dalam memori bersama: current_path yang merupakan char * dan nama_file yang juga char * .

Bergantung pada argumen perintah, proses baru dimulai fork()dan proses itu perlu membaca dan memodifikasi variabel current_path yang disimpan dalam memori bersama sementara variabel nama_file hanya baca.

Apakah ada tutorial bagus tentang memori bersama dengan kode contoh (jika mungkin) yang dapat Anda arahkan ke saya?

bleepzter.dll
sumber
1
Anda dapat mempertimbangkan untuk menggunakan utas alih-alih proses. Kemudian seluruh memori dibagikan tanpa trik lebih lanjut.
elomage
Jawaban di bawah ini membahas mekanisme Sistem V IPC, shmget()dkk. dan juga mmap()pendekatan murni dengan MAP_ANON(alias MAP_ANONYMOUS) - meskipun MAP_ANONtidak ditentukan oleh POSIX. Ada juga POSIX shm_open()dan shm_close()untuk mengelola objek memori bersama. [… Lanjutan…]
Jonathan Leffler
[… Lanjutan…] Ini memiliki keuntungan yang sama dengan memori bersama IPC Sistem V - objek memori bersama dapat bertahan melampaui masa pakai proses yang membuatnya (sampai beberapa proses dijalankan shm_unlink()), sedangkan mekanisme yang menggunakan mmap()memerlukan file dan MAP_SHAREDuntuk bertahan data (dan MAP_ANONmenghalangi ketekunan). Ada contoh lengkap di bagian Rationale dari spesifikasi shm_open().
Jonathan Leffler

Jawaban:

164

Ada dua pendekatan: shmgetdan mmap. Saya akan membicarakannya mmap, karena ini lebih modern dan fleksibel, tetapi Anda dapat melihat man shmget( atau tutorial ini ) jika Anda lebih suka menggunakan alat gaya lama.

The mmap()fungsi dapat digunakan untuk mengalokasikan buffer memori dengan parameter yang sangat disesuaikan untuk akses kontrol dan hak akses, dan untuk mendukung mereka dengan penyimpanan file-sistem jika diperlukan.

Fungsi berikut membuat buffer dalam memori yang dapat dibagikan oleh proses dengan anaknya:

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

Berikut ini adalah contoh program yang menggunakan fungsi yang ditentukan di atas untuk mengalokasikan buffer. Proses induk akan menulis pesan, garpu, dan kemudian menunggu anaknya memodifikasi buffer. Kedua proses tersebut dapat membaca dan menulis memori bersama.

#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.dll
sumber
52
Inilah sebabnya mengapa Linux sangat membuat frustasi bagi pengembang yang tidak berpengalaman. Halaman manual tidak menjelaskan bagaimana sebenarnya menggunakannya, dan tidak ada kode contoh. :(
bleepzter
47
Haha aku mengerti maksudmu, tapi sebenarnya karena kita tidak terbiasa membaca manual. Ketika saya belajar membacanya dan terbiasa dengannya, mereka menjadi lebih berguna daripada tutorial yang buruk dengan demonstrasi tertentu. Saya ingat saya mendapat nilai 10/10 dalam kursus Sistem Operasi saya hanya menggunakan halaman manual untuk referensi selama ujian.
slezica
18
shmgetadalah cara yang sangat kuno, dan beberapa orang akan mengatakan usang, cara untuk melakukan memori bersama ... Lebih baik menggunakan mmapdan shm_open, file biasa, atau sederhana MAP_ANONYMOUS.
R .. GitHub STOP HELPING ICE
4
@Mark @R Kalian benar, saya akan tunjukkan itu di jawaban untuk referensi di masa mendatang.
slezica
4
Nah, jawaban ini menjadi populer karena beberapa alasan, jadi saya memutuskan untuk membuatnya layak dibaca. Hanya butuh 4 tahun
slezica
26

Berikut adalah contoh memori bersama:

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

Langkah :

  1. Gunakan ftok untuk mengubah nama jalur dan pengidentifikasi proyek menjadi kunci IPC System V.

  2. Gunakan shmget yang mengalokasikan segmen memori bersama

  3. Gunakan shmat untuk melampirkan segmen memori bersama yang diidentifikasi oleh shmid ke ruang alamat proses panggilan

  4. Lakukan operasi di area memori

  5. Lepaskan menggunakan shmdt

Mayank
sumber
6
Mengapa Anda mentransmisikan 0 ke dalam void * daripada menggunakan NULL?
Clément Péau
Namun kode ini tidak menangani penghapusan memori bersama. Setelah program keluar, seseorang harus menghapusnya secara manual melalui ipcrm -m 0.
bumfo
12

Ini termasuk untuk menggunakan memori bersama

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

coba contoh kode ini, saya mengujinya, sumber: 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
sumber
2
Ini adalah kode yang bagus, kecuali menurut saya ini tidak menunjukkan cara mengakses segmen memori bersama oleh klien (dengan menggunakan shmgetdan shmatdari proses yang berbeda), yang merupakan inti dari memori bersama ... = (
étale-cohomology
7

Berikut contoh 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
sumber
openmenambahkan overhead I / O file. Gunakan shm_opensebagai gantinya.
osvein
1
@Spookbuster, dalam beberapa implementasi shm_open, open () dipanggil di bawah penutup, jadi saya harus tidak setuju dengan penilaian Anda; berikut ini contohnya: code.woboq.org/userspace/glibc/sysdeps/posix/shm_open.c.html
Leo
sementara beberapa implementasi shm_open () menggunakan open () di bawah tenda, POSIX memiliki persyaratan yang lebih rendah untuk deskriptor file yang dihasilkan oleh shm_open (). Misalnya, implementasi tidak diperlukan untuk mendukung fungsi I / O seperti read () dan write () untuk deskriptor file shm_open (), memungkinkan implementasi tertentu membuat pengoptimalan untuk shm_open () yang tidak dapat dibuat untuk open (). Jika semua yang akan Anda lakukan dengannya adalah mmap (), Anda harus menggunakan shm_open ().
osvein
Kebanyakan konfigurasi Linux-glibc membuat satu pengoptimalan seperti itu dengan menggunakan tmpfs untuk memundurkan shm_open (). Meskipun tmpf yang sama biasanya dapat diakses melalui open (), tidak ada cara portabel untuk mengetahui jalurnya. shm_open () memungkinkan Anda menggunakan pengoptimalan itu secara portabel. POSIX memberikan potensi shm_open () untuk bekerja lebih baik daripada open (). Tidak semua implementasi akan memanfaatkan potensi tersebut, tetapi tidak akan berkinerja lebih buruk daripada open (). Tetapi saya setuju bahwa klaim saya bahwa open () selalu menambahkan overhead terlalu luas.
osvein