使用go语言绕过page cache读写文件

有时候,我们希望我们的数据不通过page cache的缓冲直接落盘。go语言里,用参数DIRECT打开文件可以实现这一点要求。

但这样做有一个硬性的要求,就是在读写的时候,对应的数据在内存中的地址一定要满足512对齐,即首地址的2进制形式中后面至少要有9个0结尾,且数据长度为512字节的整数倍,否则读写会失败。

我们用go语言中的切片slice来验证这件事。

首先我们建立一个go语言的切片并随便赋一些值:

buf := make([]byte, 8192)
for i := 0; i < 20; i++ {
	buf[i] = byte(i)
}

我们首先尝试一下正常的读写文件过程,先不使用DIRECT参数绕开page cache。

func writeWithoutAlignmentWithoutDIRECT(buf []byte) {
	// open file
	file, err := os.OpenFile("/dev/sdb",
		os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		fmt.Printf("An error occurred with file opening or creation\n")
		return
	}
	defer file.Close()

	// write file
	fmt.Println("buffer ", unsafe.Pointer(&buf))
	fmt.Println("buffer[0] ", unsafe.Pointer(&buf[0]))
	buf2 := buf[4:516]
	fmt.Println("write with buffer ", unsafe.Pointer(&buf2[0]))

	_, err = file.WriteAt(buf2, 512)
	if err != nil {
		fmt.Println("write error ", err)
	} else {
		fmt.Println("write succeed")
	}
}

这段代码的运行结果如下:

buffer  0xc42000a2a0
buffer[0]  0xc42005a000
write with buffer  0xc42005a004
write succeed

可以看出,这个切片的地址是0xc42000a2a0,这个切片内数据的首地址是0xc42005a000,这是一个与c语言不同的地方,c语言的数据首地址即为其中数据的首地址,而go语言中,切片的地址和切片内数据首地址是不同的。

我们要写入的数据的首地址是从切片的第5个元素开始,其首地址为0xc42005a004,虽然并没有512对齐,但是由于我们没有尝试绕过page cache,所以根据WriteAt函数的返回值err可以看到,我们的写入操作是成功的。

下面我们尝试一下使用DIRECT参数打开文件,绕过page cache进行写数据操作,其他参数不变。

func writeWithoutAlignmentWithDIRECT(buf []byte) {
	// open file
	file, err := os.OpenFile("/dev/sdc",
		os.O_WRONLY|os.O_CREATE|syscall.O_DIRECT, 0666)
	if err != nil {
		fmt.Printf("An error occurred with file opening or creation\n")
		return
	}
	defer file.Close()

	// write file
	fmt.Println("buffer ", unsafe.Pointer(&buf))
	fmt.Println("buffer[0] ", unsafe.Pointer(&buf[0]))
	buf2 := buf[4:516]
	fmt.Println("write with buffer ", unsafe.Pointer(&buf2[0]))

	_, err = file.WriteAt(buf2, 512)
	if err != nil {
		fmt.Println("write error ", err)
	} else {
		fmt.Println("write succeed")
	}
}

这段代码运行后,我们可以看到如下结果。

buffer  0xc42000a2e0
buffer[0]  0xc42005a000
write with buffer  0xc42005a004
write error  write /dev/sdc: invalid argument

看到了write error,WriteAt函数这次的返回值给出的并不是nil了,我们的写入操作失败了,其返回值返回了一个不可理解的invalid argument(非法参数)。

but我们的参数毫无问题啊!下面我们尝试一下把要写入的数据改为512对齐。

func writeWithAlignmentWithDIRECT(buf []byte) {
	// open file
	file, err := os.OpenFile("/dev/sdd",
		os.O_WRONLY|os.O_CREATE|syscall.O_DIRECT, 0666)
	if err != nil {
		fmt.Printf("An error occurred with file opening or creation\n")
		return
	}
	defer file.Close()

	// write file
	fmt.Println("buffer ", unsafe.Pointer(&buf))
	fmt.Println("buffer[0] ", unsafe.Pointer(&buf[0]))
	buf2 := buf[512 : 512+512]
	fmt.Println("write with buffer ", unsafe.Pointer(&buf2[0]))

	_, err = file.WriteAt(buf2, 512)
	if err != nil {
		fmt.Println("write error ", err)
	} else {
		fmt.Println("write succeed")
	}
}

这段代码运行后,结果如下。

white with alignment and DIRECT:
buffer  0xc42000a340
buffer[0]  0xc42005a000
write with buffer  0xc42005a200
write succeed

我们的写操作成功了!而这段代码与上次未成功的不同之处只有一个,那就是将要写入数据的首地址改成了512对齐。

通过这三段go程序,我们很清晰的验证了绕过page cache写文件的条件。

类似的,下面给出验证绕过page cache读文件也需要512对齐条件的代码。

func readWithoutAlignmentWithoutDIRECT(buf []byte) {
	// read file
	file, err := os.OpenFile("/dev/sdb", os.O_RDONLY, 0666)
	if err != nil {
		fmt.Printf("An error occurred whit file ipening.\n")
		return
	}
	defer file.Close()

	buf = buf[2:514]
	fmt.Println("read with buffer ", unsafe.Pointer(&buf[0]))

	_, err = file.ReadAt(buf, 512)
	if err != nil {
		fmt.Println("read error ", err)
	} else {
		fmt.Println("read succeed", buf)
	}
}
func readWithoutAlignmentWithDIRECT(buf []byte) {
	// read file
	file, err := os.OpenFile("/dev/sdc", os.O_RDONLY|syscall.O_DIRECT, 0666)
	if err != nil {
		fmt.Printf("An error occurred whit file ipening.\n")
		return
	}
	defer file.Close()

	buf = buf[2:514]
	fmt.Println("read with buffer ", unsafe.Pointer(&buf[0]))

	_, err = file.ReadAt(buf, 512)
	if err != nil {
		fmt.Println("read error ", err)
	} else {
		fmt.Println("read succeed", buf)
	}
}
func readWithAlignmentWithDIRECT(buf []byte) {
	// read file
	file, err := os.OpenFile("/dev/sdd", os.O_RDONLY|syscall.O_DIRECT, 0666)
	if err != nil {
		fmt.Printf("An error occurred whit file ipening.\n")
		return
	}
	defer file.Close()

	buf = buf[512 : 512+512]
	fmt.Println("read with buffer ", unsafe.Pointer(&buf[0]))

	_, err = file.ReadAt(buf, 512)
	if err != nil {
		fmt.Println("read error ", err)
	} else {
		fmt.Println("read succeed", buf)
	}
}

这三个函数的运行结果分如下。

read with buffer  0xc42005a002
read succeed [4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

  

read with buffer  0xc42005a002
read error  read /dev/sdc: invalid argument

  

read with buffer  0xc42005a200
read succeed [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

可以看出,由于最初我们将切片的前20位分别赋值为0-20,其他位赋值默认值为0,第一个写入函数将buf[4:516]写入到/dev/sdb中,第二个写入函数写入失败,第三个写入函数将buf[512 : 512+512]写入到/dev/sdd,所以根据读取结果可以看出,我们的读取函数也是ok的。

最后,给出整段测试程序的完整代码。

package main

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

func main() {
	buf := make([]byte, 8192)
	for i := 0; i < 20; i++ {
		buf[i] = byte(i)
	}
	fmt.Println("----------------------------------------")
	fmt.Println("white without alignment and DIRECT:")
	writeWithoutAlignmentWithoutDIRECT(buf)
	fmt.Println("----------------------------------------")
	fmt.Println("white without alignment but with DIRECT:")
	writeWithoutAlignmentWithDIRECT(buf)
	fmt.Println("----------------------------------------")
	fmt.Println("white with alignment and DIRECT:")
	writeWithAlignmentWithDIRECT(buf)
	fmt.Println("----------------------------------------")
	fmt.Println("read without alignment and DIRECT:")
	readWithoutAlignmentWithoutDIRECT(buf)
	fmt.Println("----------------------------------------")
	fmt.Println("read without alignment but with DIRECT:")
	readWithoutAlignmentWithDIRECT(buf)
	fmt.Println("----------------------------------------")
	fmt.Println("read with alignment and DIRECT:")
	readWithAlignmentWithDIRECT(buf)
}

func writeWithoutAlignmentWithoutDIRECT(buf []byte) {
	// open file
	file, err := os.OpenFile("/dev/sdb",
		os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		fmt.Printf("An error occurred with file opening or creation\n")
		return
	}
	defer file.Close()

	// write file
	fmt.Println("buffer ", unsafe.Pointer(&buf))
	fmt.Println("buffer[0] ", unsafe.Pointer(&buf[0]))
	buf2 := buf[4:516]
	fmt.Println("write with buffer ", unsafe.Pointer(&buf2[0]))

	_, err = file.WriteAt(buf2, 512)
	if err != nil {
		fmt.Println("write error ", err)
	} else {
		fmt.Println("write succeed")
	}
}
func writeWithoutAlignmentWithDIRECT(buf []byte) {
	// open file
	file, err := os.OpenFile("/dev/sdc",
		os.O_WRONLY|os.O_CREATE|syscall.O_DIRECT, 0666)
	if err != nil {
		fmt.Printf("An error occurred with file opening or creation\n")
		return
	}
	defer file.Close()

	// write file
	fmt.Println("buffer ", unsafe.Pointer(&buf))
	fmt.Println("buffer[0] ", unsafe.Pointer(&buf[0]))
	buf2 := buf[4:516]
	fmt.Println("write with buffer ", unsafe.Pointer(&buf2[0]))

	_, err = file.WriteAt(buf2, 512)
	if err != nil {
		fmt.Println("write error ", err)
	} else {
		fmt.Println("write succeed")
	}
}
func writeWithAlignmentWithDIRECT(buf []byte) {
	// open file
	file, err := os.OpenFile("/dev/sdd",
		os.O_WRONLY|os.O_CREATE|syscall.O_DIRECT, 0666)
	if err != nil {
		fmt.Printf("An error occurred with file opening or creation\n")
		return
	}
	defer file.Close()

	// write file
	fmt.Println("buffer ", unsafe.Pointer(&buf))
	fmt.Println("buffer[0] ", unsafe.Pointer(&buf[0]))
	buf2 := buf[512 : 512+512]
	fmt.Println("write with buffer ", unsafe.Pointer(&buf2[0]))

	_, err = file.WriteAt(buf2, 512)
	if err != nil {
		fmt.Println("write error ", err)
	} else {
		fmt.Println("write succeed")
	}
}

func readWithoutAlignmentWithoutDIRECT(buf []byte) {
	// read file
	file, err := os.OpenFile("/dev/sdb", os.O_RDONLY, 0666)
	if err != nil {
		fmt.Printf("An error occurred whit file ipening.\n")
		return
	}
	defer file.Close()

	buf = buf[2:514]
	fmt.Println("read with buffer ", unsafe.Pointer(&buf[0]))

	_, err = file.ReadAt(buf, 512)
	if err != nil {
		fmt.Println("read error ", err)
	} else {
		fmt.Println("read succeed", buf)
	}
}
func readWithoutAlignmentWithDIRECT(buf []byte) {
	// read file
	file, err := os.OpenFile("/dev/sdc", os.O_RDONLY|syscall.O_DIRECT, 0666)
	if err != nil {
		fmt.Printf("An error occurred whit file ipening.\n")
		return
	}
	defer file.Close()

	buf = buf[2:514]
	fmt.Println("read with buffer ", unsafe.Pointer(&buf[0]))

	_, err = file.ReadAt(buf, 512)
	if err != nil {
		fmt.Println("read error ", err)
	} else {
		fmt.Println("read succeed", buf)
	}
}
func readWithAlignmentWithDIRECT(buf []byte) {
	// read file
	file, err := os.OpenFile("/dev/sdd", os.O_RDONLY|syscall.O_DIRECT, 0666)
	if err != nil {
		fmt.Printf("An error occurred whit file ipening.\n")
		return
	}
	defer file.Close()

	buf = buf[512 : 512+512]
	fmt.Println("read with buffer ", unsafe.Pointer(&buf[0]))

	_, err = file.ReadAt(buf, 512)
	if err != nil {
		fmt.Println("read error ", err)
	} else {
		fmt.Println("read succeed", buf)
	}
}

  

时间: 2024-12-17 04:28:14

使用go语言绕过page cache读写文件的相关文章

C语言以字符形式读写文件

一.字符读取函数 fgetc (一).函数介绍 fgetc 是 file get char 的缩写,意思是从指定的文件中读取一个字符.函数原型为: int fgetc(FILE* fp) fp 为文件指针.fgetc() 读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回 EOF 在文件内部有一个位置指针,用来指向当前读写到的位置,也就是读写到第几个字节.在打开文件时,该指针总是指向文件的第一个字节.使用fgetc函数后,该指针会向后移动一个字节,可以连续多次使用fgetc 读取多个字符

C语言学习010:fopen读写文件

在文件input.csv文件中,我们有数据如下 Apple Pear Litchis Pineapple Watermelon 现在我们将input.csv文件下的读取并写入到output.csv文件,我们会用到fopen函数 函数原型:FILE * fopen(const char * path,const char * mode) 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5

Linux系统中的Page cache和Buffer cache

Linux系统中的Page cache和Buffer cache Free命令显示内存 首先,我们来了解下内存的使用情况: Mem:表示物理内存统计 total:表示物理内存总量(total = used + free) used:表示总计分配给缓存(包含buffers 与cache )使用的数量,但其中可能部分缓存并未实际使用. free:未被分配的内存. shared:共享内存. buffers:系统分配但未被使用的buffers 数量. cached:系统分配但未被使用的cache 数量.

[C/C++语言基础]读写文件

1.打开.关闭文件: FILE* fp = fopen(string.c_str(), FLAG); string.c_str():需用C语言字符串形式: FLAG说明: r: 只读方式打开: w: 只写方式打开: a:追加方式打开: r+: 以读/写方式打开:(无文件:出错) w+: 以读/写方式打开:(无文件:新建) int fclose(FILE* fp); 成功:返回0: 失败:返回EOF: 2.读写文件: char *fgets(char *s, int n, FILE *stream

C语言顺序读写文件

if((fp=fopen(“D:\\文件.txt”,”w”))==NULL) //以写入的方式创建文件,并指定了文件的位置 fopen(文件名,使用方式) { printf(“The file can’t be open”); //不能创建则输出不能打开此文件,但一般都是可以创建的 exit(0); //exit(0)在头文件<stdlib.h> } FILE *fp //定义fp是一个指向FILE类型数据的指针变量 fgetc(fp) //从fp指向的文件读入一个字符 fputc(ch,fp

go语言读写文件的几种方式

package main import ( "bufio" "fmt" "io" "io/ioutil" "os" ) //使用ioutil读写文件 func ioutil_method(path string){ //写文件 //只能写入字节,所以需要转化一下 content := []byte("人生の半分は仕方がないでできてる.残りの半分は.帰りたい") ioutil.WriteF

Linux中的Buffer Cache和Page Cache

http://wenku.baidu.com/view/dd677d2fcfc789eb172dc868.html http://bbs.chinaunix.net/thread-3759086-1-1.html http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=4189715&extra=page%3D1%26filter%3Dauthor%26orderby%3Ddateline%26orderby%3Ddateline 了解下

文件系统学习之八:SVR4中的Page Cache

在SVR4中,所有文件的读写都会经过Page Cache.不同于有固定长度的物理cache,或者buffer cache, 或者DNLC. PageCache和其他cache的区别在于:它可以根据软件需求换进或者换出.另外也和buffer cache不同:buffer cache用设备和块号来索引cache,而page cache用vnode和offset来索引. page cache的构成 一个支持seg_map操作(比如当前段的缺页异常处理)的段: 一个具有多种用途的空闲页表链表(list)

page cache 与free

我们经常用free查看服务器的内存使用情况,而free中的输出却有些让人困惑,如下: 先看看各个数字的意义以及如何计算得到: free命令输出的第二行(Mem):这行分别显示了物理内存的总量(total).已使用的 (used).空闲的(free).共享的(shared).buffer(buffer大小). cache(cache的大小)的内存.我们知道Total.free.buffers.cached这几个字段是从/proc/meminfo中获取的,而used = total – free.S