SimpleClearFileIO icon indicating copy to clipboard operation
SimpleClearFileIO copied to clipboard

mmap 代码补充

Open wathenjiang opened this issue 4 years ago • 5 comments

mmap 机制可以很大程度上简化磁盘文件操作,通过一定的封装,磁盘上的文件读写操作可以简化为内存的读写操作。 (1)直接读写内存-简洁明了

简单地读写内存非常简单,例如下面向数组的第三个元素进行赋值:

a := make([]int, 10)
a[0] = 511

内存中字段的读写操作相当自然,你甚至可能没有注意到在写内存这件事情。

(2)读写磁盘文件-步骤复杂

但是读写磁盘上的文件就相对复杂很多了,例如我们要在磁盘上读写的文件内容如下:

Hello World! This project is for learning mmap.

如果我们要读取文件开始 6 个偏移量后的 5 个字节数据,那么如下代码是一个方式:

package main

import (
	"fmt"
	"os"
)

func main() {
	f, _ := os.Open("./article.txt")
	defer f.Close()
	buffer := make([]byte, 5)
	f.Seek(6, 0)
	_, _ = f.Read(buffer)
  fmt.Println(string(buffer)) // 输出:world
}

读取磁盘上文件对比直接读取内存麻烦很多,步骤如下:

  • 打开文件,并且注意文件资源的及时释放
  • 创建一个 []byte 数组,用于存储从磁盘上读取的字节
  • 利用 Seek 方法设置文件读取的偏移量
  • 利用 Read 方法读取文件中的字节数据到 []byte 数组中

磁盘上文件读写比内存读写麻烦很多的原因就是 CPU 只能读写内存,而不能直接读写磁盘。磁盘文件上的字节数据需要首先读取到内存中,才能够被内存操作。

(3)mmap-读写磁盘文件就像读写内存

mmap 会负责将磁盘上不一定连续存储的数据按 block 读取到内核空间中,内核空间中的磁盘数据是连续的。然后,将内核空间中的数据映射到用户空间中,这个过程不涉及拷贝。

利用 mmap 机制,操作用户空间中的数据相当于操作磁盘上文件,它们之间的一致性依赖于缺页异常以及异步的 flush 机制实现。

案例代码如下:

package main

import (
	"fmt"
	"os"
	"syscall"
	"unsafe"
)

const defaultMaxFileSize = 1 << 30        // 假设文件最大为 1G
const defaultMemMapSize = 128 * (1 << 20) // 假设映射的内存大小为 128M

type Demo struct {
	file    *os.File                  // 文件
	data    *[defaultMaxFileSize]byte // 该数组负责指向 mmap 读取数据的 []byte 首地址
	dataRef []byte                    // 引用了 Mmmap 调用返回的 []byte 数组
}

// 根据 condition 状态位进行断言
func _assert(condition bool, msg string, v ...interface{}) {
	if !condition {
		panic(fmt.Sprintf(msg, v...))
	}
}

func (demo *Demo) mmap() {
	b, err := syscall.Mmap(int(demo.file.Fd()), 0, defaultMemMapSize, syscall.PROT_WRITE|syscall.PROT_READ, syscall.MAP_SHARED)
	_assert(err == nil, "failed to mmap", err)
	demo.dataRef = b
	demo.data = (*[defaultMaxFileSize]byte)(unsafe.Pointer(&b[0]))
}

// grow 的作用是尝试将文件扩容至 size 大小,如果已经大于 size,那么不做任何操作
func (demo *Demo) grow(size int64) {
	if info, _ := demo.file.Stat(); info.Size() >= size {
		return
	}
	_assert(demo.file.Truncate(size) == nil, "failed to truncate")
}

// 资源释放
func (demo *Demo) munmap() {
	_assert(syscall.Munmap(demo.dataRef) == nil, "failed to munmap")
	demo.data = nil
	demo.dataRef = nil
}

func main() {
	_ = os.Remove("tmp.txt")
	// open file
	f, _ := os.OpenFile("tmp.txt", os.O_CREATE|os.O_RDWR, 0644) // 644 只有拥有者有读写权限;而属组用户和其他用户只有读权限
	demo := &Demo{file: f}
	demo.grow(1)
	// mmap
	demo.mmap()
	defer demo.munmap()
	// write into file on the disk like write into array in memory
	str := "helloworld"
	demo.grow(int64(len(str)))
	for i := 0; i < len(str); i++ {
		demo.data[i] = str[i]
	}
}

案例来源说明:Go Mmap 文件内存映射简明教程

wathenjiang avatar Oct 29 '21 07:10 wathenjiang

您好 您这个系列的Mysql的零拷贝技术好像看不到了

jingxuantju avatar Jun 21 '22 14:06 jingxuantju

您好 您这个系列的 Mysql 的零拷贝技术好像看不到了

你能给出一个 URL 吗?我看看问题出在哪里。

wathenjiang avatar Jun 21 '22 14:06 wathenjiang

您好 您这个系列的 Mysql 的零拷贝技术好像看不到了

你能给出一个 URL 吗?我看看问题出在哪里。

https://spongecaptain.cool/zerocopyofmysql

也就是这一篇文章的底部的引用的第8个文章https://spongecaptain.cool/SimpleClearFileIO/2.%20DMA%20%E4%B8%8E%E9%9B%B6%E6%8B%B7%E8%B4%9D%E6%8A%80%E6%9C%AF.html

jingxuantju avatar Jun 21 '22 14:06 jingxuantju

您好 您这个系列的 Mysql 的零拷贝技术好像看不到了

你能给出一个 URL 吗?我看看问题出在哪里。

https://spongecaptain.cool/zerocopyofmysql

也就是这一篇文章的底部的引用的第 8 个文章 https://spongecaptain.cool/SimpleClearFileIO/2.%20DMA%20%E4%B8%8E%E9%9B%B6%E6%8B%B7%E8%B4%9D%E6%8A%80%E6%9C%AF.html

谢谢。 已经修正。 页面刷新后应该能路由至正确的文章。

wathenjiang avatar Jun 21 '22 15:06 wathenjiang

您好 您这个系列的 Mysql 的零拷贝技术好像看不到了

你能给出一个 URL 吗?我看看问题出在哪里。

https://spongecaptain.cool/zerocopyofmysql 也就是这一篇文章的底部的引用的第 8 个文章 https://spongecaptain.cool/SimpleClearFileIO/2.%20DMA%20%E4%B8%8E%E9%9B%B6%E6%8B%B7%E8%B4%9D%E6%8A%80%E6%9C%AF.html

谢谢。 已经修正。 页面刷新后应该能路由至正确的文章。

您客气了。我阅读了您大部分博客的文章,感觉质量都很高,很深入,希望您多更新,也希望您有空可以介绍一下自己的学习方式。

jingxuantju avatar Jun 21 '22 15:06 jingxuantju