写在前面:
内存共享的几种方式:
管道:简单、数据量较小
共享内存:最快
mmap:最高效
socket:最稳定
这篇文章重点分析一下mmap:
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);int munmap(void *addr, size_t length);
如果addr参数为NULL,内核会自己在进程地址空间中选择合适的地址建立映射。如果addr不是NULL,则给内核一个提示,应该从什么地址开始映射,内核会选择addr之上的某个合适的地址开始映射。建立映射后,真正的映射首地址通过返回值可以得到。len参数是需要映射的那一部分文件的长度。offset参数是从文件的什么位置开始映射,必须是页大小的整数倍(在32位体系统结构上通常是4K)。fd是指文件描述符。
prot参数有四种取值:
- PROT_EXEC表示映射的这一段可执行,例如映射共享库
- PROT_READ表示映射的这一段可读
- PROT_WRITE表示映射的这一段可写
- PROT_NONE表示映射的这一段不可访问
- MAP_SHARED多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化。
- MAP_PRIVATE多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化,也不会真的写到文件中去。
内存会自动解除,也可以调用munmap解除映射。munmap成功返回0,出错返回-1。
简单的使用样例:
将hello.c文件的内容复制到haha.c
#include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int main() { int fdin, fdout; void* src = NULL; void* dst = NULL; struct stat statbuf; if((fdin = open("./hello.c", O_RDONLY)) < 0) { perror("open"); return 1; } if((fdout = open("haha.c", O_RDWR | O_CREAT | O_TRUNC, 0666)) < 0) { perror("open"); return 1; } if(fstat(fdin, &statbuf) < 0) { perror("fstat"); return 1; } //设置输出文件 if(lseek(fdout, statbuf.st_size - 1, SEEK_SET) == -1 ) { perror("lseek"); return 1; } if(write(fdout,"", 1) != 1) { perror("write"); return 1; } if((src = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED) { perror("mmap fdin"); return 1; } if((dst = mmap(NULL, statbuf.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fdout, 0)) == MAP_FAILED) { perror("mmap fdout"); return 1; } memcpy(dst, src, statbuf.st_size); return 0; }
mmap函数的优点:
1、对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。
2、实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。
3、提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的
4、可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好的解决。 换句话说,但凡是需要用磁盘空间代替内存的时候, mmap 都可以发挥其功效。