linux内存一致性,Linux内核 文件一致性之主动一致性

与内核中很多其他函数一样,该函数是典型的三段式。首先检查参数等信息,然后调用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实现的更为苛刻。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值