ubifs核心功能 -- 垃圾回收
可回收空间的分类
垃圾回收的目的是再利用(回收后的空间大小能写入有效的node),如果再利用的价值越低,其回收的必要性越低。为了进行有效的垃圾回收,UBIFS对可回收空间做了2个层次的水线划分:
死空间水线,即最小node大小(一般是最小的data node):dead_wm = ALIGN(MIN_WRITE_SZ, min_io_size);
暗空间水线,即最大node大小(一般是最大的inode node):dark_wm = ALIGN(UBIFS_MAX_NODE_SZ, min_io_size);
一般来说, min_io_size(page or subpage) <= dead_wm <= dark_wm。在我们的系统上,dead_wm和dark_wm的水线都是一个page的大小(8K)
free + dirty < dead_wm时,此时认为垃圾很少,一般就认为此LEB full,不值得进行垃圾回收。
free + dirty < dark_wm时,因为可回收的空间较小,dark_wm被回收的概率也变小。同时为了效率和节约空间考虑,这种leb也不会被加入到内存LPT树中。如果可回收空间小于dark_wm,这段空间的回收价值就不大。所以也得出一个结论:node越大,浪费的空间越大。
可回收对象的分类
因为index node和非index node的管理方式不一样,垃圾回收需要对这2类对象进行区分处理。
非index node的管理方式为 wbuf - flash 2层结构,比较简单。在ubifs_garbage_collect_leb(BG GC)或者ubifs_gc_start_commit(Commit GC第一阶段)阶段unmap LEB完成回收;
index node的管理方式为 TNC - journal - flash 4层结构,相对复杂。在ubifs_garbage_collect_leb(BG GC)或者ubifs_gc_start_commit(Commit GC第一阶段)阶段完成准备,在ubifs_gc_end_commit (Commit GC第二阶段)阶段unmap LEB完成回收,所有被垃圾回收的index leb加入到链表idx_gc中,这个链表中的leb在ubifs_gc_end_commit阶段unmap。
垃圾回收(后台进程阶段)
后台垃圾回收由background线程负责,由wbuf定时器定时唤醒,并判断是否需要进行垃圾回收,如是,启动垃圾回收操作。正常情况下,每次垃圾回收操作回收UBIFS_GC_ONCE(5)个LEB。
回收的leb最后被unmap,并且标记为空(bud->start 设置为 0),再次使用时,会重新map。LEB unmap-map重新映射流程,UBI根据磨损均衡算法进行重新map,达到PEB磨损平衡的效果。同时也避免了反复使用一个PEB的情况。
垃圾回收时垃圾LEB上有效的数据会搬运到gc wbuf->lnum对于的LEB上,gc_lnum作为后备的垃圾回收目的LEB,如果gc的wbuf->lnum无效,取gc_lnum作为gc wbuf->lnum,并置c->gc_lnum无效(switch_gc_head)。通过gc_lnum,保证在垃圾回收时,不会出现找不到搬运目的LEB的情况(即gc wbuf->lnum)。
垃圾回收判断准则:
检查gc水线设置gc_waterline, 只有当总的脏空间lst.total_dirty > 分卷总大小/gc_waterline时,才进行GC,避免在空闲空间较多的情况下进行gc。gc_waterline默认为UBIFS_FREE_RESERVE_RATIO(5)。
而且,对于index node,ubifs对于index的回收是只回收脏空间大于一半擦除块的,其他index擦除块可能即使有脏空间也不会进行回收。
垃圾回收流程:
1)判断是否需要commit,如是,返回-EAGAIN,触发commit流程
2)已经回收了index LEB并且gc已连续尝试SOFT_LEBS_LIMIT(4)次,返回-EAGAIN
3)如果gc已连续尝试HARD_LEBS_LIMIT(32)次,返回-ENOSPC
4)查找dirty LEB进行回收(查找顺序依次是lpt_heap[LPROPS_DIRTY_IDX], lpt_heap[LPROPS_DIRTY], lpt_heap[LPROPS_FREE],c->uncat_list,LPT in flash)
5)读取dirty LEB的node,并进行排序、分类、丢弃过时数据和无需搬运数据等处理
6)搬运数据到gc wbuf对应的LEB(gc LEB),回收垃圾LEB,这里区分index LEB和非index LEB详细流程见下
7)如果回收的是非index LEB,此次GC结束
8)如果回收的是index LEB,继续GC
9)已经回收过多次还没有回收成功,说明每次回收的块都用于gc自己(因为GC需要始终占用一块free的LEB,用来作为搬运的目地LEB,即gc LEB),而且当前用于gc回收的leb空间没有更多。这里就尝试找更脏的leb,因为这样的LEB上的有效数据少,回收这样的leb需要的空间就要小些。
10)同步gc wbuf,unmap gc LEB(c->gc_lnum)
11)更新LPT树
搬运数据,回收LEB详细流程:
可全部回收的 leb(free + dirty == leb_size):sync,然后unmap
1)如果不是全free的leb,将data和index wbuf写到对应的flash LEB。避免GC后,wbuf又把obsoleting nodes写入flash。然后再更新LEB lprops并将此LEB加入到c->frdi_idx_list(index LEB)或c->freeable_list(data LEB)。
2)unmap回收LEB
3)如果c->gc_lnum无效,将回收的lnum作为c->gc_lnum,然后返回LEB_RETAINED。否则返回LEB_FREED。
非index leb:搬运后再sync,然后unmap
1)将有效node搬运到GC LEB的wbuf(c->jheads[GCHD].wbuf)
2)为了保证垃圾回收的节点是存在flash的,同步data和index LEB的wbuf到flash。避免GC后,wbuf又把obsoleting nodes写入flash
3)更新LEB lprops并将此LEB加入到c->freeable_list(data LEB)
4)如果c->gc_lnum无效,将回收的lnum作为c->gc_lnum,然后返回LEB_RETAINED。否则,同步gc LEB的wbuf到flash,再unmap回收LEB,然后返回LEB_FREED。
index leb:更新idx_gc->list对象和c->frdi_idx_list对象,以备在end commit gc阶段使用
1)遍历index leb中index node,将 index node加入到TNC,并将TNC中该节点对应路径设置为脏,commit的时候就会重新写入flash,在gc中就达到了搬运的目的;
2)将此LEB加入到已经gc的index链表中 idx_gc->list。因为已经将这个index leb读取到TNC中,所以在下一次commit的时候会将这些读取到TNC中的index写入flash。但是不能立即unmap这个leb,因为commit还没有发生, 如果发生掉电,还需要保留这个index leb,用于恢复,否则发生掉电,这个index leb已经unmap,但又没有commit完成,则这个index leb上的数据就会丢失。
3)更新LEB lprops并将此LEB加入到c->frdi_idx_list(index LEB),然后返回LEB_FREED_IDX。
垃圾回收(提交阶段)
提交阶段的垃圾回收,以commit为界,划分为2部分:ubifs_gc_start_commit和ubifs_gc_end_commit。
ubifs_gc_start_commit:更新idx_gc->list对象和c->frdi_idx_list对象,以备在end commit gc阶段使用
1)unmap freeable_list LEB,更新lprops;
2)找出所有的frdi_idx_list LEB,并更新到idx_gc中;
ubifs_gc_end_commit:unmap idx_gc LEB
此阶段前,已完成commit,所以可以放心地unmap所有的idx_gc LEB;
至此,UBIFS文件系统垃圾回收流程完成。
--EOF--