zoukankan      html  css  js  c++  java
  • write 系统调用耗时长的原因

        前一阵子公司一部门有人叫帮忙调查,说他们write系统调用基本上是个位数微秒就返回,或者说几十us,但偶尔出现几次write系统调用达到几百毫秒和情况。大家都知道,通过vfs进行write,都是写写到page cache中,然后内核线程再定期同步到磁盘。写到内存应该是很快的才对。刚开始,我做了许多设想,1)磁盘IO太重,内存中的脏数据达到一定比率后write必须同步写到磁盘;2)那些耗时长的write是使用direct io,绕过了page cache;3、刚刚好write一个page时,read也在读同一page,那个page被lock了,write要等它。后来每一种假设又都被自己推翻了。以下是通过vfs进行write系统调用的图:

     

        前几天,使用systemtap进行一步步的定位,最后把可疑的点锁定在__block_write_begin这个函数中(fs/buffer.c)。

        先说明下这个函数是干什么的。generic_perform_write函数是把数据写入磁盘的主要处理函数。它先调用address_space_operations. write_begin(其中会调用我们上文提到的__block_write_begin函数),接下来把数据拷贝到上一步分配出来的page中,最后调用address_space_operations.write_end。上面所说的write_begin和write_end会具体根据不同的file system调用不同的函数,拿ext4来说,它的函数如下(有dellay allocation的话):

    static const struct address_space_operations ext4_da_aops = {
    
             .readpage                 = ext4_readpage,
    
             .readpages               = ext4_readpages,
    
             .writepage                = ext4_writepage,
    
             .writepages              = ext4_writepages,
    
             .write_begin            = ext4_da_write_begin,
    
             .write_end                = ext4_da_write_end,
    
             .bmap                        = ext4_bmap,
    
             .invalidatepage                = ext4_da_invalidatepage,
    
             .releasepage            = ext4_releasepage,
    
             .direct_IO                 = ext4_direct_IO,
    
             .migratepage           = buffer_migrate_page,
    
             .is_partially_uptodate  = block_is_partially_uptodate,
    
             .error_remove_page      = generic_error_remove_page,
    
    };

        可以看到,ext4的write_begin是ext4_da_write_begin。Ext4_da_write_begin会先分配一个页框,然后调用__block_write_begin分配缓冲区(为什么要分配缓冲区这里就不说明了)。那么,__block_write_begin分配缓冲区而已,为什么有时候要耗时那么长呢?先看看代码:

    int __block_write_begin(struct page *page, loff_t pos, unsigned len,
    
    get_block_t *get_block)
    
    {
    
      ......
    
      if (!buffer_uptodate(bh) && !buffer_delay(bh) &&
    
        !buffer_unwritten(bh) &&
    
         (block_start < from || block_end > to)) {
    
      ll_rw_block(READ, 1, &bh); //这里指定read操作,提交BIO
    
      *wait_bh++=bh;
    
      }
    
    ......
    
     
    
    /*
    
    * If we issued read requests - let them complete.
    
    */
    
      while(wait_bh > wait) { //这里等待io操作完成
    
      wait_on_buffer(*--wait_bh);
    
      if (!buffer_uptodate(*wait_bh))
    
      err = -EIO;
    
      }
    
    }
    
     
    

      

        write的基本单位是块(由磁盘文件系统定义,默认为4K)。这样的话,page cache中一个page(刚好也为4K)刚好就是一个块。比如说write的地址是512,长度是20,page cache就分配了一个能写 0~4095的页page frame,这个page frame刚好对应一个磁盘块,假如把要write的数据拷贝到这个page frame的 512~531,这样的话0~511和532~4095是空的。但是下次write back的时候会把整块数据都写入磁盘,所以需要把这一整块的数据都先从磁盘中读出来,写入page cache中,以防止0~511和532~4095被误写。这个读操作应该就是耗时长的原因了。

  • 相关阅读:
    USACO Name That Number
    USACO Milking Cows
    hdu 1540 Tunnel Warfare (线段树维护左右最长连续区间)
    Contest 1
    JNU周练1026
    树形DP
    Python和C扩展实现方法
    Python模拟C++输出流
    SkipList算法实现
    Python 迭代dict 效率
  • 原文地址:https://www.cnblogs.com/hbt19860104/p/3516327.html
Copyright © 2011-2022 走看看