mmap

发布时间 2023-08-30 18:23:09作者: INnoVation-V2

mmap

Memory Map

1.mmap使用方法

func Mmap(fd int, offset int64, length int, prot int, flags int) ([]byte, error)
  1. fd:文件描述符

  2. offset:文件映射的起始偏移量。通常情况下,可以设置为0表示从文件的开头开始映射

  3. length:映射的字节数。一般可以设置为文件大小,但不得超过文件的实际大小

  4. prot(protection mode保护模式):控制着内存区域的访问权限。它是一个整数,可以通过按位或组合多个常量来实现不同的保护模式。以下是一些常见的prot常量:

    syscall.PROT_NONE:不允许访问,相当于内存区域被禁用。
    syscall.PROT_READ:允许读取访问。
    syscall.PROT_WRITE:允许写入访问。
    syscall.PROT_EXEC:允许执行访问。
    

    例如, 创建一个允许读写但不允许执行的内存映射:

    syscall.PROT_READ | syscall.PROT_WRITE
    
  5. .flags: 用于设置内存映射的标志,以指定映射的类型和行为。它是一个整数,可以通过按位或组合多个常量来设置不同的标志。以下是一些常见的flags常量:

    syscall.MAP_PRIVATE:私有映射,映射的内存只能被当前进程访问。
    syscall.MAP_FIXED:指定固定的映射地址。
    syscall.MAP_ANONYMOUS:创建匿名映射,不与任何文件关联。
    syscall.MAP_SHARED:共享映射,多个进程可以共享同一块内存。
    

    例如,创建一个共享的映射

    syscall.MAP_SHARED
    

2.mmap解决了什么问题

先来看看传统读取文件的流程

2.1 传统读取文件的流程

  1. 打开文件:使用 open 函数打开文件,获取文件描述符。
  2. 申请内存:使用 mallocnew 函数申请内存缓冲区。
  3. 读取数据:使用 read 函数从文件中读取数据到内存缓冲区。
  4. 处理数据:对内存缓冲区中的数据进行处理操作。
  5. 释放内存:使用 free 函数释放内存缓冲区。
  6. 关闭文件:使用 close 函数关闭文件描述符。

代码

func main() {
	// 1使用 `open` 函数打开文件,获取文件描述符。
	file, err := os.Open("data.txt")
	if err != nil {
		log.Fatal(err)
	}
  // 关闭文件
	defer file.Close()

	// 获取文件大小
	fileInfo, err := file.Stat()
	if err != nil {
		log.Fatal(err)
	}
	fileSize := int(fileInfo.Size())

	// 申请内存缓冲区
	buffer := make([]byte, fileSize)

	// 读取数据到内存缓冲区
	_, err = file.Read(buffer)
	if err != nil {
		log.Fatal(err)
	}

	// 处理数据(这里假设 data.txt 是文本文件)
	fmt.Println(string(buffer))
}

2.2 mmap 读取文件的流程

  1. 打开文件:使用 open 函数打开文件,获取文件描述符。
  2. 获取文件大小:使用 fstat 函数获取文件的大小。
  3. 映射文件到内存:使用 mmap 函数将文件内容映射到内存。
  4. 访问数据:直接访问内存中的映射数据,无需额外的读取函数。
  5. 解除内存映射:使用 munmap 函数解除内存映射。
  6. 关闭文件:使用 close 函数关闭文件描述符。

代码

package main

import (
	"fmt"
	"log"
	"os"
	"syscall"
)

func main() {
	// 打开文件
	file, err := os.Open("data.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	// 获取文件大小
	fileInfo, err := file.Stat()
	if err != nil {
		log.Fatal(err)
	}
	fileSize := int(fileInfo.Size())

	// 映射文件到内存
	data, err := syscall.Mmap(int(file.Fd()), 0, fileSize, syscall.PROT_READ, syscall.MAP_SHARED)
	if err != nil {
		log.Fatal(err)
	}
	defer syscall.Munmap(data)

	// 访问数据(这里假设 data.txt 是文本文件)
	fmt.Println(string(data))
}

3.mmap的优势

  1. 零拷贝(Zero-Copy):传统读取方法需要在内核缓冲区和应用程序内存之间进行多次数据拷贝,而 mmap 可以直接将文件数据映射到应用程序内存中,避免了这些数据拷贝操作,从而减少了CPU和内存的开销,提高了读取速度。
  2. 内存映射:通过 mmap,文件数据被映射到内存中的一页页内存区域。这使得操作系统可以更有效地管理这些内存页,以适应操作系统的虚拟内存管理机制,从而提高读取操作的效率。
  3. 延迟加载(Lazy Loading)mmap 支持懒加载机制,只有当应用程序访问内存中的数据时,数据才会从磁盘加载到内存。这意味着在文件打开后,不是立即将整个文件加载到内存中,而是根据实际需求逐页加载,从而减少了初始化时的开销。
  4. 随机访问mmap 允许应用程序通过内存地址进行随机访问,而传统的读取方法需要在每次随机访问时都进行文件定位和读取操作,这些操作的开销相对较大。

下面详细说一下零拷贝

传统的读取方法拷贝流程如下

  1. 文件数据拷贝到内核缓冲区:使用传统的读取方法时,文件数据首先被读取到操作系统内核的缓冲区中。这个过程涉及将文件数据从存储设备(如硬盘)读取到内核中的内存缓冲区中。
  2. 内核缓冲区拷贝到应用程序内存:一旦数据从文件读取到了内核缓冲区,应用程序需要通过系统调用(如 read 函数)将数据从内核缓冲区复制到应用程序的内存中。

mmap直接将文件映射到进程的页表中,当读取发生缺页时,就直接将文件读出,放入进程中供使用,减少了多次拷贝的开销