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

Posted adream307

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[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

以上是关于[linux] page 写磁盘并不是原子性的主要内容,如果未能解决你的问题,请参考以下文章

管道的原子性 linux写操作原子性

undolog实现事务原子性,redolog实现事务的持久性

sqlite原子提交原理

Linux读写缓存Page Cache

Linux读写缓存Page Cache

在linux的用户空间,有没有原子操作的函数