内存映射文件

1.什么是内存映射文件

  内存映射文件,就是把磁盘上的物理文件映射至进程地址空间中,使用内存映射文件的特性是,所有的I/O都是在内核掩盖下完成,我们只需编写存取内存映射区中各个值的代码,也就是不需要调用read/write/lseek。

如图示:

  内存映射文件_第1张图片


2.内存映射文件与read文件时物理内存占用区别
  需要了解物理内存和进程空间地址的映射关系。
  可以通过 top -p $pid 查看进程使用的虚拟内存与物理内存
  VIRT:虚拟内存 KB
  RES:物理内存 KB
  也可以通过 cat /proc/$pid/status 查看进程使用的虚拟内存与物理内存
  VmHWM:虚拟内存 KB
  VmRSS:物理内存 KB

  调用read函数时,会将文件内容读取至物理内存中,并映射至进程空间地址。
  调用内存映射文件,并不需要马上将文件内容拷贝至物理内存,由缺页中断处理。
  假设用read函数读取了一个32MB的文件,会发现进程的虚拟内存和物理内存大小马上增加了32MB,
  而用内存映射文件,进程的物理内存并不会马上增加32MB。

3.什么是缺页中断
  进程线性地址空间里的页面不必常驻内存,在执行一条指令时,如果发现他要访问的页没有在内存中,那么停止该指令的执行,并产生一个页不存在异常,对应的故障处理程序可通过从外存加载该页到内存的方法来排除故障,之后,原先引起的异常的指令就可以继续执行,而不再产生异常。

4.内存映射文件与read/write文件的效率对比(建议抱着怀疑的心态阅读此段)
  当调用read函数时,先要经过系统调用,把文件内容拷贝至内核的高速缓存区,然后再把数据从高速缓存区拷贝至物理内存,实际是两次数据拷贝。
  当调用write函数时,先把物理内存的数据拷贝至内核的高速缓存区,然后再把数据从高速缓存区拷贝至磁盘的物理文件,也是两次数据拷贝。

  内存映射文件可以减少这种不必要的数据拷贝。它由mmap()将文件直接映射到进程空间地址,mmap()并没有进行数据拷贝,真正的数据拷贝是在缺页中断处理时进行的,由于mmap()将文件直接映射到进程空间地址,所以中断处理函数根据这个映射关系,直接将文件从硬盘拷贝到进程空间地址,所以只进行了一次数据拷贝,比read进行两次数据拷贝要少了一次,同理write也一样,因此,内存映射的效率要比read/write效率高( 这里考虑的是磁盘与进程交互数据的效率,实际上如果文件很小而内存够大,且文件内容基本上不更改,那么应该用read将文件加载至内存而不是用mmap)。

5.内存映射文件的用途
  文件很大,但是又需要频繁读写的文件,比如索引文件。

6.内存映射文件需要注意的细节


  /*mmap函数把一个文件或者一个Posix共享内存区对象映射至调用进程的地址空间*/
  void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  
  addr可以指定描述符fd应被映射到进程内空间的起始地址,设为NULL表示让内核自己去选择,  
  length是指定映射区大小,通常设为文件大小
  offset也就是文件的offset,也就是映射从文件哪个位置开始,通常设置为0,
  
  port用于设置内存映射区的保护:
  PORT_READ    -> 可读
  PORT_WRITE  -> 可写
  PORT_EXEC    -> 可执行
  PORT_NONE   -> 数据不可访问

  flags用于设置内存映射区的数据被修改时,是否改变其底层支撑对象(这里的对象是文件),MAP_SHARED和MAP_PRIVATE必须指定一个
  MAP_SHARED  -> 变动是共享的
  MAP_PRIVATE  -> 变动是私自的
  MAP_FIXED        -> 准确的解析addr参数

  举例:当flags设定为MAP_SHARED时,在内存中对文件的修改会同步到物理文件中,可通过less查看
  当flags设定为MAP_PRIVATE时,在内存中对文件的修改不会同步到物理文件中,可通过less查看

  /*从某个进程空间删除一个映射关系*/
  int munmap(void *addr, size_t length);
  addr参数是有mmap返回的地址,length是映射区的大小

  /*同步内存映射区与硬盘上的文件内容*/
  int msync(void *addr, size_t length, int flags);

  addr与length通常指代内存中的整个内存映射区
  flags的说明:
  MS_ASYNC            -> 执行异步写
  MS_SYNC               -> 执行同步写
  MS_INVALLDATE   -> 使高速缓存的数据失效

  MS_ASYNC与MS_SYNC必须指定一个,但是不能同时指定,它们的差别是,一旦写操作已由内核排队,MS_ASYNC即返回,而MS_SYNC则要等到写操作完成后才返回。 如果指定了MS_INVALLDATE,那么与文件不一致的内存副本都会失效,后续引用从文件中获取数据。


  需要注意内存页对齐:
  命令 getconf PAGE_SIZE 可以查看当前系统的内存页大小,假设为4096字节。
  假设物理文件是5000字节,对这个文件进行内存映射后,在内存中能访问的地址范围是0-8191(2个内存页,页对齐),但是如果对5000-8191地址的内容进行修改,并不会同步至物理文件,一旦访问8192地址或者以上的地址,就会引发SIGSEGV信号(无效内存引用)。

7.简单的内存映射文件例子

----- gcc mmap.c -----

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

#define MAXSIZE 1024*1024*16   /*文件大小,建议设置成内存页的整数倍*/
#define FILEPATH "./mmap.test" /*文件路径*/

int main(int argc, char const *argv[])
{	
	
	char* ptr = (char*)malloc(MAXSIZE); /*申请内存*/
	memset(ptr, 'A', MAXSIZE); /*填充字符A*/
	int fd = open(FILEPATH, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR); /*创建一个文件*/
	if (fd == -1) {
		perror("open fail:");
		return -1;
	}
	write(fd, ptr, MAXSIZE); /*写入到文件*/
	free(ptr); /*释放内存*/
	ptr = NULL; /*防野指针*/
	printf("create file success\n");

	/*mmap*/
	ptr = (char*)mmap(NULL, MAXSIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); /*设置为MAP_SHARED*/
	close(fd); /*创建映射区区成功后,关闭fd并不影响映射区的使用*/

	printf("ptr: %p\n", ptr); /*打印映射区的首地址*/
	printf("char: %c\n", *ptr); /**打印映射区首地址的内容*/
	*ptr = 'C'; /*修改映射区内容*,然后通过less查看*/

	return 0;
}


参考:《unix网络编程》卷2 12章共享内存区介绍


原文出自:http://blog.csdn.net/daiyudong2020/article/details/50493522


你可能感兴趣的:(linux,内存)