您的位置:

深入浅出mmap内存映射

一、mmap内存映射主要用在哪里

mmap内存映射是一种将文件或其他对象映射到进程地址空间的机制,主要用于以下场景:

1. 文件映射:在许多操作系统中,可以通过mmap将一个文件的整个内容映射到进程的地址空间中。这种技术被广泛应用于一些高效的文件操作,如数据库系统,谷歌的大量的Web服务器和开发一些离线处理的大数据应用。

2. 共享内存:mmap还可以将一块共享内存映射到多个进程的地址空间中,这些进程可以直接通过访问共享内存来交换数据,可以在这些进程间实现高效的通信。

3. 匿名映射:通过mmap可以在进程的地址空间中分配一块虚拟内存,来实现不同进程之间的数据共享,这种匿名映射的内存映射技术被广泛应用于Unix/Linux系统中的共享库,而且在现代操作系统中也是非常有用的。

二、mmap内存映射函数的参数

mmap函数在使用时需要传递四个必要的参数,它们依次是:

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

1. addr:可选的指定映射的开始地址,通常将它设置为0,让系统自动选择一个未使用的地址作为映射的地址,这是一个好的习惯。

2. length:映射的大小,单位是字节。

3. prot:映射区域的保护方式,可以是下面几个选项中的一个或多个组合:

  • PROT_EXEC:表示可执行的代码
  • PROT_READ:表示可读
  • PROT_WRITE:表示可写
  • PROT_NONE:表示不可访问

4. flags:控制映射区域的各种属性,可以通过以下的选项进行组合:

  • MAP_PRIVATE:建立一个写入时拷贝(COW)的私有映射,不会改变原对象。
  • MAP_SHARED:对映射区域的写入数据会复制回文件

5. fd:需要映射的文件描述符。

6. offset:文件映射的偏移量,通常将它设置为0,表示从文件头开始。

三、mmap内存映射原理

mmap内存映射是基于虚拟内存进行的,它允许应用程序像访问内存一样访问磁盘上的文件,实际上是将文件的一段内容直接映射到进程地址空间的一段区域中,这导致了一系列的优点:

  • 有时不需要将整个文件都读入内存即可开始工作
  • 当需要更新文件时,不需要显式地拷贝到磁盘上
  • 不会造成page cache的污染,同时内存限制也大大增加

它的底层实现利用了虚拟内存的一个非常重要的好处,即虚拟内存能够将物理内存和交换空间(磁盘)组合在一起使用。CPU需要访问进程的某个虚拟地址时,会计算出这个地址在物理内存或者磁盘上的位置,如果在物理内存上,那么直接访问;否则就会将物理内存中不常用的页替换到磁盘上,然后再从磁盘上读入需要的页。

四、mmap内存映射内核

mmap内存映射是由操作系统内核虚拟内存系统的具体实现来支持的。具体来说,内核在mmap函数调用时,会向虚拟内存子系统发送一个请求,让它将一个虚拟内存区域映射到用户进程的地址空间中。当进程访问这个虚拟内存区域时,虚拟内存子系统会将对应的数据从文件中读入,或者将数据写回磁盘上。

这个虚拟内存区域是由用户进程管理的,称为内存映射区域。在虚拟内存系统中,被映射文件的每一个页都有多个副本,既可以在文件中存在,也可以在内存中存在。因此,当进程对一个虚拟内存区域中的某个页进行访问时,虚拟内存系统会将这个页映射到多个虚拟内存区域中,并将这些虚拟内存区域转换为物理页框,使得这些虚拟内存区域实际上都指向了同一个物理页。

五、mmap内存映射大小限制

在64位系统中,由于地址空间的大小可以达到2^64个字节,因此可以创建非常大的内存映射区域。但是在32位系统中,由于地址空间的大小只有2^32个字节,因此内存映射的大小受到了一定的限制,在一些系统中可以定义一些参数来调整限制的大小。

六、mmap内存映射 文件为空

当被映射的文件为空时,根据函数的flags参数的不同,mmap内存映射的行为也有所不同。

1. 如果标志选项包含MAP_PRIVATE,则映射区域中的内容将被初始化为0。

2. 如果标志选项包含MAP_SHARED,则映射区域的是未定义的,并且对该区域的访问会产生总线错误(bus error)。

七、mmap内存映射bus error

当mmap映射区域为空时,在对映射区域进行写入等操作时,会产生总线错误(bus error)。这是因为在尝试使用空的映射区域时,访问的内存地址无效。这时可以使用mmap函数的MAP_ANONYMOUS参数来初始化该映射区,例如:

char *ptr = mmap(NULL, sizeof(char)*512, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);

这样就可以正确初始化一个空的映射区域了。