概述

内存映射就是将一个磁盘文件和内存的某个缓冲区相映射。经过映射之后,进程可以像操作普通内存一样来操作文件。这样一来,可以减少对系统调用readwrite的调用,也就避免了用户态到内核态的频繁切换,降低了系统开销,对于读写大文件时,效率会显著增强。

函数原型

1
2
 void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
 int munmap(void *addr, size_t length);

mmap函数

参数说明:

  • 第一个参数是要将文件映射到内存的起始地址。一般该参数传入NULL,让操作系统内核来分配。函数返回值就是文件映射到进程空间内存的起始地址。
  • length指定了要映射文件到进程内存空间的长度。
  • prot制定了内存的访问权限。有以下几种参数可选:
    1. PROT_NONE:映射内存区不可访问。
    2. PROT_WRITE:可以向映射内存区写入内容。
    3. PROT_READ:可以向映射内存区读取内容。
    4. PROT_EXEC:映射内存区的内容可以被执行。
  • flags:如果由多个进程都映射了同一个文件,这个参数值将会影响进程之间对文件修改的可见性。简单地说,就是如果有一个进程更改了文件内容,另一个进程能不能立刻感知到文件内容的变化。该参数有两个参数可选:
    1. MAP_SHARED:设定该参数后,映射同一文件区域的不同进程会感知到文件的变化。
    2. MAP_PRIVATE:和上一个参数相反,这块共享内存是私有的,其他进程感知不到变化。
  • fd参数是要映射文件的文件描述符。也就意味着,要使用内存映射文件,必须先用open 函数打开文件,获得文件描述符。
  • offset一般设置为0 ,代表从文件开头开始映射。

返回值:

mmap调用成功会返回映射到进程内存空间的地址,否则会返回MAP_FAILED,该值本质上是(void *)-1,一次在判断mmap函数是否调用成功可以利用函数返回值来判断。

munmap函数

munmap函数可以取消当前进程内存空间中由addr开始的长度为length的内存映射。

代码实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <error.h>
#include <strings.h>

void print_errno(char *str,int line,int err_no)
{
    printf("%d,%s:%s\n",line,str,strerror(err_no));
    exit(-1);
}

int main(void)
{
    int srcfd=-1;
    void *srcaddr=NULL;

    int destfd=-1;
    void *destaddr=NULL;

    srcfd=open("./file.h",O_RDWR);
    if(srcfd==-1){
        print_errno("open file",__LINE__,errno);
    }

    destfd=open("./file",O_RDWR|O_CREAT|O_TRUNC,0664);
    if(destfd==-1){
        print_errno("open file",__LINE__,errno);
    }
    struct stat srcstat={0};
    fstat(srcfd,&srcstat);
    srcaddr=mmap(NULL,srcstat.st_size,PROT_READ,MAP_SHARED,srcfd,0);
    if(srcaddr==(void *)-1)
        print_errno("mmap failed",__LINE__,errno);

    ftruncate(destfd,srcstat.st_size);

    destaddr=mmap(NULL,srcstat.st_size,PROT_WRITE,MAP_SHARED,destfd,0);
    if(destaddr==(void *)-1)
        print_errno("mmap failed",__LINE__,errno);

    memcpy(destaddr,srcaddr,srcstat.st_size);

    munmap(srcaddr,srcstat.st_size);
    munmap(destaddr,srcstat.st_size);

    close(srcfd);
    close(destfd);
    return 0;
}

上述程序将file.h文件中的内容通过内存映射,复制到了file文件。需要注意的是,file.h文件是原本就存在的,但是file文件是新创建的,所以file文件的初始大小是0,如果直接进行映射并写入,那么由于文件大小是0,系统会报出总线错误。所以在进行映射之前,需要改变新创建的文件的大小。程序中调用了ftruncate函数,将文件变为和file.h文件一样大,在进行映射拷贝就不会出错了。