使用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)
        }
}