天天看點

linux程序間通信:POSIX 共享記憶體

文章目錄

  • ​​思維導圖​​
  • ​​通信原理​​
  • ​​優勢​​
  • ​​POSIX 共享記憶體 程式設計接口​​
  • ​​程式設計案例​​

思維導圖

linux程式間通信:POSIX 共享記憶體

之前學習過sysemV 的共享記憶體的實作及使用原理,參考​​linux程序間通信:system V 共享記憶體​​ POSIX 同樣提供共享記憶體的接口,基本原理和system V的共享記憶體是一樣的。

通信原理

  • 多個程序共享實體記憶體的同一塊區域(通常稱之為“段”:segment)
  • 抛棄了核心态消息轉存處理的過程,讓兩個程序直接通過一塊記憶體進行通信

我們普通的像PIPE,FIFO,消息隊列等的通信方式如下圖:

linux程式間通信:POSIX 共享記憶體

這種方式的通信不論讀寫,都需要核心态(系統調用 read,write,pipe,mkfifo,msgget,msgsnd,msgrcv等)的介入,而且都需要經過資料從虛拟位址空間到實體位址空間的拷貝。

而共享記憶體的通信方式則都避免了以上的通信問題,直接為兩個程序開辟相同的記憶體空間進行資料互動。

linux程式間通信:POSIX 共享記憶體

優勢

  • 減少了記憶體的拷貝(從使用者拷貝到核心,從核心拷貝到使用者)
  • 減少了2次系統調用(系統調用比較消耗性能,因為CPU處理系統調用時需要從使用者态切換到核心态),提高了系統性能

POSIX 共享記憶體 程式設計接口

關于共享記憶體的接口詳細使用就不一一描述,可以通過​

​man shm_open​

​這種類似的方式檢視具體如何使用接口

//建立共享記憶體
int shm_open(const char *name, int oflag, mode_t mode);
//當共享記憶體引用計數為0時,删除共享記憶體
int shm_unlink(const char *name);
//擷取檔案相關的資訊,将擷取到的資訊放入到statbuf結構體中
int fstat(int fd, struct stat *statbuf);
//調整檔案大小,通過裁剪指定位元組達到對檔案大小的精準控制
int ftruncate(int fd, off_t length);
//将程序空間的檔案映射到記憶體,也可以将程序空間的匿名區域映射到記憶體
void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
//解除檔案或者匿名映射
int munmap(void *addr, size_t length);      

以上接口包含頭檔案 ​

​<sys/mman.h> <sys/mman.h>​

程式設計案例

  1. 共享記憶體基本使用

    ​shm_read.c​

    ​ 共享記憶體的讀端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>

#define SHM_NAME "/shm"

int main() {
  int shm_fd;
  //建立共享記憶體檔案辨別符
  shm_fd = shm_open(SHM_NAME, O_RDWR | O_CREAT, 0666);
  if (shm_fd == -1) {
    printf("shm_open failed\n");
  }

  //設定共享記憶體的檔案大小
  ftruncate(shm_fd , 8192);

  //擷取共享記憶體檔案相關屬性資訊,這裡擷取的是檔案大小
  struct stat filestat;
  fstat(shm_fd, &filestat);
  printf("st_size :%ld\n",filestat.st_size);

  //映射共享記憶體,并擷取共享記憶體的位址
  char *shm_ptr;
  shm_ptr = (char*)mmap(NULL,filestat.st_size,\
        PROT_READ|PROT_WRITE,MAP_SHARED,shm_fd,0);
  close(shm_fd);
  
  //擷取共享記憶體位址中的内容并列印,最後再解除映射,删除共享記憶體
  printf("pid %d:%s\n",getpid(),shm_ptr);
  munmap(shm_ptr, filestat.st_size);
  shm_unlink(SHM_NAME);
  return 0;
}      

​shm_write.c​

​共享記憶體的寫端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>

#define SHM_NAME "/shm"

int main() {
  int shm_fd;
  //建立和讀端相同的檔案辨別
  shm_fd = shm_open(SHM_NAME, O_RDWR | O_CREAT, 0666);
  if (shm_fd == -1) {
    printf("shm_open failed\n");
  }
  
  ftruncate(shm_fd , 8192);
  struct stat filestat;
  fstat(shm_fd, &filestat);
  printf("st_size :%ld\n",filestat.st_size);

  char *shm_ptr;
  shm_ptr = (char*)mmap(NULL,filestat.st_size,\
        PROT_READ|PROT_WRITE,MAP_SHARED,shm_fd,0);
  close(shm_fd);
  
  //向共享記憶體中寫入資料,這裡利用memmove進行記憶體拷貝寫入
  char buf[] = "hello world";
  memmove(shm_ptr,buf,sizeof(buf));
  printf("pid %d:%s\n",getpid(),shm_ptr);
  //寫入完成後解除映射
  munmap(shm_ptr, filestat.st_size);
  return 0;
}      

編譯​

​gcc shm_read.c -o read -lrt​

​​ ​

​gcc shm_write.c -o write -lrt​

​ 輸出如下:

  1. 共享記憶體和信号量一同使用,記憶體通路的同步

    當讀端能夠讀出的前提是讀的時候信号量的value值為1,否則無法讀出

    同樣寫的時候對信号量進行v操作,将信号量的value值加1,為讀提供同步條件

    實作如下

    讀端​

    ​sem_shm_read.c​

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <string.h>
#include <fcntl.h>

#define SHM_NAME "/shm"
#define SEM_NAME "/memmap_sem"

int main() {
  //增加信号量的初始建立
  int shm_fd;
  sem_t *sem;
  shm_fd = shm_open(SHM_NAME, O_RDWR | O_CREAT, 0666);
  sem = sem_open(SEM_NAME, O_CREAT, 0666, 0);
  if (shm_fd == -1 || sem == SEM_FAILED) {
    printf("open failed\n");
    _exit(-1);
  }

  ftruncate(shm_fd , 8192);
  struct stat filestat;
  fstat(shm_fd, &filestat);
  printf("st_size :%ld\n",filestat.st_size);

  char *shm_ptr;
  shm_ptr = (char*)mmap(NULL,filestat.st_size,\
        PROT_READ|PROT_WRITE,MAP_SHARED,shm_fd,0);
  close(shm_fd);
  
  //讀的時候對信号量做p操作(-1),如果信号量此時為0時則無法讀出
  sem_wait(sem);
  printf("pid %d:%s\n",getpid(),shm_ptr);
  sem_close(sem);
  
  //讀完之後删除共享記憶體,删除信号量
  munmap(shm_ptr, filestat.st_size);
  shm_unlink(SHM_NAME);
  sem_unlink(SEM_NAME);
  return 0;
}      
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <string.h>
#include <fcntl.h>

#define SHM_NAME "/shm"
#define SEM_NAME "/memmap_sem"

int main() {
  int shm_fd;
  sem_t *sem;
  shm_fd = shm_open(SHM_NAME, O_RDWR | O_CREAT, 0666);
  sem = sem_open(SEM_NAME, O_CREAT, 0666, 0);
  if (shm_fd == -1 || sem == SEM_FAILED) {
    printf("open failed\n");
    _exit(-1);
  }

  ftruncate(shm_fd , 8192);
  struct stat filestat;
  fstat(shm_fd, &filestat);
  printf("st_size :%ld\n",filestat.st_size);

  char *shm_ptr;
  shm_ptr = (char*)mmap(NULL,filestat.st_size,\
        PROT_READ|PROT_WRITE,MAP_SHARED,shm_fd,0);
  close(shm_fd);

  char buf[] = "hello world";
  memmove(shm_ptr,buf,sizeof(buf));
  printf("pid %d:%s\n",getpid(),shm_ptr);

  //寫的時候對信号量的值執行v(+1)操作, 友善後續的讀
  sem_post(sem);
  sem_close(sem);
  munmap(shm_ptr, filestat.st_size);
  return 0;
}