文章目錄
- 思維導圖
- 通信原理
- 優勢
- POSIX 共享記憶體 程式設計接口
- 程式設計案例
思維導圖
之前學習過sysemV 的共享記憶體的實作及使用原理,參考linux程序間通信:system V 共享記憶體 POSIX 同樣提供共享記憶體的接口,基本原理和system V的共享記憶體是一樣的。
通信原理
- 多個程序共享實體記憶體的同一塊區域(通常稱之為“段”:segment)
- 抛棄了核心态消息轉存處理的過程,讓兩個程序直接通過一塊記憶體進行通信
我們普通的像PIPE,FIFO,消息隊列等的通信方式如下圖:
這種方式的通信不論讀寫,都需要核心态(系統調用 read,write,pipe,mkfifo,msgget,msgsnd,msgrcv等)的介入,而且都需要經過資料從虛拟位址空間到實體位址空間的拷貝。
而共享記憶體的通信方式則都避免了以上的通信問題,直接為兩個程序開辟相同的記憶體空間進行資料互動。
優勢
- 減少了記憶體的拷貝(從使用者拷貝到核心,從核心拷貝到使用者)
- 減少了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>
程式設計案例
-
共享記憶體基本使用
共享記憶體的讀端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
輸出如下:
-
共享記憶體和信号量一同使用,記憶體通路的同步
當讀端能夠讀出的前提是讀的時候信号量的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;
}