本文通过分析ubifs的mount、read、write和commit流程,挖掘ubifs背后的设计决策和性能优化手段,并结合自身产品的特点,给出一些读写性能改进方案。
 

1.     ubifs mount流程

    mount过程就是初始化对象的过程。这其中包括上层(vfs层、页缓存层、通用块层)的回调接口的注册,从设备中获取相关信息(super block, master node,log,orphan, index node),初始化ubifs_info、TNC、LPT等内部对象,并对ubifs各区(默认不检查main区的index node,因为有log区的日志,一般情况下不需要扫描所有的index tree)、journal head、lpt head等进行校验、检查、修复、更新,创建后台进程等。可以看出,mount中包含了检查和修复过程,所以ubifs并没有提供额外的修复工具,这一点区别于vfat、ext3等文件系统。
     mount的核心函数为ubifs_init,其主要负责外部对象的初始化,内部对象的初始化由ubifs_get_sb负责。具体细节如下:
     ubifs_init主要流程:
  1. 创建ubifs inode slab(kmem_cache_create )
  2. 注册ubifs TNC shrinker回收功能 ubifs_shrinker_info(register_shrinker )
  3. 注册压缩算法(ubifs_compressors_init )
  4. 注册debugfs(dbg_debugfs_init )
  5. 注册ubifs文件系统 ubifs_fs_type(register_filesystem )
  6. 调用ubifs_fs_type.ubifs_get_sb()继续初始化
     ubifs_get_sb主要流程:
  1. 获取ubi_volume_desc对象
  2. 创建并初始化ubifs_info对象和super_block对象
  3. 读取并验证、修复ubifs_sb_node,并以ubifs_sb_node继续初始化ubifs_info对象
  4. 创建wbuf和后台线程ubifs_bgt1_0,其主要作用是后台同步write-buffers、commit、垃圾回收等。
  5. 读取并验证、修复ubifs_mst_node,并以ubifs_mst_node继续初始化ubifs_info对象
  6. 如果发现 index and LPT 头有损坏就进行修复,以继续初始化ubifs_info
  7. 更新master node信息
  8. 遍历、检查indexing node (ubifs_zbranch, ubifs_znode)的总大小是否与c->bi.old_idx_sz一致(dbg_check_idx_size,由chk_index控制,默认关闭)
  9. 回放log,检查修复index node,并更新TNC(ubifs_replay_journal)
  10. 删除orphan inode(ubifs_mount_orphans)
  11. 检查indexing tree的叶节点是否存在、crc等验证信息(dbg_check_filesystem,由chk_fs控制,默认关闭)
  12. 设置垃圾回收gc_waterline(UBIFS_FREE_RESERVE_RATIO 5),唤醒后台线程。
 

2.     ubifs read流程

ubifs read按如下顺序,在存储层次中依次查找所需数据,直至找到并完成读取:
  1. page cache
  2. write buffer
  3. flash
ubifs一切数据都封装成node,不同类型的node有不同的长度。一个data node最大可以存储的数据大小为UBIFS_BLOCK_SIZE  (4096)。也就是说ubifs单次读的最大长度即block大小。
 
ubifs读系统调用路径如下:read -> do_sync_read -> aio_read -> generic_file_aio_read -> generic_file_aio_read -> do_generic_file_read -> readpage -> ubifs_bulk_read 或 do_readpage
 
do_readpage:读取一个内存page,ubifs按block大小,把page切分成ubifs block后再依次按block进行读(read_block)。
ubifs_bulk_read:如果data node连续并在同一个LEB中,并超过3个内存page及以上(read_in_a_row控制),自动启动bulk_read。或者在mount时可以指定bulk_read option使能bulk read功能。bulk read最多支持UBIFS_MAX_BULK_READ(32) 个block的连续读。

3.     ubifs write流程

ubifs write按如下顺序,在存储层次中依次写入,直至写到flash中:
  1. page cache
  2. write buffer
  3. TNC
  4. log area
  5. main area
ubifs wirte系统调用过程如下:    
write -> do_sync_write -> aio_write -> ubifs_aio_write -> generic_file_aio_write -> __generic_file_aio_write -> generic_file_buffered_write -> generic_perform_write
 
ubifs wirte分为三个阶段:write_begin, write_end, writepage。
write_begin阶段主要做2件事:获取并更新page,flash空间预算申请。详细流程如下 : 
  1. 查询cache page,如没有就创建一个;如果page需要update,调用do_readpage从flash读取数据更新page,
  2. 为page申请flash上的budget空间,如果page appending 没置位或者ui->dirty置位,则不需要为此page申请budget,否则调用allocate_budget申请budget,流程如下:a)如果没有新数据,则返回成功
                    b)如果flash空间足够,则返回成功
                    c)如果fast budgeting,因为页缓存已经锁定,不能触发后面流程,只能直接返回错误
                    d)脏也写回flash(shrink_liability)
                    e)垃圾回收(0run_gc)
                    f)内存数据提交(ubifs_run_commit)
  3. 如果allocate_budget失败,释放之前申请的页缓存,并调用write_begin_slow,slow path先调用allocate_budget申请budget,然后再申请页缓存
  4. 如果allocate_budget成功,vfs将用户数据就copy到page cache中,然后进入write_end
    
write_end阶段主要做1件事:更新page和inode标记。详细流程如下:

  1. 如果write_begin因为优化原因没有更新缓存页,在write_end中更新缓存页
  2. 如果发现脏页,设置page dirty标记
  3. 如果发现appending,设置inode dirty标记
  4. 然后vfs后台进程调度将page cache提交到 writeback queue, 然后通过pdflush线程调用ubifs_writepage
writepage阶段主要做3件事: 查找存储位置,更新wbuf,更新TNC。先写索引信息,再写数据信息。写入flash在commit流程中完成。
        write_inode流程如下:
              1.  按如下顺序查找存储位置(make_reservation):
                   1.1. 如果当前的write buf剩余空间满足大小,如果空间足够直接返回;
                   1.2. 查找LPT内存节点
                   1.3. 触发垃圾回收(ubifs_garbage_collect,不是回收所有垃圾,只需要会受到符合要求的LEB即可)
                   1.4. 同步write buf(ubifs_wbuf_sync_nolock)
                   1.5. 将找到的leb作为bud 写入对于的refnode 以便commit的时候能找到这个bud
                   1.6. 更新wbuf中的leb为新的leb(ubifs_wbuf_seek_nolock)
                   1.7. 触发提交流程(do_commit)
               2.  更新wbuf(write_head)。如果wbuf刚好满,将wbuf写入flash,并清空wbuf;如果node大小大于wbuf有效部分,先把wbuf填满并将wubf写入flash,剩余部分中将整page大小部分字节写入flash,不满整页的写入wbuf中。如果设置sync标志,同步wbuf数据到flash,并清空wbuf(ubifs_wbuf_sync_nolock)
               3.  更新TNC(ubifs_tnc_add)。从TNC中查找匹配key(inode->i_ino)的znode,在叶子节点层查找指定key的znode节点,如果key值精确匹配,返回该节点对应分支在父节点分支数组中的序号;如果key值不是精确匹配,返回父节点中最接近的分支号;返回-1 说明key值太小 在树的最左边不管最终有没有找到匹配key的znode。并将从根到找到的znode这条路径上的索引znode都会被设置为脏(lookup_level0_dirty);如果znode不在TNC中,从flash中读取,并添加到TNC的页节点中;如果在TNC中找到,则将此页节点设置为脏,并找到LPT中此页节点对应的LEB所在路径上的所有LPT节点设置为脏。
 
          do_writepage流程如下:
               1. 将ubifs_data_node的data进行压缩(ubifs_compress)
               2. 查找存储位置(make_reservation),同write_inode 步骤1
               3. 更新wbuf(write_node),同write_head步骤2,但没有ubifs_wbuf_sync_nolock这一步
               4. 更新TNC(ubifs_tnc_add),data 的key为(inode->i_ino, block)组合。同write_head步骤3

4.     ubifs commit流程

多种场景下都可以触发commit流程,比如后台进程定时触发。commit的主要作用就是将内存中的大量数据对象刷新到flash中。为了减少commit过程中对系统的影响,commit分为2阶段:start阶段和end阶段。start阶段负责刷新前准备,比如收集需要刷新的内存数据、查找存储位置,更新节点属性等;end阶段负责写flash和内存数据更新。第二阶段的commit可以和文件系统正常操作同步进行。
start commit阶段:
  1. 将inode wbuf、data wbuf、gc wbuf同步到flash(ubifs_wbuf_sync)
  2. ubifs_gc_start_commit
  3. ubifs_log_start_commit:将各c->jheads[i].wbuf中的位置信息更新到内存对象树ubifs_bud,并写入log区
  4. ubifs_tnc_start_commit:找出tnc中所有脏节点,加入c->cnext链表,为需要写入的znode分配leb,在leb上安排zonde的位置,更新znode的leb等属性,更新lpt属性
  5. ubifs_lpt_start_commit:对LPT区进行垃圾回收,找出LPT中所有脏节点,加入c->lpt_cnext链表,更新c->ltab表数据,分配LPT 区LEB(alloc_lpt_leb)
  6. ubifs_orphan_start_commit
  7. ubifs_get_lp_stats:获取lprops统计信息,以便在更新master node时使用
end commit阶段:
  1. ubifs_tnc_end_commit:根据c->cnext znode链表构造flash对象ubifs_idx_node,并将index node集合写入main区,释放或标记c->cnext上所有znode
  2. ubifs_lpt_end_commit:更加ltab,lsave,nnode, pnode对象,构造UBIFS_LPT_LTAB,UBIFS_LPT_LSAVE,UBIFS_LPT_NNODE,UBIFS_LPT_PNODE LPT节点,并写入flash LPT区
  3. ubifs_orphan_end_commit
  4. ubifs_log_end_commit:更新log tail lnum(ltail_lnum)等信息
  5. 更新mst_node并写入flash master区
  6. ubifs_log_post_commit:释放已提交到main区的ubifs_bud内存对象,并回收其相应的log区空间
  7. ubifs_gc_end_commit
  8. ubifs_lpt_post_commit:回收LPT区置垃圾回收标记的LEB块

5.     设计决策及性能优化手段总结

为了提高ubifs读写速度,ubifs采用了缓存、压缩和异地更新手段,这给ubifs的设计上引入了巨大的复杂性。
首先不同的缓存数据对象,有不同的缓存结构、写入策略、提交策略。
然后缓存、压缩和异地更新,又导致缓存数据实际占有的flash物理空间无法准确计算,给查找存储位置带来复杂性。
其次异地更新和数据的细分处理,导致存储对象和存储单元需要细分,给管理存储对象和存储单元带来复杂性。
 
 

6.     参考资料

linux kernel 2.6.32

—— 完 ——

ubifs性能优化分析的更多相关文章

  1. SQL SERVER 查询性能优化——分析事务与锁(五)

    SQL SERVER 查询性能优化——分析事务与锁(一) SQL SERVER 查询性能优化——分析事务与锁(二) SQL SERVER 查询性能优化——分析事务与锁(三) 上接SQL SERVER ...

  2. 一次 group by + order by 性能优化分析

    一次 group by + order by 性能优化分析 最近通过一个日志表做排行的时候发现特别卡,最后问题得到了解决,梳理一些索引和MySQL执行过程的经验,但是最后还是有5个谜题没解开,希望大家 ...

  3. golang 性能优化分析:benchmark 结合 pprof

    前面 2 篇 golang 性能优化分析系列文章: golang 性能优化分析工具 pprof (上) golang 性能优化分析工具 pprof (下) 一.基准测试 benchmark 简介 在 ...

  4. mysql性能优化分析 --- 下篇

    概要回顾 之前看过<高性能mysql>对mysql数据库有了系统化的理解,虽然没能达到精通,但有了概念,遇到问题时会有逻辑条理的分析; 这回继上次sql分析结果的一个继续延伸分析,我拿了; ...

  5. Web性能优化系列(1):Web性能优化分析

    本文由 伯乐在线 - 鸭梨山大 翻译,sunbiaobiao 校稿.未经许可,禁止转载!英文出处:gokulkrishh.github.io.欢迎加入翻译小组. 如果你的网站在1000ms内加载完成, ...

  6. Web性能优化分析

    如果你的网站在1000ms内加载完成,那么会有平均一个用户停留下来.2014年,平均网页的大小是1.9MB.看下图了解更多统计信息. 直击现场 <HTML开发MacOSApp教程>  ht ...

  7. mysql性能优化分析 --- 上篇

    概要 之前看过<高性能mysql>对mysql数据库有了系统化的理解,虽然没能达到精通,但有了概念,遇到问题时会有逻辑条理的分析; 问题 问题:公司xxx页面调用某个接口时,loading ...

  8. MySQL索引及性能优化分析

    一.SQL性能下降的原因 查询语句问题,各种连接.子查询 索引失效(单值索引.复合索引) 服务器调优及各个参数设置(缓冲.线程池等) 二.索引 排好序的快速查找数据结构 1. 索引分类 单值索引 一个 ...

  9. 大型网站调试工具之一(php性能优化分析工具XDebug)

    一.安装配置 1.下载PHP的XDebug扩展,网址:http://xdebug.org/ 2.在Linux下编译安装XDebug 引用 tar -xzf xdebug-2.0.0RC3.gzcd x ...

随机推荐

  1. Ubuntu中给eclipse和android studio添加桌面快捷图标

    Ubuntu 12.04 创建应用程序启动项(可在Unity LaucherPad显示) http://www.cnblogs.com/bluestorm/archive/2012/10/12/272 ...

  2. Myeclipse 2016 & 2014 下载

    myeclipse-2016-ci-6-offline-installer-windowshttps://downloads.genuitec.com/downloads/myeclipse/inst ...

  3. Eclipse中怎么设置Add cast to Clazz 快捷键

    方法如下:window => preferences => 搜索keys => 然后点击进去,搜索add cast => 看到如图所示Quick Fix , 点击进去 => ...

  4. Yii 同域名的单点登录 SSO实现

    SSO (Single Sign-on) 顾名思义就是几个子项目共用一个登录点. 原理简单来说就是服务端session 共享, 客户端跨域cookies. 实现非常简单,protected/confi ...

  5. C#委托(Action、Func、predicate)

    Predicate 泛型委托:表示定义一组条件并确定指定对象是否符合这些条件的方法.此委托由 Array 和 List 类的几种方法使用,用于在集合中搜索元素. public delegate boo ...

  6. Angular JS [Draft]

    AngularJS应用是完全运行在客户端的应用.没有后端的支持,我们只能展示随页面一起加载进来的数据.AngularJS提供了几种方式从服务器端获取数据. $http服务 $http 封装了浏览器原生 ...

  7. [译] Extending jQuery Part1 Simple extensions

    本章包含: JQuery 的起源和目标. 你能扩展JQuery 的那些部分. JQuery 扩展的实例. 如今,JQuery 已经是网络上最受欢迎的JavaScript Library. 1.1 jQ ...

  8. C++中单例模式

    //C++单例模式:指一个类只生成一个对象 #include <iostream> using namespace std; class A{ public: static A* getA ...

  9. java中的负数的问题

    在计算机中是使用二制数中的最高位表示来正负. 二进制的储存中都是用的补码,正数的原码.反码和补码相同,负数的原码是最高位为1,反码最高位不变,其余各位取反,补码为其反码+1(重要!!) 首先得知道最高 ...

  10. WebView中实现文件下载功能

      WebView控制调用相应的WEB页面进行展示.当碰到页面有下载链接的时候,点击上去是一点反应都没有的.原来是因为WebView默认没有开启文件下载的功能,如果要实现文件下载的功能,需要设置Web ...