实现写文件的原子性

实现写文件的原子性

最近在阅读docker源码时发现保存container configuration的时候用到atomicFileWriter把写文件变成原子操作,源码

实现步骤

  1. 使用ioutil.TempFile新建一个临时文件
  2. 跟平时一样使用Write写入文件
  3. 需要 close 文件时先调用sync函数保证数据落盘,然后才真正 close 文件,修改权限等,最后利用系统调用os.Rename

为什么可以这样做

第一步首先要知道TempFile是怎么实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func TempFile(dir, prefix string) (f *os.File, err error) {
if dir == "" {
dir = os.TempDir()
}

nconflict := 0
for i := 0; i < 10000; i++ {
name := filepath.Join(dir, prefix+nextSuffix())
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if os.IsExist(err) {
if nconflict++; nconflict > 10 {
randmu.Lock()
rand = reseed()
randmu.Unlock()
}
continue
}
break
}
return
}

主要是os.O_EXCL参数,作用是保证os.O_CREATE create 文件之前,保证文件不存在才创建。
for 循环是使用前缀加随机数创建临时文件保证没有冲突,重试10000次都冲突返回错误。

对于后面几步,首先要知道 inode
维基百科的定义

The inode is a data structure in a Unix-style file system that describes a filesystem object such as a file or a directory “Directory (computing)”). Each inode stores the attributes and disk block location(s) of the object’s data.[1] Filesystem object attributes may include metadata (times of last change,[2] access, modification), as well as owner and permission data.[3]

inode 是unix类文件系统中的数据结构,用于描述文件系统对象,例如文件或者目录。每一个inode都存储着对象的属性和数据存储在磁盘块的位置。文件系统对象属性可能包含了元信息,拥有者和权限。

rm

当我们使用rm删除文件时,首先会删除文件名对这个 inode 的链接,如果 inode 链接计数器为零并且没有进程占用,那么就会删除这个文件(旧数据会存在磁盘,只是没有inode记录)。rm 命令底层使用unlink实现的。unlink 删除的是符号链接本身,而不是其指向的文件。

rename

当我们使用 rename 时,实际上是删除旧的 inode 节点,然后新建一个一样的,底层数据没有被删除。

总结

docker源码中实现的atomicFileWriter,如果文件已经被读进程打开然后被写进程调用rename,那么读进程还是可以通过文件描述符读取到“旧”的文件,但是新的文件路径已经指向新的 inode,并且可以读到新的数据。