与内核中很多其他函数一样,该函数是典型的三段式。首先检查参数等信息,然后调用filemap_write_and_wait_range()同步文件脏数据。等文件脏数据写入完成以后,调用具体文件系统的fsync方法,该方法主要是回写文件元数据,即inode信息。在此之前,需要对inode加锁,即mutex_lock。
点击(此处)折叠或打开
int filemap_write_and_wait_range(structaddress_space *mapping,loff_t lstart, loff_t lend)
{
int err = 0;
if (mapping->nrpages) {
err = __filemap_fdatawrite_range(mapping,lstart, lend,WB_SYNC_ALL);
/* See comment of filemap_write_and_wait() */
if (err != -EIO) {
int err2 =filemap_fdatawait_range(mapping,lstart, lend);
if (!err)
err = err2;
}
}
return err;
}
该函数主要作用是回写文件的脏页面并等待脏页面写入完成。参数lstart,lend分别表示文件要回写的脏页面范围。该过程主要调用了两个函数 __filemap_fdatawrite_range和filemap_fdatawait_range, __filemap_fdatawrite_range找到位于偏移范围内的脏页面,并将页面写入磁盘相应位置处,其中WB_SYNC_ALL表示回写的模式,表示本次回写是文件完整性回写,不同于为了内存紧张时回收页面而进行的回写。接下来,调用 filemap_fdatawait_range 等待上面的写页面全部完成,因为上面的函数只是发出写页面请求,而完整性回写必须要确保所有的页面回写完成才可返回。因此,filemap_fdatawait_range等待在所有上面已经发出的请求的页面上,发请求时给页面加上了PG_Writeback,到页面回写完成以后才会清除该标志位,而filemap_fdatawait_range等待在所有文件正回写页面的该标志位,直到该标志位被清除该函数才返回,意味着脏页面的回写已经完成,可以进行接下来的工作了。具体的页面写入的工作我们会在别的文章中仔细讨论。
在文件的所有脏页面写入完成以后,接下来需要同步文件元数据即inode结构,因为此时需要对inode进行独占,因此在这之前需要对inode进行加锁,接下来调用file->f_op->fsync(),对于ext2文件系统来说,该方法被实例化为ext2_fsync。
点击(此处)折叠或打开
int ext2_fsync(struct file *file, int datasync)
{
int ret;
structsuper_block *sb = file->f_mapping->host->i_sb;
structaddress_space *mapping = sb->s_bdev->bd_inode->i_mapping;
ret =generic_file_fsync(file, datasync);
if (ret == -EIO ||test_and_clear_bit(AS_EIO, &mapping->flags)) {
/*We don't really know where the IO error happened... */
ext2_error(sb,__func__,"detected IO error when writing metadata buffers");
ret= -EIO;
}
return ret;
}
这里主要调用通用函数generic_file_fsync来完成接下来的同步工作。
int generic_file_fsync(struct file *file,int datasync)
{
structwriteback_control wbc = {
.sync_mode= WB_SYNC_ALL,
.nr_to_write= 0, /* metadata-only; caller takes care of data */
};
struct inode*inode = file->f_mapping->host;
int err;
int ret;
//what does it do?
ret =sync_mapping_buffers(inode->i_mapping);
if(!(inode->i_state & I_DIRTY))
return ret;
if (datasync&& !(inode->i_state & I_DIRTY_DATASYNC))
return ret;
err =sync_inode(inode, &wbc);
if (ret == 0)
ret = err;
return ret;
}
该函数中主要完成两个任务:1.将associate mapping中的private中的buffer_head中的block数据同步至磁盘中,暂时还不清楚该buffer中保存的数据是什么,不过初步猜测可能保存文件数据间接块。2.步骤1完成后,将inode同步至磁盘中,现在我们主要关注sync_inode函数。
传递给该函数的参数有2:参数1. inode代表要写入的inode结构,参数2. wbc表示控制写入的参数,如在调用者中设置了以下几个控制参数:
struct writeback_control wbc = {
.sync_mode = WB_SYNC_ALL,//表示数据完整性写入,一定等到同步完成才可以返回
.nr_to_write = 0, //要写入的页面数,因为目前文件的所有脏页面已经全部同步,因此在回写inode的时候没有必要再同步文件脏数据页面
};
点击(此处)折叠或打开
int sync_inode(struct inode *inode, structwriteback_control *wbc)
{
int ret;
spin_lock(&inode_lock);
ret =writeback_single_inode(inode, wbc);
spin_unlock(&inode_lock);
return ret;
}
staticint writeback_single_inode(struct inode *inode, struct writeback_control*wbc)
{
structaddress_space *mapping = inode->i_mapping;
unsigned dirty;
int ret;
if(!atomic_read(&inode->i_count))
WARN_ON(!(inode->i_state& (I_WILL_FREE|I_FREEING)));
else
WARN_ON(inode->i_state& I_WILL_FREE);
if (inode->i_state& I_SYNC) {
/*
* If thisinode is locked for writeback and we are not doing
*writeback-for-data-integrity, move it to b_more_io so that
* writebackcan proceed with the other inodes on b_io.
*
* We'll haveanother go at writing back this inode when we
* completeda full scan of b_io.
*/
if(wbc->sync_mode != WB_SYNC_ALL) {
requeue_io(inode);
return0;
}
/*
* It's adata-integrity sync. We must wait for this inode to be synced.
*/
inode_wait_for_writeback(inode);
}
BUG_ON(inode->i_state& I_SYNC);
/* Set I_SYNC,reset I_DIRTY_PAGES */
inode->i_state|= I_SYNC;
inode->i_state&= ~I_DIRTY_PAGES;
spin_unlock(&inode_lock);
ret =do_writepages(mapping, wbc);
/*
* Make sureto wait on the data before writing out the metadata.
* This isimportant for filesystems that modify metadata on data
* I/Ocompletion.
*/
if(wbc->sync_mode == WB_SYNC_ALL) {
interr = filemap_fdatawait(mapping);
if(ret == 0)
ret= err;
}
/*
* Somefilesystems may redirty the inode during the writeback
* due todelalloc, clear dirty metadata flags right before
*write_inode()
*/
spin_lock(&inode_lock);
dirty =inode->i_state & I_DIRTY;
inode->i_state&= ~(I_DIRTY_SYNC | I_DIRTY_DATASYNC);
spin_unlock(&inode_lock);
/* Don't write theinode if only I_DIRTY_PAGES was set */
if (dirty & (I_DIRTY_SYNC| I_DIRTY_DATASYNC)) {
int err =write_inode(inode, wbc);
if (ret == 0)
ret= err;
}
spin_lock(&inode_lock);
inode->i_state&= ~I_SYNC;
if(!(inode->i_state & I_FREEING)) {
if(mapping_tagged(mapping, PAGECACHE_TAG_DIRTY)) {
/*
* We didn'twrite back all the pages. nfs_writepages()
* sometimesbales out without doing anything.
*/
inode->i_state|= I_DIRTY_PAGES;
if(wbc->nr_to_write <= 0) {
/*
* slice usedup: queue for next turn
*/
requeue_io(inode);
} else {
/*
* Writebackblocked by something other than
*congestion. Delay the inode for some time to
* avoidspinning on the CPU (100% iowait)
* retryingwriteback of the dirty page/inode
* thatcannot be performed immediately.
*/
redirty_tail(inode);
}
} else if(inode->i_state & I_DIRTY) {
/*
*Filesystems can dirty the inode during writeback
*operations, such as delayed allocation during
* submissionor metadata updates after data IO
*completion.
*/
redirty_tail(inode);
} else if(atomic_read(&inode->i_count)) {
list_move(&inode->i_list,&inode_in_use);
} else {
list_move(&inode->i_list, &inode_unused);
}
}
inode_sync_complete(inode);
return ret;
}
该函数的主要作用是向磁盘中写回脏的inode。每次在写入之前需要判断该inode是否正被写入,inode被写入之前会设置标志位I_SYNC,因此,只需对inode判断该标志位是否被设置即可。如果该标志位已被设置,说明别的内核线程/任务正在写回该inode,此时我们需要根据当前写的类型来决定下一步动作,如果只是为了内存回收而写回文件脏数据,那么只需将该inode添加到一个额外的链表中,直接返回;而如果是为了数据完整性而进行的回写(wbc->sync_mode == WB_SYNC_ALL),我们必须等待该回写任务完成才能进行接下来的处理,调用inode_wait_for_writeback(inode)等待在该inode的I_SYNC标志位上。
上述判断完成并执行以后,接下来就需要进行inode回写了,回写涉及两个任务,第一写回wbc中预先设置好的脏页面数,因为我们目前看到调用者将wbc_nr_pages设置为0,那么此时并不需要写回任何脏页面(因为脏页面已经被写回过了)。当然,在写回脏inode之前需要设置inode相应的标志位,如设置好inode的I_SYNC标志,然后写回inode代表的文件脏页面。
脏页面写回以后,接下来判断是否需要写回inode,if (dirty & (I_DIRTY_SYNC |I_DIRTY_DATASYNC))为真,表示该inode为脏,为什么这样就表示inode为脏呢,我们就需要弄清楚这两个标志位的含义,根据代码注视:
· I_DIRTY_SYNC代表inode被弄脏,但这种弄脏并不是由文件数据被修改而导致的,典型的如文件的访问时间被修改,此时文件数据没有被修改;
· I_DIRTY_DATASYNC表示由于文件数据被修改而导致文件inode变脏;
· I_DIRTY_PAGES表示仅仅文件数据被修改,但并未导致文件inode被改动,此时是不需要同步文件元数据的。
之所以为inode设置这么多标志位是从效率方面来考虑的,当我们不需要同步文件inode的时候,尽量不同步。如仅当I_DIRTY_PAGES被设置时,我们是无需去同步inode的。同时在fdata_sync调用中,如果仅仅I_DIRTY_SYNC被设置,此时亦是无需同步inode的。
因此if (dirty & (I_DIRTY_SYNC | I_DIRTY_DATASYNC))为真意味着该inode确实被修改过,调用函数write_inode(最终是调用具体文件系统的写回inode方法),注意该函数是同步的,即从该函数返回意味着inode已经写回或者在写入过程中出错,因此接下来的任务就是清除inode的I_SYNC标志位(inode->i_state &= ~I_SYNC)并唤醒阻塞在该标志位上的所有进程(inode_sync_complete(inode))。
但在修改inode标志位之前,我们需要获取inode_lock,在写入inode的过程中是不持有该锁的,写入inode完成修改inode的i_state的时候必须要持有该锁以实现串行修改。获取锁的过程中其他进程可能会修改inode的i_state,因此必须在获取锁以后作一个判断:
if (mapping_tagged(mapping,PAGECACHE_TAG_DIRTY)),即如果该文件尚有脏页面没有被写回,需要为inode重新设置I_DIRTY_PAGES。inode->i_state |= I_DIRTY_PAGES。同时根据本次写回任务的参数来决定inode的处理过程。
如果1不成立,即文件所有的脏页面已被写回,但别的进程可能已经修改inode,(inode->i_state & I_DIRTY) ,此时也需要对inode添加到相应的脏链表中。
如果1,2都不成立,也即意味着没有别的进程修改过inode,此时需要根据inode的引用计数决定将inode添加到inode_in_use或inode_unused链表中。
以上便是fsync的主要流程,可以看到,整体上的结构非常清楚,同步脏数据页面到同步脏inode。但在每一步的实现时候又是危机四伏,需要考虑的细节问题太多。
fdatasync(int fd)
fdatasync()的语义和fsync颇为接近,均是将文件脏数据页面写回磁盘上,他们的区别在于是否同步文件inode。参数fd亦代表需要同步文件的文件描述符。我们追踪fdatasync的实现流程:
fdatasync(intfd)(位于fs/sync.c中)
------->do_fsync(fd, 1)(位于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中)
对比它与fsync的函数流程可发现它们的处理路径完全相同,均是先刷新脏的缓存页面,在这就不赘述了,它们唯一的区别在于datasync这个参数的设置,fsync将其设置为0,而fdatasync的实现中将其设置为1。在函数generic_file_fsync(file, datasync)中会对该参数的设置做出判断。
点击(此处)折叠或打开
int generic_file_fsync(struct file *file,int datasync)
{
structwriteback_control wbc = {
.sync_mode= WB_SYNC_ALL,
.nr_to_write= 0, /* metadata-only; caller takes care of data */
};
struct inode*inode = file->f_mapping->host;
int err;
int ret;
//what does it do?
ret =sync_mapping_buffers(inode->i_mapping);
if(!(inode->i_state & I_DIRTY))
returnret;
if (datasync&& !(inode->i_state & I_DIRTY_DATASYNC))
returnret;
err =sync_inode(inode, &wbc);
if (ret == 0)
ret= err;
return ret;
}
可以发现,在同步完成mapping_buffers以后,在决定是否需要同步inode时会判断datasync参数:
若inode并没有被修改,即I_DIRTY没有被修改,此时无需同步inode;
若inode未被设置I_DIRTY_DATASYNC且datasync被设置,这意味着调用者只想在I_DIRTY_DATASYNC被设置时(inode关键部分被修改,如文件大小信息)才去同步inode,如果I_DIRTY_DATASYNC未被设置,也就意味着inode关键成员未作修改,此时无需同步,直接返回。
因此,对比fsync和fdatasync的实现我们发现,fdatasync仅刷新了脏页面以及必要时刷新inode,而fsync实现的更为苛刻。