ubi 是位于 ubifs 文件系统和mtd 层之间,负责ubi 卷管理
先看下两个 on-flash 数据结构 (ubi-media.h)
1. UBI erase counter header.
- 1. UBI erase counter header.
- struct ubi_ec_hdr { /* 64 byte */
- __be32 magic;
- __u8 version;
- __u8 padding1[3];
- __be64 ec; /* Warning: the current limit is 31-bit */
- __be32 vid_hdr_offset;
- __be32 data_offset;
- __u8 padding2[36];
- __be32 hdr_crc;
- } __attribute__ ((packed));
ec 表示该逻辑块被擦除过的次数
vid_hdr_offset 表示vid 头的偏移,一般跟在ec header 后面
data_offset 表示用户数据的偏移位置
2. UBI volume identifier header
虽然叫卷标识头,但实际是描述logical 块的信息,
可以看ubi_eba_write_leb 中关于未映射logical block 处理部分的代码
- struct ubi_vid_hdr {
- __be32 magic;
- __u8 version;
- __u8 vol_type;
- __u8 copy_flag;
- __u8 compat;
- __be32 vol_id;
- __be32 lnum; /* leb num !!! */
- __u8 padding1[4];
- __be32 data_size;
- __be32 used_ebs;
- __be32 data_pad;
- __be32 data_crc;
- __u8 padding2[4];
- __be64 sqnum;
- __u8 padding3[12];
- __be32 hdr_crc;
- } __attribute__ ((packed));
其中主要字段:
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,
主要字段介绍下:
- struct ubi_device {
- /* 下面这两个 结构,表示 ubi device 是个char device
- 并且 ,属于linux 2.6的设备模型,支持sysfs */
- struct cdev cdev;
- struct device dev;
-
- int ubi_num;
- char ubi_name[sizeof(UBI_NAME_STR)+5];
- /*下面表示该ubi device 上有几个卷,以及ubi volume数组
- 该数组最大外部卷数(128)+内部卷数(1) */
- int vol_count;
- struct ubi_volume *volumes[UBI_MAX_VOLUMES+UBI_INT_VOL_COUNT];
-
- spinlock_t volumes_lock;
- int ref_count;
- int rsvd_pebs; /*保留的物理块 */
- int avail_pebs; /* 有效物理块*/
- int beb_rsvd_pebs; /* 为处理坏块保留的peb*/
-
- /* 同上,但只是个百分比,由CONFIG_MTD_UBI_BEB_RESERVE 决定*/
- int beb_rsvd_level;
- /* ubi init 之后,有该标志的必须被resize */
- int autoresize_vol_id;
-
- /* 卷表(vtabl)中 slot数 ,和vtbl 的大小
- 具体计算方法(ubi_read_volume_table):
- ubi->vtbl_slots = ubi->leb_size / UBI_VTBL_RECORD_SIZE;
- if (ubi->vtbl_slots > UBI_MAX_VOLUMES)
- ubi->vtbl_slots = UBI_MAX_VOLUMES;
- ubi->vtbl_size = ubi->vtbl_slots * UBI_VTBL_RECORD_SIZE;
- */
- int vtbl_slots;
- int vtbl_size;
-
- /* 放卷信息的结构,也是on flash 的,在内部卷中,
- 将它读入内存 */
- struct ubi_vtbl_record *vtbl;
- struct mutex volumes_mutex;
- int max_ec;
- /* Note, mean_ec is not updated run-time - should be fixed */
- int mean_ec;
- /* EBA sub-system's stuff */
- /*EBA => Eraseblock Association */
- unsigned long long global_sqnum;
- spinlock_t ltree_lock;
- struct rb_root ltree;
- struct mutex alc_mutex;
- /* Wear-leveling sub-system's stuff */
- /* 已使用的pebs 的rb tree 根*/
- struct rb_root used;
- /* 空闲的pebs 的rb tree 根*/
- struct rb_root free;
- /* 需要擦写的pebs 的rb tree 根*/
- struct rb_root scrub;
- /*上面这写rb-tree 都是以pnum ,物理块号做权重*/
-
- /*用于磨损平衡保护的队列 ,联接被保护的物理块*/
- struct list_head pq[UBI_PROT_QUEUE_LEN];
- int pq_head;
- spinlock_t wl_lock;
- struct mutex move_mutex;
- struct rw_semaphore work_sem;
- int wl_scheduled;
- /* ubi_wl_entry ,peb 的写平衡entry,可能挂接到不同的地方*/
- struct ubi_wl_entry **lookuptbl;
- struct ubi_wl_entry *move_from;
- struct ubi_wl_entry *move_to;
- int move_to_put;
- struct list_head works;
- int works_count;
- struct task_struct *bgt_thread;
- int thread_enabled;
- char bgt_name[sizeof(UBI_BGT_NAME_PATTERN)+2];
- /* I/O sub-system's stuff */
- /* 从下面(io_init)可以看到,这些ubi 变量与 mtd的关系
- ubi->peb_size = ubi->mtd->erasesize;
- ubi->peb_count = mtd_div_by_eb(ubi->mtd->size, ubi->mtd);
- ubi->flash_size = ubi->mtd->size;
- ubi->min_io_size = ubi->mtd->writesize;
- ubi->hdrs_min_io_size = ubi->mtd->writesize >> ubi->mtd->subpage_sft;
- ubi->ec_hdr_alsize = ALIGN(UBI_EC_HDR_SIZE, ubi->hdrs_min_io_size);
- ubi->vid_hdr_alsize = ALIGN(UBI_VID_HDR_SIZE, ubi->hdrs_min_io_size);
- ubi->leb_start = ubi->vid_hdr_offset + UBI_EC_HDR_SIZE;
- ubi->leb_start = ALIGN(ubi->leb_start, ubi->min_io_size);
- ubi->leb_size = ubi->peb_size - ubi->leb_start;
- */
- long long flash_size;
- int peb_count;
- int peb_size; /* peb_size: physical eraseblock size */
- int bad_peb_count;
- int good_peb_count;
- int min_io_size;
- int hdrs_min_io_size;
- int ro_mode;
-
- /* logical eraseblock size
- 其实就是leb 中去掉vid hdr,ec hdr
- 并且对齐到write size 后,剩余的部分*/
- int leb_size;
- int leb_start;
- int ec_hdr_alsize;
- int vid_hdr_alsize;
- int vid_hdr_offset;
- int vid_hdr_aloffset;
- int vid_hdr_shift;
- int bad_allowed;
- struct mtd_info *mtd;
- void *peb_buf1;
- void *peb_buf2;
- struct mutex buf_mutex;
- struct mutex ckvol_mutex;
- struct mutex mult_mutex;
- };
ubi_init=>ubi_attach_mtd_dev=>attach_by_scanning
=>ubi_scan
其中 相关数据结构
- struct ubi_scan_info {
- struct rb_root volumes;
- struct list_head corr;
- struct list_head free;
- struct list_head erase;
- struct list_head alien;
- int bad_peb_count;
- int vols_found;
- int highest_vol_id;
- int alien_peb_count;
- int is_empty;
- int min_ec;
- int max_ec;
- unsigned long long max_sqnum; /* 和ubi_vid_hdr 的sqnum 有关*/
- int mean_ec;
- uint64_t ec_sum;
- int ec_count;
- };
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 :
- struct ubi_scan_leb {
- int ec;
- int pnum;
- int lnum;
- int scrub;
- unsigned long long sqnum;
- union {
- struct rb_node rb;
- struct list_head list;
- /* 表示该ubi_scan_leb 可以连接到per-volume RB-tree 或者 eraseblock list !!! */
- } u;
- };
scan 中获得的卷信息
scanning information about a volume:
- struct ubi_scan_volume {
- int vol_id;
- int highest_lnum;
- int leb_count;
- int vol_type;
- int used_ebs;
- int last_data_size;
- int data_pad;
- int compat;
- struct rb_node rb; /* 向上连到scan info */
- struct rb_root root; /* root ,向下连到 scan leb info */
- };
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
- struct ubi_vtbl_record {
- /* 卷上保留 物理块数*/
- __be32 reserved_pebs;
- __be32 alignment;
- /* 每个物理为对齐而保留的byte数*/
- __be32 data_pad;
- /* static or dynamic */
- __u8 vol_type;
- /* 卷记录开始更新,还没完成标志*/
- __u8 upd_marker;
- __be16 name_len;
- __u8 name[UBI_VOL_NAME_MAX+1];
- __u8 flags;
- __u8 padding[23];
- __be32 crc;
- } __attribute__ ((packed));
这个结构也是个 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 结构:
- struct ubi_volume {
- struct device dev;
- struct cdev cdev;
- struct ubi_device *ubi;
- int vol_id;
- int ref_count;
- int readers;
- int writers;
- int exclusive;
- /* 卷上保留的peb */
- int reserved_pebs;
- int vol_type;
- int usable_leb_size;
- /* 卷上包含数据的leb */
- int used_ebs;
- /* 卷上包含数据的最后leb的实际字节数 */
- int last_eb_bytes;
- /* 卷上包含数据的总节数*/
- long long used_bytes;
- int alignment;
- int data_pad;
- int name_len;
- char name[UBI_VOL_NAME_MAX + 1];
-
- /*执行UBI_IOCVOLUP 卷更新命令时,更新的bytes
- 转换成不带data_pad的usable_leb_size(leb_size-data_pad)
- 后的块数 */
- int upd_ebs;
-
- /* UBI_IOCEBCH 相关 (ubi_start_leb_change)*/
- int ch_lnum;
- int ch_dtype;
- long long upd_bytes;
- long long upd_received;
- void *upd_buf;
- /* 这个很重要,leb 到peb 的映射表 */
- int *eba_tbl;
- /* 一些标志*/
- unsigned int checked:1;
- unsigned int corrupted:1;
- unsigned int upd_marker:1;
- /*卷正在被更新*/
- unsigned int updating:1;
-
- unsigned int changing_leb:1;
- /* UBI_IOCSETPROP */
- unsigned int direct_writes:1;
- };
下面代码显示ubi_volume的相关信息如何从vtbl(ubi_vtbl_record)中获得的:
- vol->reserved_pebs = be32_to_cpu(vtbl[i].reserved_pebs);
- vol->alignment = be32_to_cpu(vtbl[i].alignment);
- vol->data_pad = be32_to_cpu(vtbl[i].data_pad);
- vol->vol_type = vtbl[i].vol_type == UBI_VID_DYNAMIC ?
- UBI_DYNAMIC_VOLUME : UBI_STATIC_VOLUME;
- vol->name_len = be16_to_cpu(vtbl[i].name_len);
- vol->usable_leb_size = ubi->leb_size - vol->data_pad;
- memcpy(vol->name, vtbl[i].name, vol->name_len);
- vol->name[vol->name_len] = '\0';
- vol->vol_id = i;
接下来ubi_scan_find_sv(si, i);
从扫描信息ubi_scan_info 中根据volume id 找到volume scan info (sv)
接下来用sv (ubi_scan_volume)的信息更新vol(ubi_volume)
- vol->used_ebs = sv->used_ebs;
- vol->used_bytes =
- (long long)(vol->used_ebs - 1) * vol->usable_leb_size;
- vol->used_bytes += sv->last_data_size;
- vol->last_eb_bytes = sv->last_data_size;
上面这些操作,循环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->rsvd_pebs += reserved_pebs;
- ubi->avail_pebs -= reserved_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
其中数据结构:
- struct ubi_wl_entry {
- union {
- /* link 到free 或者used RB tree*/
- struct rb_node rb;
- /* link 到写平衡保护队列(ubi_device 的
- struct list_head pq[UBI_PROT_QUEUE_LEN];)*/
- struct list_head list;
- } u;
- int ec; /*erase count */
- int pnum; /* phiscal block number */
- };
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 中
(上面提到的写平衡和磨损平衡是同一个意思)