写在前面:
内存共享的几种方式:
管道:简单、数据量较小
共享内存:最快
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 都可以发挥其功效。