思维导图
之前学习过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; }
写端
shm_write.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); 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; }