众所周知,Linux内核采用了page cache来缓存文件数据以及元数据。既然采用缓存,就有可能会产生缓存数据与磁盘中的数据不一致的问题,本系列博客中我们重点关注Linux内核如何解决这种不一致。
一般来说,一个成熟的系统需要提供多种机制来保证数据一致性,其一是用户可控的,即用户能通过特定的接口去控制文件数据的一致性,这是对于文件数据一致性要求比较高的应用需要的语义。另一方面,某些用户或者应用程序对文件数据的一致性要求可能没有那么高,无需在每次写入的时候都调用相应的接口去保证文件缓存数据和磁盘上数据的一致性,此时,操作系统必须能够主动地承担起保证文件一致性的任务。
下面我们再来讨论下一致性的概念。操作系统中的文件除了数据外(最常见的形式就是字节流),还包含元数据,即文件=数据+元数据,元数据用来描述文件的各种属性,也必须存储在磁盘上。因此,我们说保证文件一致性其实包含了两个方面:数据一致+元数据一致。关于文件数据和元数据在内存中的缓存机制请参考我的另外一篇博客。
当前Linux下以两种方式实现文件一致性:1. 向用户提供特定接口,用户可通过接口来主动地保证文件一致性;2.系统中存在定期任务(表现形式为内核线程),周期性地同步文件系统中文件脏数据块。这两种方式各有优劣,1可保证文件数据的强一致性,但效率较为低下,每次写入必须等待落实磁盘,2克服了1的低效,但无法保证文件数据的always onsistent。
文件系统一致性接口
当前Linux主要对用户提供如下接口来保证文件数据一致性(均位于fs/sync.c文件)。
- fsync(intfd)
- fdatasync(int fd)
- sync()
fsync(fd)将fd代表的文件的脏数据和脏元数据全部刷新至磁盘中。fdatasync(fd)将fd代表的文件的脏数据刷新至磁盘,同时对必要的元数据刷新至磁盘中,这里所说的必要的概念是指:对接下来访问文件有关键作用的信息,如文件大小,而文件修改时间等不属于必要信息。sync()则是对系统中所有的脏的文件数据元数据刷新至磁盘中。本篇博客中我们讲述文件系统的主动一致性也就是详细分析上述三个接口的实现原理。
fsync(int fd)
fsync的语义是同步fd代表的文件。要实现这种同步,让我们来看看需要有哪些棘手的问题需要解决:
1. 快速定位脏缓存数据。当前Linux以页面为单位组织文件缓存,且将所有的页面组织在radix tree中,那么该问题就变为如何快速定位脏页面?
2. 如何设计一个简单高效的框架来将主动同步和系统中的刷新线程纳入同步框架之中?
本篇博客中我们不太去关注问题2,待到下一篇中我们会详细思考同步框架的设计。因此下面我们简单描述fsync的实现。追踪它的实现流程如下:
fsync(int fd)(位于fs/sync.c中)
------->do_fsync(fd, 0)(位于fs/sync.c中)
------->vfs_fsync(file, datasync)(位于fs/sync.c中)
------->vfs_fsync_range(file, 0, LLONG_MAX, datasync)(位于fs/sync.c中)
------->filemap_write_and_wait_range(mapping,start, end)(位于mm/filemap.c中)
------->__filemap_fdatawrite_range(mapping,lstart, lend,WB_SYNC_ALL)(位于mm/filemap.c中)
------->filemap_fdatawait_range(mapping,lstart,lend)(位于mm/filemap.c中)
------->ext2_fsync(struct file*file, int datasync)(针对ext2文件系统,位于fs/ext2/file.c)
------->generic_file_fsync(file, datasync)(位于fs/libfs.c中)
SYSCALL_DEFINE1(fdatasync, unsigned int,fd)
{
return do_fsync(fd, 1);
}
static int do_fsync(unsigned int fd, intdatasync)
{
struct file*file;
int ret =-EBADF;
file =fget(fd);
if (file) {
ret =vfs_fsync(file, datasync);
fput(file);