页缓存回写时,导致sync阻塞的问题

来源:互联网 发布:一洋电商软件 编辑:程序博客网 时间:2024/06/10 16:22

问题抽象出来是这样:

有一块ubifs文件格式的flash,挂在/mtd3上

拷贝一个大文件big_flash(大约40M),到/mtd3上

如果在拷贝文件的时候,执行sync命令,则会导致sync长时间阻塞,阻塞链为:


wait_on_bit(WS_USED_B)

bdi_wait_on_work_clear

bdi_sync_writeback(sb)

sync_inodes_sb(sb)

__sync_filesystem

sync_filesystems


其中,关键代码为线程将自己挂载到新初始化的work等待队列里,等待flusher处理。

bdi_queue_work

list_add_tail_rcu(&work->list, &bdi->work_list);

wake_up_process(wb->task);


可以看出,sync将本刷新请求封装到work里,挂到bdi的work_list链表,然后唤醒flusher,尝试处理这些回写。

因为flusher处理不及时,导致sync长时间阻塞。

于是,就要找出问题时,flusher线程在做什么。

猜测如下场景:

1) cp命令,在big_flash的inode里的mapping页缓存里,产生大量脏页,flusher线程处理这些脏页的回写,

      以每次1024个页面的规模,往flash写数据。疑问:这个flusher正在处理的work是如何生成的。

2)当刷出的page cache数目达到系统水线后,不再继续刷此work代表的page cache,退出返回上一层后,唤醒处于等待此刷新work

     的进程

   3)继续取下一个work进行处理。这个work正好就是sync提交的刷页请求。在最终调用pagevec_lookup_tag 找不到更多的脏页后,

   退出此work处理,唤醒sync线程


所以问题还是在于1步骤太耗时导致的。


循环拷贝文件时, 执行sync一直不能退出问题测试分析结论如下:




原理及知识要点:


1) sync: 同步等待所有脏页刷入 flash


2) 内核flusher线程,负责将脏页刷入 flash后,通知1完成


3) 测试用例,循环拷贝,删除文件,不断产生脏页


4) 大部分flash文件系统,采取的是异地更新策略。即不修改当前数据,而重新拷贝一份,修改好拷贝的数


据后,删除原来的废弃数据。
    
5)在出问题时,2的速度,小于3的速度,导致1完成不了。
   








出问题时,发现flusher一直在处理sync提交的刷新任务(即sync阻塞等待的任务)
为什么会一直处理sync的任务?
因为,sync提交的任务是同步刷新,即带有WB_SYNC_ALL标志。这个标志的意思是,等待bdi设备里的所有脏页


刷完为止。
具体的内核实现点在write_cache_pages函数,这个函数本身是个大循环:




int write_cache_pages(struct address_space *mapping,
     struct writeback_control *wbc, writepage_t writepage,
     void *data)
{


...


while (!done && (index <= end)) {
int i;


nr_pages = pagevec_lookup_tag(&pvec, mapping, &index,
     PAGECACHE_TAG_DIRTY,
     min(end - index, (pgoff_t)PAGEVEC_SIZE-1) + 1);


            //每次尝试从inode的mapping里取出14个脏页,刷到flash里,如果这个inode没有脏页了,就停


止刷新
if (nr_pages == 0)
break;


        ...


             for (i = 0; i < nr_pages; i++) {
...
ret = (*writepage)(page, wbc, data);
                     
if (nr_to_write > 0) {
nr_to_write--;
                       if (nr_to_write == 0 &&
   wbc->sync_mode == WB_SYNC_NONE) {  
                          //非同步方式的刷新,当回写1024个页面后,就退出不再回写,但我们是


WB_SYNC_ALL,因此会一直遍历inode的脏页
                          //直到没有脏页可写为止。


                         //由于ubifs(其实所有异地更新文件系统都有这个问题),在修改文件过程中,
                         //会产生大量无效的物理块,如果不及时回收,则ubi写flash会非常慢。


                          //就算最后,终于刷完从index=0后的所有脏页,由于此时一定有nr_to_write = 


0,即已经刷完了1024个页面,返回到外层函数
                           //wb_writeback后,还会再次进入write_cache_pages尝试从index = 0刷1024


个页,
                          //因此,造成不能退出的位置在 wb_writeback函数的循环里
   done = 1;
   break;
 
                       }


                    }
               


}


}


}








static long wb_writeback(struct bdi_writeback *wb,
struct wb_writeback_args *args)
{


   for (;;) {
            writeback_inodes_wb(wb, &wbc);


          if (wbc.nr_to_write <= 0)  //这里无法退出到上层,不能设置work处理完毕,也就不能


通知sync线程刷新完成
continue;


   }




}







原创粉丝点击