ubi简介

目录

参考文档

什么是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主要功能

  1. LEB到PEB的映射;(逻辑块是连续的,物理块不一定连续)
  2. 坏块的回收;
  3. 物理擦除块磨损均衡处理;
  4. 处理位翻转现象;
  5. 衔接MTD设备到UBI;
  6. 创建UBI卷;
  7. 挂载UBI文件系统

ubi的主要特性

  1. ubi提供的卷可以动态创建,转移和重定义大小
  2. ubi提供整个flash的磨损均衡(可以考虑新的分区布局,把flash划分一个mtd分区,划分多个卷, 都挂载ubifs)
  3. ubi可以处理坏块问题。
  4. 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子系统重要的数据结构

  1. UBI设备。                                     (struct ubi_device)
  2. UBI卷、UBI卷表。                       (struct ubi_volume、struct ubi_vtbl_record)(卷表:volume table
  3. LEB擦除块头:EC头、VID头。(struct ubi_ec_hdr、struct ubi_vid_hdr)(EC:erase count  VID:volume identification
  4. EC表、EBA表;(在内存中构建)。(EBA:Erase Block Association
  5. attach扫描信息、扫描leb。             (struct ubi_scan_info、struct ubi_scan_leb)
  6. 损耗均衡模块

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卷的共同点

  1. 它们都是由物理块组成,但是ubi卷是由逻辑块,而mtd分区主要是基于物理块。
  2. 都支持三种基本操作:读写擦

mtd分区与ubi卷不同之处 

  1. ubi卷基于磨损均衡使得上层变得简单。
  2. ubi卷没有坏块,这也方便了上层的操作。
  3. ubi卷可以动态调整大小,而mtd分区是固定的。
  4. ubi处理位翻转情况,这又方便了上层处理。
  5. ubi提供卷升级操作(volume update),可以容易检测到中断的软件更新,并且可以恢复。
  6. ubi提供原子逻辑块修改操作,在修改逻辑块内容的时候发生突然重启但是数据也不会丢失。这对上层软件来讲可能比较有用。
  7. ubi支持un-map操作,可以解除逻辑卷到物理块的映射。解除映射后,物理块会被安排擦除,这可以作为块擦除的机制,它的速度很快,方便上层软件的操作。

        因为ubi能够处理坏块和位翻转,所以ubi还可以提供一个块设备,给那些基于块设备的文件系统。
        现在有一个驱动模块叫glubi,它在ubi基础之上模仿了mtd设备,这样就可以支持一些基于mtd设备的上层软件,比如jiffs文件系统。它就可以在ubi之上运行,而且可以享受到ubi提供的便利(ubi处理了很多地产棘手的问题)。

 

 

你可能感兴趣的:(Linux)