介绍

mmap并不是什么新技术,早在4.2BSD的系统说明中就已经出现了mmap相关函数,并且mmap属于POSIX标准。在一众开源软件中都使用到了mmap,如Etcd、InfluxDB、RocketMQ等。

想要理解MMAP,就不得不说到** 虚拟内存(Virtual Memory,VM)**

虚拟内存 VM

虚拟内存是现代操作系统重要的一部分,它具备3个重要的能力:

  • 把内存用做硬盘的高速缓存快速交换数据
  • 为每个进程提供一致的地址空间,简化内存管理
  • 保护每个进程的地址空间不被其它进程破坏

为了让每个进程都有一致的地址空间,引入虚拟内存空间的概念,由虚拟内存映射至物理内存,进程就无需关心数据存放在物理内存哪个位置。CPU通过虚拟地址访问内存,由CPU的**内存管理单元(Memory Management Unit,MMU)**负责将虚拟地址翻译为物理地址。

虚拟内存按一定大小分割为页——虚拟页(Virtual Page,VP)。n位虚拟地址分为两部分,p位虚拟地址偏移量和(n-p)位的虚拟页号(Virtual Page Number,VPN),p由虚拟页大小决定。为了将虚拟地址翻译为物理地址,每个进程都有独立的 页表(Page Table,PT),由 页表条目(Page Table Entry,PTE) 组成的数组,每条PTE记录了虚拟页号 ->物理页号(Physical Page Number,PPN)/硬盘地址 的映射关系,就好像字典的目录一样。MMU翻译时会读取页表,由操作系统负责维护页表内容。

mmap() 就是将虚拟地址映射至硬盘上的对象,当CPU首次请求这部分虚拟地址时,发现有效位为0即缺页,触发 页面调度(paging),将页从磁盘换入内存。如果页被修改则变为脏页,操作系统控制将脏页写回硬盘。

Golang实现mmap调用

Golang使用系统调用的时候,需要注意 syscall 已经被弃用,替代的库为 golang.org/x/sys

 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
package main

import (
	"bytes"
	"fmt"
	"os"
	"strings"

	"golang.org/x/sys/unix"
)

const FILENAME = "test.mmap"

func main() {
	pagesize := os.Getpagesize()

	file, _ := os.OpenFile(FILENAME, os.O_RDWR|os.O_CREATE, 0644)

	state, _ := file.Stat()

	if state.Size() == 0 {
		_, _ = file.WriteAt(bytes.Repeat([]byte{'0'}, pagesize), 0)

		state, _ = file.Stat()
	}

	fmt.Printf("pagesize: %d\n filesize: %d\n", pagesize, state.Size())

	data, _ := unix.Mmap(int(file.Fd()), 0, int(state.Size()), unix.PROT_WRITE, unix.MAP_SHARED)

	// 关闭文件,不影响mmap
	file.Close()

	//for i := 0; i < 8; i++ {
	//data[i] = '1'
	//}

	for i, x := range strings.Split("hello world", "") {
		data[i] = []byte(x)[0]  // string 转 byte ,用[]byte(*)[index]的方式
		//fmt.Println(i, x)
	}

	unix.Munmap(data)
}

参考