Cách sử dụng bộ nhớ dùng chung với Linux trong C


117

Tôi có một chút vấn đề với một trong những dự án của mình.

Tôi đã cố gắng tìm một ví dụ được ghi chép rõ ràng về việc sử dụng bộ nhớ dùng chung với fork()nhưng không thành công.

Về cơ bản, kịch bản là khi người dùng khởi động chương trình, tôi cần lưu trữ hai giá trị trong bộ nhớ chia sẻ: current_pathchar * và tên tệp cũng là char * .

Tùy thuộc vào các đối số lệnh, một tiến trình mới được khởi động fork()và tiến trình đó cần đọc và sửa đổi biến current_path được lưu trữ trong bộ nhớ dùng chung trong khi biến file_name chỉ được đọc.

Có hướng dẫn tốt về bộ nhớ dùng chung với mã ví dụ (nếu có thể) mà bạn có thể hướng dẫn tôi không?


1
Bạn có thể cân nhắc sử dụng các luồng thay vì các quy trình. Sau đó, toàn bộ bộ nhớ được chia sẻ mà không có thủ thuật nào khác.
elomage

Các câu trả lời dưới đây thảo luận về cả cơ chế Hệ thống V IPC, shmget()et al. và cũng là mmap()cách tiếp cận thuần túy với MAP_ANON(hay còn gọi là MAP_ANONYMOUS) - mặc dù MAP_ANONkhông được định nghĩa bởi POSIX. Ngoài ra còn có POSIX shm_open()shm_close()để quản lý các đối tượng bộ nhớ dùng chung. [… Tiếp tục…]
Jonathan Leffler

[… Tiếp tục…] Những điều này có cùng lợi thế mà bộ nhớ chia sẻ IPC System V có - đối tượng bộ nhớ dùng chung có thể tồn tại ngoài thời gian tồn tại của quá trình tạo ra nó (cho đến khi một số quá trình thực thi shm_unlink()), trong khi các cơ chế sử dụng mmap()yêu cầu tệp và MAP_SHAREDtồn tại dữ liệu (và MAP_ANONloại trừ sự tồn tại). Có một ví dụ đầy đủ trong phần Cơ sở lý luận của đặc điểm kỹ thuật của shm_open().
Jonathan Leffler

Câu trả lời:


164

Có hai cách tiếp cận: shmgetmmap. Tôi sẽ nói về mmapnó, vì nó hiện đại và linh hoạt hơn, nhưng bạn có thể xem qua man shmget( hoặc hướng dẫn này ) nếu bạn muốn sử dụng các công cụ kiểu cũ.

Các mmap()chức năng có thể được sử dụng để phân bổ bộ đệm bộ nhớ với các thông số tùy biến cao để kiểm soát truy cập và điều khoản, và để sao chúng với lưu trữ tập tin hệ thống nếu cần thiết.

Hàm sau tạo một bộ đệm trong bộ nhớ mà một tiến trình có thể chia sẻ với các con của nó:

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

Sau đây là một chương trình ví dụ sử dụng hàm được định nghĩa ở trên để cấp phát bộ đệm. Tiến trình mẹ sẽ viết một thông báo, fork, và sau đó đợi con của nó sửa đổi bộ đệm. Cả hai tiến trình đều có thể đọc và ghi bộ nhớ dùng chung.

#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
Đây là lý do tại sao Linux rất khó chịu đối với các nhà phát triển thiếu kinh nghiệm. Trang người đàn ông không giải thích cách thực sự sử dụng nó và không có mã mẫu. :(
bleepzter

47
Haha, tôi biết bạn muốn nói gì, nhưng thực ra là do chúng tôi không quen đọc các trang. Khi tôi học cách đọc chúng và làm quen với chúng, chúng thậm chí còn trở nên hữu ích hơn những bài hướng dẫn tệ hại với những minh chứng cụ thể. Tôi nhớ rằng tôi đã đạt điểm 10/10 trong khóa học Hệ điều hành và không sử dụng gì ngoài các trang để tham khảo trong kỳ thi.
slzica

18
shmgetlà một cách thực sự lỗi thời, và một số người sẽ nói rằng cách sử dụng bộ nhớ chia sẻ không được dùng nữa ... Tốt hơn là sử dụng mmapshm_open, các tệp đơn giản, hoặc đơn giản hơn MAP_ANONYMOUS.
R .. GitHub DỪNG TRỢ GIÚP ICE

4
@Mark @R Các bạn nói đúng, tôi sẽ chỉ ra điều đó trong câu trả lời để tham khảo trong tương lai.
slzica

4
Chà, câu trả lời này đã trở nên phổ biến vì một số lý do, vì vậy tôi quyết định làm cho nó đáng để đọc. Nó chỉ mất 4 năm
slzica

26

Đây là một ví dụ cho bộ nhớ được chia sẻ:

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

Các bước:

  1. Sử dụng ftok để chuyển đổi tên đường dẫn và số nhận dạng dự án thành khóa IPC Hệ thống V

  2. Sử dụng shmget phân bổ phân đoạn bộ nhớ dùng chung

  3. Sử dụng shmat để đính kèm đoạn bộ nhớ chia sẻ được xác định bởi shmid vào không gian địa chỉ của quá trình gọi

  4. Thực hiện các thao tác trên vùng nhớ

  5. Tách bằng shmdt


6
Tại sao bạn chuyển 0 vào một khoảng trống * thay vì sử dụng NULL?
Clément Péau

Tuy nhiên, mã này không xử lý việc xóa bộ nhớ dùng chung. Sau khi thoát khỏi chương trình, người ta phải xóa nó bằng tay qua ipcrm -m 0.
bumfo

12

Chúng bao gồm để sử dụng bộ nhớ dùng chung

#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

hãy thử mẫu mã này, tôi đã thử nghiệm nó, nguồn: 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
Đây là một đoạn mã tốt, ngoại trừ việc tôi không nghĩ nó cho thấy cách truy cập vào phân đoạn bộ nhớ dùng chung bởi một máy khách (bằng cách sử dụng shmgetshmattừ một quy trình khác), đó là toàn bộ điểm của bộ nhớ được chia sẻ ... = (
étale-cohomology

7

Đây là một ví dụ về 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;                                                                                        
}                                                                                                     

openthêm chi phí I / O tệp. Sử dụng shm_openthay thế.
osvein

1
@Spookbuster, trong một số triển khai của shm_open, open () được gọi dưới các trang bìa, vì vậy tôi sẽ không đồng ý với đánh giá của bạn; đây là một ví dụ: code.woboq.org/userspace/glibc/sysdeps/posix/shm_open.c.html
Leo

trong khi một số triển khai shm_open () sử dụng open () bên dưới, POSIX có yêu cầu thấp hơn đối với bộ mô tả tệp do shm_open () tạo ra. Ví dụ: không cần triển khai để hỗ trợ các chức năng I / O như read () và write () cho bộ mô tả tệp shm_open (), cho phép một số triển khai thực hiện tối ưu hóa cho shm_open () không thể thực hiện cho open (). Nếu tất cả những gì bạn định làm với nó là mmap (), bạn nên sử dụng shm_open ().
osvein

Hầu hết các thiết lập Linux-glibc thực hiện một tối ưu hóa như vậy bằng cách sử dụng tmpfs để sao lưu shm_open (). Mặc dù các tmpfs giống nhau thường có thể được truy cập thông qua open (), nhưng không có cách di động nào để biết đường dẫn của nó. shm_open () cho phép bạn sử dụng tối ưu hóa đó theo cách di động. POSIX cung cấp cho shm_open () tiềm năng hoạt động tốt hơn open (). Không phải tất cả các triển khai đều tận dụng được tiềm năng đó, nhưng nó sẽ không hoạt động kém hơn open (). Nhưng tôi đồng ý rằng tuyên bố của tôi rằng open () luôn thêm chi phí là quá rộng.
osvein
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.