標籤:賦值 return file 看到了 port 問題 Go語言 pointer 地址
有時候,我們希望我們的資料不通過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 filefile, 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 filefmt.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 0xc42000a2a0buffer[0] 0xc42005a000write with buffer 0xc42005a004write succeed
可以看出,這個切片的地址是0xc42000a2a0,這個切片內資料的首地址是0xc42005a000,這是一個與c語言不同的地方,c語言的資料首地址即為其中資料的首地址,而go語言中,切片的地址和切片內資料首地址是不同的。
我們要寫入的資料的首地址是從切片的第5個元素開始,其首地址為0xc42005a004,雖然並沒有512對齊,但是由於我們沒有嘗試繞過page cache,所以根據WriteAt函數的傳回值err可以看到,我們的寫入操作是成功的。
下面我們嘗試一下使用DIRECT參數開啟檔案,繞過page cache進行寫資料操作,其他參數不變。
func writeWithoutAlignmentWithDIRECT(buf []byte) {// open filefile, 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 filefmt.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 0xc42000a2e0buffer[0] 0xc42005a000write with buffer 0xc42005a004write error write /dev/sdc: invalid argument
看到了write error,WriteAt函數這次的傳回值給出的並不是nil了,我們的寫入操作失敗了,其傳回值返回了一個不可理解的invalid argument(非法參數)。
but我們的參數毫無問題啊!下面我們嘗試一下把要寫入的資料改為512對齊。
func writeWithAlignmentWithDIRECT(buf []byte) {// open filefile, 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 filefmt.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 0xc42000a340buffer[0] 0xc42005a000write with buffer 0xc42005a200write succeed
我們的寫操作成功了!而這段代碼與上次未成功的不同之處只有一個,那就是將要寫入資料的首地址改成了512對齊。
通過這三段go程式,我們很清晰的驗證了繞過page cache寫檔案的條件。
類似的,下面給出驗證繞過page cache讀檔案也需要512對齊條件的代碼。
func readWithoutAlignmentWithoutDIRECT(buf []byte) {// read filefile, 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 filefile, 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 filefile, 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 0xc42005a002read 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 0xc42005a002read error read /dev/sdc: invalid argument
read with buffer 0xc42005a200read 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 mainimport ("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 filefile, 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 filefmt.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 filefile, 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 filefmt.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 filefile, 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 filefmt.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 filefile, 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 filefile, 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 filefile, 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)}}
使用go語言繞過page cache讀寫檔案