分类: LINUX
2011-08-09 00:54:45
ubi 是位于 ubifs 文件系统和mtd 层之间,负责ubi 卷管理
先看下两个 on-flash 数据结构 (ubi-media.h)
1. UBI erase counter header.
2. UBI volume identifier header
虽然叫卷标识头,但实际是描述logical 块的信息,
可以看ubi_eba_write_leb 中关于未映射logical block 处理部分的代码
其中主要字段:
vol_id 卷id号
used_ebs; total number of used logical eraseblocks in this volume
lnum 逻辑块号
data_size 逻辑块包含字节数
data_crc 存储在该逻辑块上数据的CRC checksum
sqnum 该逻辑块的全局唯一串号
注释中提到了1个leb(逻辑块)对应2个peb(物理块的)情况
发生在两种情况,但主要都是块写操作过程中发生异常reset引起
所以引入data_crc ,和sqnum
当发生1对2情况,在选择peb 时的依据是:
1. 如果sqnum 大的块,data_crc 正确,那么选择sqnum 大的
2. 否则寻找sqnum 小的那个块
详细可看(ubi-media.h) 中大段注释
下面顺着ubi_init个过程,了解下所有相关的数据结构
首先 ubi_attach_mtd_dev 中会创建一个ubi_device,
主要字段介绍下:
ubi_init=>ubi_attach_mtd_dev=>attach_by_scanning
=>ubi_scan
其中 相关数据结构
volumes RB-tree 的根 (point to ubi_scan_volume 's struct rb_node rb)
corr 链接 数据无效的block 的list
比如write copy 抛弃的,或者其他数据损坏的block
free 链接 已经写上ubi_ec_hdr的block 的list
erase erase和free 的区别就是mtd 层意义上的Free ,没有写过ubi层的东西
比如ubi_ec_hdr
alien 链接卷上保留的block 的list
bad_peb_count
卷上 坏块数
is_empty 记录卷对应的mtd 区是否是空闲的
max_ec,max_ec,mean_ec 写平衡用的,记录卷上块当前最大最小的擦写次数
和
max_sqnum 卷上最大串号
ec_sum,ec_count 是临时变量,用于process_eb 时,对used, free(no vid)的块进行
erase count累加和记数,然后最后用来计算mean_ec 的
mean_ec = ec_sum/ec_count
scan 过程的最后会将 各seb 的 ec 设置为 si->mean_ec;
因为一开始总为0,后面因为写平衡原因,各块应该差不多的ec count,
设置为平均值比较好
下面这个结构是在scan mtd 时 将物理block 根据扫描状态
挂到(add_to_list)ubi_scan_info 的 corr,free,earse,alien list
上的 一个物理块扫描信息结构
scanning information about a physical eraseblock :
scan 中获得的卷信息
scanning information about a volume:
root 链接所有属于该卷的scan leb info (ubi_scan_leb)的根
rb 红黑树节点,连接到 ubi_scan_info 的 volumes
具体可见下图:
在图中的连接有rb tree ,和list
大概层次为 ubi_scan_info
对应于多个卷,向下通过rb tree连接ubi_scan_volume,
volume id 大小做为rb-tree 的权重
或者通过list (free,erase,corr) 连接到非隶属于某volume
的ubi_scan_leb
ubi_scan_volume
对应于某一个卷,向下通过rb tree 连接到ubi_scan_leb
erase count 做为rb tree 的权重
ubi_scan_leb
对应于某一个逻辑块扫描信息,可以通过union ,rb node
或 list 连接到 ubi_scan_info 和ubi_scan_volume
所以当flash 第1次初始化,并且没有坏块,那么所有ubi_scan_leb
都将list 到ubi_scan_info 的 erase list 上
上面就是scan 过程中的所有扫描结构,这些结构的产生,都是由
ec_hdr,vid_hdr 两个on-flash 结构 ,做判断依据的
ubi_attach_mtd_dev =>attach_by_scanning => ubi_scan
执行完成回到attach_by_scanning 可以通过si (struct ubi_scan_info)
获得的信息去填充ubi的一些数据:
ubi->bad_peb_count = si->bad_peb_count;
ubi->max_ec = si->max_ec;
ubi->mean_ec = si->mean_ec;
ubi_attach_mtd_dev =>attach_by_scanning =>ubi_read_volume_table
接下来ubi_read_volume_table 过程中会涉及到 ubi_vtbl_record
这个结构也是个 on flash 结构,被写到一个叫UBI_LAYOUT_VOLUME_ID
的内部卷,
ubi_read_volume_table 有两个分支,首先执行ubi_scan_find_sv
查找UBI_LAYOUT_VOLUME_ID的scan volume (ubi_scan_volume)
1. 如果没找到 create_empty_lvol=>create_vtbl创建
最先设置一个 empty_vtbl_record,只是设置他CRC为0xf116c36b,代表
是个empty record
然后ubi_scan_get_free_peb 获得free peb ,写入layout volume 头
vid_hdr->vol_type = UBI_VID_DYNAMIC;
vid_hdr->vol_id = cpu_to_be32(UBI_LAYOUT_VOLUME_ID);
然后将整个empty vtbl , 最大为128slots,写入,
最后执行ubi_scan_add_used,将前面分配并写入的peb 添加到ubi_scan_info
(如果有旧的ubi_scan_leb,这个过程会根据 1个leb 对应2个peb 情况,
释放old seb 对应的原来那个peb )
2. 如果layout 卷已经存在,执行process_lvol ,
对这个卷用ubi_rb_for_each_entry(rb, seb, &sv->root, u.rb)
进行编历,(layout volume 只包含vulome table及其备份)
所以应该是两个leb(logical eraseblock),
使用ubi_io_read_data,将leb0,leb1 ,全部读出来,经常检查,如果有
需要,就进行修复
注释中提到这两个块发生变化时的保存流程:
* a. erase LEB 0;
* b. write new data to LEB 0;
* c. erase LEB 1;
* d. write new data to LEB 1.
处理过ubi_vtbl_record之后进行init_volumes
ubi_attach_mtd_dev = >attach_by_scanning =>ubi_read_volume_table
=>init_volumes
在 init_volumes 过程中主要就是把 获得的
on flash 的 volume 信息 (ubi_vtbl_record) 去初始化
ubi_device中的
struct ubi_volume *volumes[UBI_MAX_VOLUMES+UBI_INT_VOL_COUNT];
下面看下 ubi_volume 结构:
下面代码显示ubi_volume的相关信息如何从vtbl(ubi_vtbl_record)中获得的:
接下来ubi_scan_find_sv(si, i);
从扫描信息ubi_scan_info 中根据volume id 找到volume scan info (sv)
接下来用sv (ubi_scan_volume)的信息更新vol(ubi_volume)
上面这些操作,循环ubi->vtbl_slots次后,排除所有empty record
(判断条件为vtbl[i].reserved_pebs)为0,是因为empty_vtbl_record
被设置为static 全局变量)
接下来添加 layout volume 相关的ubi_volume ,这个ubi_volume 放到
ubi->volumes数组中 ubi->vtbl_slots 下标后
(ubi->volumes[vol_id2idx(ubi, vol->vol_id)] = vol;)
最后更新 整个ubi 的reserved pebs 和avail_pebs
代码如下:
ubi_attach_mtd_dev = >attach_by_scanning =>ubi_read_volume_tabl
=>check_scanning_info
check 主要是 扫描所有vtbl_slots个static + 1个内部卷(layout),
使用ubi_scan_find_sv 查找sv(ubi_scan_volume),对于sv 存在,而该vol_id对应的
ubi_volume不存在的情况,通过 ubi_scan_rm_volume 做下面两步
1. delete sv 's seb rb-tree (&struct ubi_scan_leb objects),
2 .delete sv from si->volumes rb-tree
(si =>sv=>seb !!! 注意这样一个层次)
做ubi_scan_rm_volume的还有一种情况是vol->reserved_pebs 为0
ubi_init=>ubi_attach_mtd_dev=>attach_by_scanning
=>ubi_wl_init_scan
其中数据结构:
ubi_wl_init_scan 是通过si(ubi_scan_info)来初始化,磨损平衡子系统的
1.先对si->erase 上每个peb 分配1个ubi_wl_entry ,放到ubi->lookuptbl[e->pnum]中
以pnum 为下标,然后调度erase work (对erase list 上的seb 继续erase 操作)
具体操作可以看下erase_worker,正真执行是在ubi_thread
2.对si->free list 上的每个seb, 分配并初始ubi_wl_entry, 然后将该ubi_wl_entry
插入ubi_devic 的free RB tree
3.对si上corrupted list 的每个seb进行处理,基本同si->erase
4.最后扫描 si->volumes,获得各sv(ubi_scan_volume),然后遍历sv 上
挂seb(ubi_scan_leb)的RB tree,然后对每个seb分配1个ubi_wl_entry
然后根据seb的scrub 判断是否需要擦除,如果需要,就挂到ubi_device
的scrub RB tree 上,否则挂到used RB tree上
5. 执行ensure_wear_leveling,判断是否要进行写平衡处理(wear_leveling_worker)
判断的条件是,ubi_device used RB-tree 上最左边(ec 最小的)和
ubi_device free RB tree 上接ec近于WL_FREE_MAX_DIFF的节点
两个node 之间ec的差值大于等于UBI_WL_THRESHOLD
wear_leveling_worker 的工作就是将used 上ec 大的,copy 到 free 上ec
小的block 上
上图主要描述了ubi_device,ubi_volume,ubi_wl_entry
的关系,
1.ubi_device 上free,used,scrub,上根据不同情况的3个rb tree root
各ubi_wl_entry根据其状态free ,used or scrub挂到各自的rb tree上
2. ubi_device 上pq 是个保护队列,比如free 到used 的 rb tree的移动
过程中需要保护下, 这时就先挂到pq上,具体原因可见 wl.c的
UBI wear-leveling sub-system 部分注释
3. lookuptbl 是个ubi_wl_entry的指针数组,为了快速查找ubi_wl_entry,
每个ubi_wl_entry pointer都在lookuptbl数组里,以pnum 做下标
4. ubi_volume 通过 ubi_device 类型指针ubi回指向ubi_device
ubi_volume 中另一个比较重要的就是LEB 和 PEB 的映射表
int *eba_tbl
ubi_init=>ubi_attach_mtd_dev=>attach_by_scanning
=>ubi_eba_init_scan
这个函数作用,如其注释,就是init eraseblock Association 子系统
主要完成对ubi_volume上 int *eba_tbl (LEB->PEB mapping)的
初始化工作
1.先分配eba_tbl
2.然后对小于reserved_pebs物理块号的设置为unmap
3.遍历sv root, 获得seb lnum ,pnum ,来对eab_tbl 进行初始化
ubi_init=>ubi_attach_mtd_dev=>attach_by_scanning
=>ubi_scan_destroy_si
把前面为scan 所生成的si, sv,seb 全部释放,这些数据
只是为生成上面ubi_device,ubi_volume 结构中数据,所使用的中间变量
ubi_init=>ubi_attach_mtd_dev
=>uif_init
该函数完成用户接口初始化
主要两个层次
ubi_device
1.init ubi device cdev 的操作为ubi_cdev_operations
主要是ioctrl
2.以char dev 注册 ubi device 到系统
3.ubi_sysfs_init 实现ubi device sysfs 接口部分属性
ubi_volme
对 ubi->vtbl_slots个volum 进行
1.volem cdev 的操作设置为ubi_vol_cdev_operations
2.以char dev 注册 ubi volume dev 到系统
3.volume_sysfs_init 实现ubi volume sysfs 接口部分属性
ubi_init=>ubi_attach_mtd_dev
最后ubi_attach_mtd_dev 中创建了 ubi_thread
该内核线程,用来执行ubi_device 上的works,主要就是后台擦除,和wear level
处理
到这里整个ubi init 基本完成,大体可以看出
每个mtd partition 可以attach 到一个ubi device上,
在每个ubi device上又可以创建很多ubi volume,
而每个ubi volume又被作为一个mtd device 保存于mtd table 中
(上面提到的写平衡和磨损平衡是同一个意思)