测试ubi镜像的方法:
sudo modprobe mtd
sudo modprobe mtdblock
sudo modprobe nandsim first_id_byte=0xec second_id_byte=0xa1 third_id_byte=0x00 fourth_id_byte=0x15
#其中fourth_id_byte,含义如下
sudo chmod 660 /dev/mtd0*
ls -lah /dev/mtd*
cat /proc/mtd
mtdinfo /dev/mtd0
sudo modprobe ubi
echo "add" > /sys/devices/virtual/misc/ubi_ctrl/uevent
sudo ubiformat /dev/mtd0 -f test.ubi -O 2048
sudo ubiattach /dev/ubi_ctrl -m 0 -O 2048
sudo mount -t ubifs /dev/ubi0_0 ./mount
对于squashfs格式的卷,mount命令如下:
sudo ubiblock -c /dev/ubi0_0 //生成/dev/ubiblock0_0
sudo mount -t squashfs -o loop /dev/ubiblock0_0 ./mount
以上是页大小为2048的UBI测试,如果是4096的页大小,改用命令:
sudo modprobe nandsim first_id_byte=0xec second_id_byte=0xd3 third_id_byte=0x10 fourth_id_byte=0xa6
------------------------------------------------------------------
UBIFS文件系统 :https://blog.youkuaiyun.com/shichaog/article/details/45932339
------------------------------------------------------------------
ubi 是位于 ubifs 文件系统和mtd 层之间,负责ubi 卷管理
先看下两个 on-flash 数据结构 (ubi-media.h)
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 中
(上面提到的写平衡和磨损平衡是同一个意思)