[linux] page 写磁盘并不是原子性

page 为单位,向磁盘写数据,并不是原子性的,举例说明:

  1. 写线程 writer 每次向磁盘输入一个 page 的数据量,w1 w2 w3 ...
  2. w1 w2 w3 ... 分别代表一个 page 的数据量
  3. 读线程 reader 每次从磁盘读一个 page 的数据量
  4. 操作系统并不保证 writer 写完整个 page 后,才让当前写入的数据整体对 reader 可见
  5. reader 读取一个 pape 的数据,可能部分来自 w1 部分来自 w2
    测试代码如下:
package main

import (
	"fmt"
	"hash/fnv"
	"log"
	"math/rand"
	"os"
	"sync"
	"syscall"
	"unsafe"
)

func randPage(page []byte) {
     
	size := int(unsafe.Sizeof(uint64(0)))
	uintLen := len(page) / size
	for i := 0; i < uintLen-1; i++ {
     
		dPtr := (*uint64)(unsafe.Pointer(&page[i*size]))
		*dPtr = rand.Uint64()
	}
}

func hashPage(page []byte) uint64 {
     
	hashEnd := len(page) - int(unsafe.Sizeof(uint64(0)))
	h := fnv.New64a()
	_, _ = h.Write(page[:hashEnd])
	return h.Sum64()
}

func setPage(page []byte) {
     
	randPage(page)
	hashEnd := len(page) - int(unsafe.Sizeof(uint64(0)))
	dPtr := (*uint64)(unsafe.Pointer(&page[hashEnd]))
	*dPtr = hashPage(page)

}

func main() {
     
	pageFile := "/tmp/page.db"
	if _, err := os.Stat(pageFile); err == nil {
     
		os.Remove(pageFile)
	}

	pageSize := syscall.Getpagesize()
	fmt.Printf("page size = %d \n", pageSize)
	fd, err := os.OpenFile(pageFile, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
     
		log.Fatal(err)
	}
	defer fd.Close()

	ref, err := syscall.Mmap(int(fd.Fd()), 0, pageSize, syscall.PROT_READ, syscall.MAP_SHARED)
	if err != nil {
     
		log.Fatal(err)
	}
	defer syscall.Munmap(ref)

	page := make([]byte, pageSize)
	setPage(page)
	if n, err := fd.WriteAt(page, 0); n != pageSize || err != nil {
     
		log.Fatal(err)
	}
	if err := syscall.Fsync(int(fd.Fd())); err != nil {
     
		log.Fatal(err)
	}
	ch := make(chan struct{
     })
	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
     
		defer wg.Done()
		hashEnd := pageSize - int(unsafe.Sizeof(uint64(0)))
		dPtr := (*uint64)(unsafe.Pointer(&ref[hashEnd]))
		ret := false
		cnt := 0
		var initHash uint64
		initHash = 0
		for {
     
			if ret {
     
				log.Printf("read count = %d\n", cnt)
				return
			}
			h1 := hashPage(ref)
			if initHash == h1 {
     
				log.Printf("same hash value, i = %x, h = %x\n", initHash, h1)
			}
			initHash = h1
			if *dPtr != h1 {
     
				log.Fatalf("check sum error, h = %x, d = %x\n", h1, *dPtr)
			}
			select {
     
			case <-ch:
				ret = true
			default:
				cnt++
			}
		}
	}()

	wg.Add(1)
	go func() {
     
		defer wg.Done()
		for i := 0; i < 100000; i++ {
     
			setPage(page)
			if n, err := fd.WriteAt(page, 0); n != pageSize || err != nil {
     
				log.Fatal(err)
			}
			if err := syscall.Fsync(int(fd.Fd())); err != nil {
     
				log.Fatal(err)
			}
		}
		ch <- struct{
     }{
     }
	}()
	wg.Wait()

}

程序输出结果如下:

2020/08/28 10:51:24 same hash value, i = fa854bd3b8edaf60, h = fa854bd3b8edaf60
2020/08/28 10:51:24 check sum error, h = 2202313290c04b15, d = d82723c176e773c1
exit status 1

你可能感兴趣的:(go,Linux)