[基于2.6.8内核]
这两个函数在do_sync的时候都要调用两次,这是为何?sync_filesystems这个函数的作用是什么?do_sync在什么时候会被调用?
对于第一个问题,调用两次其实就是一个分类,do_sync的实现如下:
static void do_sync(unsigned long wait)
{
wakeup_pdflush(0); //唤醒pdflush,使之帮忙在调用do_sync的进程被调度出去之后pdflush(实际上是一个worker)被调度进来时仍然执行写回操作,也就是举全国之力进行同步
sync_inodes(0); //第一次调用同步inodes
sync_filesystems(0);//第一次...
sync_filesystems(wait); //第二次...
sync_inodes(wait); //第二次调用同步inodes
}
第一次调用同步inodes的时候,参数为0,进入sync_inodes之后发现对于被锁住的inode来说并不等待其被解锁直接返回,而且很轻松的仅仅是发起了一个当前可以被同步的inode的同步操作,然后在第二次进行sync_inodes调用的时候再等待所有第一次未被发起同步操作的inode的同步操作,包括等待第一次调用时发起但是还没有完成的同步操作。如果合并成一个调用,那么能迅速完成的和不能迅速完成混合在一起,处理起来弊端很多,比如等待被锁inode解锁的时间会很长,这期间可能它后面的一个inode也被锁上了,或者发生了别的什么事,最好的办法当然就是将可以尽快完成的操作尽快完成,万不得已的时候,也就是第二次调用sync_inodes的时候再处理那些慢速操作,这里面有一个思想,那就是对于那些慢速操作(inode被锁定,需要等待其解锁),反正第一次调用时会很慢,那么既然有第二次调用机会,姑且先不管它,说不定到第二次调用sync函数的时候,它还可能被解锁了呢!因此这两次sync_inodes调用的分工很明确,第一次调用的目的是尽可能多地“发起”io操作,并不等待其完成,第二次调用sync_inodes的目的是发起第一次调用不便发起的io操作,并等待两次发起的io操作的完成。
还有一个细节,那就是sync_inodes中对sync_blockdev的调用。既然在__sync_single_inode中已经调用了do_writepages,而sync_blockdev中基本上也是do_writepages的调用,这不是重复了吗?其实是不重复的,两个地方的调用参数mapping并不是一个mapping,__sync_single_inode中的mapping是针对一个特定的文件系统文件的,比如一个ext2文件系统的文件/home/aaa,而sync_blockdev中的mapping是针对整个块设备的,由于块设备也是一个文件,因此也有一个address_space负责管理页缓存,它主要是管理inode元数据的,比如一个特定文件系统的特定文件的inode所包含的数据就由这个address_space来管理,在__sync_single_inode中有write_inode的调用,然而这个调用将最终的执行流交给了具体文件系统超级块的回调函数,这个回调函数由该文件系统的设计者来实现,并不能保证其已经将数据刷新到了磁盘(但是必须能够弄脏一个inode数据所在的页面),因此必须显式刷新块设备的address_space中的dirty页面才可以保证。如果看一下代码就会发现,实际上即使是一般的文件,比如/home/aaa,它的数据页面最终也是进入到了其所在块设备的address_space中的,这在具体文件系统的get_block(如果有的话)中有所体现,这就意味着任何的(起码大多数)文件系统都是基于“块”来进行io的,而到了“块”这一层也就没有文件的概念了,因此几乎所有的文件数据最终都无法绕开“块”的管理,也就是说,所有的文件数据页面最终都要进入到对应块设备的address_space中,但是虽然一般文件的数据页面进入了对应块设备的address_space中,这个address_space并不管理这个页面的PAGECACHE_TAG_DIRTY或者PAGECACHE_TAG_XX,它仅仅管理诸如inode等元数据的数据页面在address_space中的tag(PAGECACHE_TAG_XX),因此sync_blockdev最终到达:
nr_pages = pagevec_lookup_tag(&pvec, mapping, &index,
PAGECACHE_TAG_DIRTY,
的时候,是不会触及到一般文件的数据页面的,块设备的address_space中页面的tag在诸如write_inode等处理元数据的位置被更改。因此__sync_inodes中的:
if (sb->s_root) {
sync_inodes_sb(sb, wait);
sync_blockdev(sb->s_bdev);
}
中sync_inodes_sb负责一般文件,而sync_blockdev负责元数据。
sync_filesystems这个函数的作用是什么?它很简单,就是调用了文件系统超级块的sync_fs回调函数,具体怎么实现,那就是文件系统自己的事情了,比如很多日志文件系统或者基于快照的文件的可恢复文件系统,在这里可以提交一个事务。do_sync在什么时候被调用呢?有两个地方,第一个就是显式的调用sync的时候,第二个就是pdflushd被唤醒的时候,pdflush在执行do_sync的时候,参数为0,这意味着它仅仅进行两次碰运气的操作,以求得最大限度的刷新文件,定时刷新这件事并不是急迫的。
最后set_sb_syncing是干什么的?实际上从do_sync中就可以看出,其第一句表明pdflush也会参与这次刷新操作,或者说别的用户进程也调用了sync,并且这个一般的操作并没有禁用掉中断之类的,也没有禁止抢占,因此真的有可能好几个执行流同时进行同步刷新操作,这并不是什么好事,为了保证每次只有一个进程在执行这个操作,set_sb_syncing函数起到了作用