目录
参考文档
什么是ubi
ubi主要功能
ubi的主要特性
编程相关
UBI子系统重要的数据结构
UBI设备
UBI卷
volume表
EC头
VID头
EBA表
attach扫描信息
attach扫描leb
EC表
mtd分区和ubi卷的共同点
mtd分区与ubi卷不同之处
参考文档
《UBI-Unsorted Block Images》
http://www.linux-mtd.infradead.org/doc/ubi.html
UBI(上边网址的翻译)
什么是ubi
UBI(Unsorted Block Images)指的是UBI subsystem,其工作在MTD设备上,是MTD设备的高层次表示,对上屏蔽了一些MTD需要处理的问题,如磨损均衡和坏块处理.
ubi子系统可以理解为ubifs的驱动层,它在文件系统层和MTD层之间起到衔接作用。ubi主要功能
- LEB到PEB的映射;(逻辑块是连续的,物理块不一定连续)
- 坏块的回收;
- 物理擦除块磨损均衡处理;
- 处理位翻转现象;
- 衔接MTD设备到UBI;
- 创建UBI卷;
- 挂载UBI文件系统
ubi的主要特性
- ubi提供的卷可以动态创建,转移和重定义大小
- ubi提供整个flash的磨损均衡(可以考虑新的分区布局,把flash划分一个mtd分区,划分多个卷, 都挂载ubifs)
- ubi可以处理坏块问题。
- ubi通过scrubbing机制,把数据损失降低到最小。
编程相关
1. flash控制器驱动(probe函数)中,可以通过设置struct nand_chip的options选项来控制sub-pages:
NAND_NO_SUBPAGE_WRITE、NAND_SUBPAGE_READ
2. ubiformat命令有sub-pages参数,如果不想使用或者不支持sub-pages,将sub-pages大小指定为页大小即可。
还有个VID offset参数,指定为页大小即可
3. SLC和MLC
nand_base.c => nand_scan_ident=> nand_get_flash_type函数会判断是SLC还是MLC,判断函数为:
static inline bool nand_is_slc(struct nand_chip *chip)
{
return chip->bits_per_cell == 1;
}
因此,如果是SLC,则设置struct nand_chip的bits_per_cell成员为1即可。UBI子系统重要的数据结构
- UBI设备。 (struct ubi_device)
- UBI卷、UBI卷表。 (struct ubi_volume、struct ubi_vtbl_record)(卷表:volume table)
- LEB擦除块头:EC头、VID头。(struct ubi_ec_hdr、struct ubi_vid_hdr)(EC:erase count VID:volume identification
- EC表、EBA表;(在内存中构建)。(EBA:Erase Block Association)
- attach扫描信息、扫描leb。 (struct ubi_scan_info、struct ubi_scan_leb)
- 损耗均衡模块
UBI设备
struct ubi_device {
struct cdev cdev;
struct device dev;
int ubi_num; //UBI 设备的标号,在 ubiattach 用户程序时以-d 选项来输入
char ubi_name[sizeof(UBI_NAME_STR)+5]; //ubi 设备的名称
int vol_count; //在该 UBI 设备中有多少个 volume
struct ubi_volume *volumes[UBI_MAX_VOLUMES+UBI_INT_VOL_COUNT];
spinlock_t volumes_lock;
int ref_count;
int image_seq;
int rsvd_pebs; //保留的 LEB 数目
int avail_pebs; //可用的 LEB 数目
int beb_rsvd_pebs; //为坏块处理而保留的 LEB 数目
int beb_rsvd_level; //为坏块处理而保留的 LEB 的正常数目
int autoresize_vol_id;
int vtbl_slots;
int vtbl_size; //volume 表的大小(bytes)
struct ubi_vtbl_record *vtbl; //内存中 volume 表的拷贝
struct mutex device_mutex;
int max_ec; //最大的 erase counter
/* Note, mean_ec is not updated run-time - should be fixed */
int mean_ec; //平均 erase counter
/* EBA sub-system's stuff */
unsigned long long global_sqnum;
spinlock_t ltree_lock;
struct rb_root ltree;
struct mutex alc_mutex;
/* Wear-leveling sub-system's stuff */
struct rb_root used; //一个红黑树,其中是已用的 block
struct rb_root erroneous; // RB-tree of erroneous used physical eraseblocks
struct rb_root free; //红黑树的根,其中是没有用到的 block
struct rb_root scrub; //需要擦除的 blcok
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;
struct ubi_wl_entry **lookuptbl; //一个 struct ubi_wl_entry 类型的数组,以 pnum 为下标,
//记录该UBI 设备的每一个 block,可快速为peb找到a &struct ubi_wl_entry object
struct ubi_wl_entry *move_from; // physical eraseblock from where the data is being moved
struct ubi_wl_entry *move_to; // physical eraseblock where the data is being moved to
int move_to_put; //标志位,用于标志目的 LEB 是否被 put
struct list_head works; // list of pending works
int works_count; // count of pending works
struct task_struct *bgt_thread; //UBI 的后台进程
int thread_enabled;
char bgt_name[sizeof(UBI_BGT_NAME_PATTERN)+2];//后台进程的名字
struct notifier_block reboot_notifier; //内核通知链
/* I/O sub-system's stuff */
long long flash_size; //MTD 分区的大小
int peb_count; //LEB 的数目
int peb_size; //LEB 的大小(每一个 block 的大小)
int bad_peb_count; //坏块数目
int good_peb_count; //能使用的 LEB 数目
int erroneous_peb_count;
int max_erroneous;
int min_io_size; //最小操作单元的大小,也就是一个 page 的大小
int hdrs_min_io_size;
int ro_mode;
int leb_size; //逻辑块的大小,一般等于 peb_size
int leb_start; //逻辑块块从物理块中那一块开始算,也就是之前的物理块保留用于其他目的
int ec_hdr_alsize; // size of the EC header aligned to @hdrs_min_io_size
int vid_hdr_alsize; //size of the VID header aligned to @hdrs_min_io_size
int vid_hdr_offset; //VID 头部在一块之中的偏移量。一般是一个 pagesize
int vid_hdr_aloffset; // starting offset of the VID header aligned to @hdrs_min_io_size
int vid_hdr_shift // contains @vid_hdr_offset - @vid_hdr_aloffset
unsigned int bad_allowed:1;
unsigned int nor_flash:1; // non-zero if working on top of NOR flash
struct mtd_info *mtd; //指向 MTD 分区信息,我们知道, UBI 层是构建在 MTD 层之上的。
void *peb_buf1; //一个缓冲区,大小为一个 block 的大小
void *peb_buf2; //一个缓冲区,大小为一个 block 的大小
struct mutex buf_mutex;
struct mutex ckvol_mutex;
#ifdef CONFIG_MTD_UBI_DEBUG_PARANOID
void *dbg_peb_buf;
struct mutex dbg_buf_mutex;
#endif
};UBI卷
可分为静态卷和动态卷,静态卷只读,由CRC32校验和保护;动态卷是可读写的,该数据完整性由上层负责。根据用途,UBI卷可分为用户卷和内部卷,内部卷外部不可见,仅供UBI内部使用,现在UBI中只有一个内部卷:布局(layout)卷,其余全是用户卷。
struct ubi_volume {
struct device dev;
struct cdev cdev;
struct ubi_device *ubi; //该 volume 在哪一个 UBI 设备上
int vol_id; //volume 标号
int ref_count; //引用次数(不知道什么用途)
int readers; // number of users holding this volume in read-only mode
int writers; // number of users holding this volume in read-write mode
int exclusive; // whether somebody holds this volume in exclusive mode
int reserved_pebs; //该 volume 中保留的 peb 数
int vol_type; //volume 类型
int usable_leb_size; // logical eraseblock size without padding
int used_ebs //可用 PEB 数目
int last_eb_bytes; // how many bytes are stored in the last logical eraseblock
long long used_bytes; //已用空间大小
int alignment;
int data_pad;
int name_len; //volume 名字的长度
char name[UBI_VOL_NAME_MAX + 1];
int upd_ebs;
int ch_lnum; // LEB number which is being changing by the atomic LEB change operation
//这样在后面修改 LEB 数据的操作中可以看到
int ch_dtype;
long long upd_bytes;
long long upd_received;
void *upd_buf;
int *eba_tbl; // EBA table of this volume,极其重要, LEB 到 PEB 得影射关系需要查该表来获得
unsigned int checked:1;
unsigned int corrupted:1;
unsigned int upd_marker:1;
unsigned int updating:1;
unsigned int changing_leb:1;
unsigned int direct_writes:1;
};volume表
UBI卷可分为用户卷和内部卷,内部卷外部不可见,仅供UBI内部使用,现在UBI中只有一个内部卷:布局(layout)卷,其余全是用户卷。
内部卷保存的是volume table,包含每个卷信息,比如卷的大小、卷更新标记、卷号等。维护在flash上,volume表存储在layout卷中,layout卷包含两个LEB,每个LEB都包含一份volume表。volume表仅在ubi卷被创建、删除和重定义大小时改变。volume表是以 struct ubi_vtbl_record 数据结构的格式来保持的。
struct ubi_vtbl_record {
__be32 reserved_pebs; // how many physical eraseblocks are reserved for this volume
__be32 alignment; // volume alignment
__be32 data_pad; // how many bytes are unused at the end of the each physical eraseblock
// to satisfy the requested alignment
__u8 vol_type; //volume 的类型,分为动态和静态两种,动态 volume 可以动态的改变它的大小
__u8 upd_marker;
__be16 name_len; //volume name length
__u8 name[UBI_VOL_NAME_MAX+1]; //volume name
__u8 flags;
__u8 padding[23];
__be32 crc;
} __attribute__ ((packed));EC头
包含物理擦除块的擦除次数以及其它一些不太重要的信息。64bytes。
EC头位置:在0个擦除块的第0页,所以偏移量是0。
struct ubi_ec_hdr {
__be32 magic; // #define UBI_EC_HDR_MAGIC 0x55424923 // "UBI#"
__u8 version;
__u8 padding1[3];
__be64 ec; /* Warning: the current limit is 31-bit anyway! */
__be32 vid_hdr_offset;
__be32 data_offset;
__be32 image_seq;
__u8 padding2[32];
__be32 hdr_crc;
} __attribute__ ((packed));VID头
包含属于这个PEB的卷ID和逻辑块号,及其它信息。512bytes
VID头位置:
此数值即EC头的vid_hdr_offset的值。
若NAND支持sub-pages,则在第1个sub-pages的位置,偏移量为一个sub-pages
若NAND不支持在擦除块的第1页,偏移量为一页大小。
每个非坏的PEB都包含一个EC头和VID头,这也是逻辑擦除块小于PEB大小的原因,EC头和VID头占用
了一些空间(两页大小)。
在上面 EC 头部中有一个成员变量是 vid_hdr_offset,是指 vid_hdr 在 FLASH 中的偏移量
struct ubi_vid_hdr {
__be32 magic;
__u8 version;
__u8 vol_type;
__u8 copy_flag;
__u8 compat;
__be32 vol_id;
__be32 lnum;
__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));
这其中最重要的成员变量是__be32 vol_id 和__be32 lnum。vol_id 是标示该 LEB 属于哪儿一个 volume。 Lnum 是指与该 PNUM 相对于的 lnum。对于上层而言,我们操作的是逻辑块,也就是 lnum,但是最终需要将数据写进 pnum 中去,在 ubi_eba_write_leb 函数中有这样的一句:pnum = vol->eba_tbl[lnum];每一个 volume 都有一个 eba_tbl,是在扫描的时候建立的。如果该 lnum 没有影射,那么调用 ubi_wl_get_peb 来获得一个 pnum,并相应的修改 volume 的 eba_tbl。EBA表
EBA(Eraseblock Association)包含所有LEB到PEB的映射信息。
EBA表的map和unmap操作
对EBA表最重要的操作是map和unmap,map过程是首先找到相应的PEB,然后将VID头写入PEB,然后修改内存中相应的EBA表。unmap首先解除LEB与相应PEB的映射,然后调度擦除该PEB,unmap并不会等到擦除完成,该擦除由UBI后台进程负责。
attach扫描信息
这个结构体是在 attach 的过程中使用的。在 attach 的过程中, UBIFS 需要获知该设备上每一个 PEB 的状态,然后为重新挂载文件系统做准备。
struct ubi_scan_info {
struct rb_root volumes;//volume 的红黑树的根节点
//下面是 4 个链表,是在扫描的过程将扫描的 block 进行分类,然后连接到下面 4 个链表中的其中一个。
struct list_head corr;
struct list_head free;
struct list_head erase;
struct list_head alien;
int bad_peb_count; //坏块数
int vols_found; //volume 数
int highest_vol_id; //volume 的最高标号
int alien_peb_count;int is_empty;//标志位,用于表示该 UBI 设备是否为空的,在上面所说的扫描过程被置位
int min_ec; //最小 erase counter
int max_ec; //最大 erase counter
unsigned long long max_sqnum;//64 位的 sqnum
int mean_ec; //平均 erase counter
uint64_t ec_sum;
int ec_count;
int corr_count;
};attach扫描leb
在上面的 struct ubi_scan_info 中我们说到了在 attach 操作中的扫描过程,并且说到了 struct
ubi_scan_info 中的 4 个队列,是将扫描的每一个 block 的信息抽象,然后挂载到这些队列中
去,下面就简单的说一下对于 block 扫描信息的抽象。
struct ubi_scan_leb {
int ec; //erase counter,用于均衡损耗目的,以后详细介绍
//每一个卷的 eba_table 就是由下面两个成员构成的。
int pnum; //物理块标号
int lnum; //逻辑块标号
int scrub;
unsigned long long sqnum;
union {
struct rb_node rb;
struct list_head list;
} u;
};EC表
EC表包含着每一个PEB的擦写次数。
EBA表和EC表是在每次挂载MTD设备时建立,这也意味着UBI必须扫描整个Flash,从每个PEB读取VID和EC头部以构造EBA和EC表。相对于volume表,EBA和EC表变动较大,因为每次对PEB写时都有可能修改表。每个UBI卷都有一个EBA表,用eba_tbl结构表示。mtd分区和ubi卷的共同点
- 它们都是由物理块组成,但是ubi卷是由逻辑块,而mtd分区主要是基于物理块。
- 都支持三种基本操作:读写擦
mtd分区与ubi卷不同之处
- ubi卷基于磨损均衡使得上层变得简单。
- ubi卷没有坏块,这也方便了上层的操作。
- ubi卷可以动态调整大小,而mtd分区是固定的。
- ubi处理位翻转情况,这又方便了上层处理。
- ubi提供卷升级操作(volume update),可以容易检测到中断的软件更新,并且可以恢复。
- ubi提供原子逻辑块修改操作,在修改逻辑块内容的时候发生突然重启但是数据也不会丢失。这对上层软件来讲可能比较有用。
- ubi支持un-map操作,可以解除逻辑卷到物理块的映射。解除映射后,物理块会被安排擦除,这可以作为块擦除的机制,它的速度很快,方便上层软件的操作。
因为ubi能够处理坏块和位翻转,所以ubi还可以提供一个块设备,给那些基于块设备的文件系统。
现在有一个驱动模块叫glubi,它在ubi基础之上模仿了mtd设备,这样就可以支持一些基于mtd设备的上层软件,比如jiffs文件系统。它就可以在ubi之上运行,而且可以享受到ubi提供的便利(ubi处理了很多地产棘手的问题)。