UBI系统详解

一.UBI是什么?

UBI表示: Unsorted Block Images

UBI是用于raw flash设备单个mtd分区的卷管理系统,管理单个mtd分区上的多个逻辑卷,并在单个mtd分区上的多个逻辑卷之间实现磨损均衡;

UBI在整个系统中的位置如下图所示:

抽象理解UBI:通过MTD提供的读,写,擦除操作为FlashFile System提供简单的读,写接口;

二.UBI想要解决什么问题?

既然MTD就为操作raw flash提供了读,写,擦除操作,为什么还要多经过一层UBI再为FlashFile System提供读写操作?

回答这个问题前,先搞清楚什么是raw flash以及raw flash有什么特点;

nand flash, norflash, OneNand flash 都是raw flash,基本特点是需要3个接口才能完整操作flash:读(read),写(write),擦除(erase);

在写数据到raw flash之前必须要先擦除才能写,一次擦除的大小还必须是芯片手册规定的字节(eraseblock size),比如norflash的erasesize有4KB,64KB等,nandflash的erasesize有128KB,256KB等;(eraseblock即:块,擦写块)

对于nand flash还有坏块和位反转的问题,nandflash读写时需要跳过坏块;从nand flash读取数据时如果发生了位反转,在不超过nand flash ECC可校验的位数时,可以被校正过来,但是反转的位数过多时,虽然可以正确校正过来,但这个块可能快要坏了,需要及时处理;

不管是nor flash还是nand flash,每个eraseblock 都是有擦写次数限制,超过擦写次数就可能变为坏块,所以最好不要对着一个eraseblock反复擦写;

除了以上raw flash自身的问题,还有个普遍的问题,在写数据的过程中,如果突然掉电,还需要考虑数据丢失的问题;

所以为了正确操作raw flash,除了基本的读,写操作,还需要关注写之前要擦除,跳过坏块,位反转,以及eraseblock的擦写次数,很是繁琐;对于上层应用,存储设备就应该只关心读,写操作,其它繁琐的操作就应该打包交给独立的中间层;UBI就是这样的中间层,利用MTD提供的接口,为上层的FlashFile System提供简单的读,写接口;

当然,除了UBI还有另外一种解决思路:把繁琐的操作交给独立的硬件控制器处理,即Flash Translation Layer (FTL);比如MMC,eMMC,SD,mini-SD等,这些存储设备的内部可能也是nand flash,跟raw flash不同的是,这些存储设备内部还集成了一个控制器(micro-controller),用于处理坏块,位反转,磨损均衡等繁琐的操作,对外提供简单的读写操作;

在嵌入式设备上,如果不支持Flash Translation Layer,使用UBI是个非常好的选择!

三.UBI如何解决问题?

先列举下UBI需要处理的重点问题:

1. 使用MTD提供的接口,对raw flash进行正常的读,写,擦除操作;

2. 坏块处理;

3. 磨损均衡;

4. 处理位反转;

5. 写数据过程中的掉电保护;

在说明UBI如何解决上面的问题之前,先来看看UBI是如何组织和管理raw flash的;

3.1 UBI如何组织和管理raw flash

UBI的组织结构如下图所示:

简单来说:一个ubi设备包含多个volume,一个volume有多个逻辑擦写块,每个逻辑擦写块都可以映射到物理擦写块;

注:下文涉及到的结构体都出自 linux-5.4.132

UBI必须要基于MTD层,所以UBI的处理对象也是raw flash上的某一个mtd分区;

一般来说,raw flash由若干个page组成一个erase block,由若干个erase block组成一个raw flash;

nand flash的erase block组成示意图:

上图中的 Data Buffer(2KB)+128byte(oob大小) = 1 page;64个page=1个erase block

raw flash的读写单元是擦写块(erase block);虽然通过MTD接口也可以只读一个page(多个page组成一个erase block),但MTD内部实现上也是先把对应的erase block读取到内存中,再把对应的page拷贝给你;在UBI系统中raw flash的擦写块被称为物理擦写块(physical eraseblock, PEB);

但是物理擦写块中有坏块,会导致读写过程中需要跳过坏块,不连续,UBI读写操作并不直接作用于物理擦写块,而是使用逻辑擦写块(logic eraseblock, LEB),逻辑擦写块只是软件上的概念,逻辑擦写块与物理擦写块之间存在映射关系,读写的时候通过逻辑擦写块找到物理擦写块,再把数据从物理擦写块中读出来或者写进去;

LEB到PEB的map示意图如下:

UBI按逻辑卷(volume)来管理mtd分区,volume有volume id, volume type(静态和动态2种类型),volume的容量大小等属性,逻辑擦写块到物理擦写块的映射,在UBI系统中用 struct ubi_volume来表示volume,主要数据成员如下所示:

/* linux-5.4.132/drivers/mtd/ubi/ubi.h */
struct ubi_volume {
    ......
    struct ubi_device *ubi;           /* reference to the UBI device description object */
    int vol_id;                       /* volume ID */

    int reserved_pebs;                /* how many physical eraseblocks are reserved for this volume */
    int vol_type;                     /* volume type (%UBI_DYNAMIC_VOLUME or %UBI_STATIC_VOLUME) */
    ......
    int name_len;                     /* volume name length */
    char name[UBI_VOL_NAME_MAX + 1];  /* volume name */
    ......
    struct ubi_eba_table *eba_tbl;    /* EBA table of this volume (LEB->PEB mapping) */
    unsigned int skip_check:1;        /* %1 if CRC check of this static volume should be skipped. */
    unsigned int checked:1;           /* %1 if this static volume was checked */
    unsigned int corrupted:1;         /* %1 if the volume is corrupted (static volumes only) */
    unsigned int upd_marker:1;        /* %1 if the update marker is set for this volume */
    unsigned int updating:1;          /* %1 if the volume is being updated */
    unsigned int changing_leb:1;      /* %1 if the atomic LEB change ioctl command is in progress */
    unsigned int direct_writes:1;     /* %1 if direct writes are enabled for this volume */

    ......
};

ubi_volume结构体中的eba_tbl用于记录volume使用了哪些逻辑擦写块,以及逻辑擦写块到物理擦写块的映射; eba_tbl 的数据类型为struct ubi_eba_table,结构体定义如下所示:

struct ubi_eba_entry {
    int pnum;  /* the physical eraseblock number attached to the LEB */
};

struct ubi_eba_table {
    struct ubi_eba_entry *entries; /* the LEB to PEB mapping (one entry per LEB). */
};

ubi_eba_table结构体中的entries是个数组,数组长度为ubi_volume结构体中的reserved_pebs,即为这个volume分配的物理擦写块个数;数组元素类型为 struct ubi_eba_entry,这个结构体只包含一个成员pnum;

问题:逻辑擦写块如何映射到物理擦写块?struct ubi_eba_entry结构体中的pnum是什么?

以nand flash来举例说明,假设nand flash的erase block size=128KB,这个nand flash上有一个96MB的mtd分区,假设这个分区名称为mtd1,ubi attach到mtd1;则mtd1总共包含有 96MB/128KB=768个erase block;

如果要读取pnum=10的物理擦写块的内容,则pnum=10在mtd1分区的offset=10*(erase block size)=10*128KB=1280KB,有了offset后就可以通过mtd提供的接口把这个物理擦写块的内容读出来;

如果给定逻辑擦写块lnum=3,通过ubi_eba_table结构体中的entries[lnum].pnum 获取到对应的pnum(因为mtd1有768个erase block,这个值的取值范围:0~767),假设entries[lnum].pnum=10,则lnum=3被映射到pnum=10,即通过逻辑擦写块lnum=3可以读写物理擦写块pnum=10(mtd1分区offset=1280KB)的数据;

volume又分内部volume和用户创建的volume;内部volume对外不可见,比如layer volume;

layer volume也是用struct ubi_volume结构体来表示,只不过这个volume内部物理擦写块记录的是用户创建了多少个volume,每个volume的名称,volume type,volume的容量等,在layer volume中使用 struct ubi_vtbl_record 来描述一个volume的信息,主要数据成员如下所示:

/* linux-5.4.132/drivers/mtd/ubi/ubi-media.h */
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 type (%UBI_DYNAMIC_VOLUME or %UBI_STATIC_VOLUME) */
    __u8    upd_marker;                 /* if volume update was started but not finished */
    __be16  name_len;                   /* volume name length */
    __u8    name[UBI_VOL_NAME_MAX+1];   /* the volume name */
    __u8    flags;                      /* volume flags (%UBI_VTBL_AUTORESIZE_FLG) */
    __u8    padding[23];                /* reserved, zeroes */
    __be32  crc;                        /* a CRC32 checksum of the record */
} __packed;

用户创建了多少个volume,就有多少个 struct ubi_vtbl_record存储在layer volume中;

问题:ubi系统是如何保存组织信息?

如上可知,ubi的组织信息主要包括 卷(volume), 物理擦写块(PEB),逻辑擦写块(LEB),以及逻辑擦写块到物理擦写块的映射;

因为磨损均衡也是ubi的核心功能,磨损均衡必须要知道每个物理擦写块的擦写次数;所以ubi的组织信息必须还要包含每个物理擦写块的擦写次数;

如果将组织信息保存在DDR内存中,则断电重启后就丢失了,所以这些组织信息还必须要保存到raw flash中;

为了保存每个物理擦写块的擦写次数,ubi定义了struct ubi_ec_hdr结构体,并保存在每个物理擦写块的第一个page的开始位置;

struct ubi_ec_hdr {
    __be32  magic;           /* erase counter header magic number (%UBI_EC_HDR_MAGIC) */
    __u8    version;         /* version of UBI implementation which is supposed to accept this UBI image */
    __u8    padding1[3];     /* reserved for future, zeroes */
    __be64  ec;              /* the erase counter, Warning: the current limit is 31-bit anyway! */
    __be32  vid_hdr_offset;  /* where the VID header starts */
    __be32  data_offset;     /* where the user data start */
    __be32  image_seq;       /* image sequence number */
    __u8    padding2[32];    /* reserved for future, zeroes */
    __be32  hdr_crc;         /* erase counter header CRC checksum */
} __packed;

ec字段保存的就是物理擦写块的擦写次数;

为了保存每个物理擦写块属于哪个volume,被映射到哪个逻辑擦写块,ubi定义了struct ubi_vid_hdr结构体,并保存在每个物理擦写块的第二个page的开始位置,如果raw flash支持读写sub page,则ubi_vid_hdr结构体保存在每个擦写块的第一个page的第二个sub page开始位置;

struct ubi_vid_hdr {
    __be32  magic;         /* volume identifier header magic number (%UBI_VID_HDR_MAGIC) */
    __u8    version;       /* BI implementation version which is supposed to accept this UBI */
    __u8    vol_type;      /* volume type (%UBI_VID_DYNAMIC or %UBI_VID_STATIC) */
    __u8    copy_flag;     /* if this logical eraseblock was copied from another physical eraseblock (for wear-leveling reasons) */
    __u8    compat;        /* compatibility of this volume (%0, %UBI_COMPAT_DELETE,%UBI_COMPAT_IGNORE, %UBI_COMPAT_PRESERVE, or %UBI_COMPAT_REJECT)*/
    __be32  vol_id;        /* ID of this volume */
    __be32  lnum;          /* logical eraseblock number */
    __u8    padding1[4];   /* reserved for future, zeroes */
    __be32  data_size;     /* how many bytes of data this logical eraseblock contains */
    __be32  used_ebs;      /* total number of used logical eraseblocks in this volume */
    __be32  data_pad;      /* how many bytes at the end of this physical eraseblock are not used */
    __be32  data_crc;      /* CRC checksum of the data stored in this logical eraseblock */
    __u8    padding2[4];   /* reserved for future, zeroes */
    __be64  sqnum;         /* sequence number */
    __u8    padding3[12];  /* reserved for future, zeroes */
    __be32  hdr_crc;       /* volume identifier header CRC checksum */
} __packed;

vol_id字段保存的就是物理擦写块属于哪个volume,lnum字段保存的就是物理擦写块被映射到哪个逻辑擦写块;

struct ubi_ec_hdr 和 struct ubi_vid_hdr 在raw flash上的存储位置:

因为每个物理擦写块都要保存 struct ubi_ec_hdr 和 struct ubi_vid_hdr,所以每个物理擦写块可用空间都会少1到2个page,比如某种型号nand flash的物理擦写块大小为128KB,page大小为2KB,不支持sub page读写,则每个物理擦写块用户可用空间只有128KB – 2个page=128KB – 2 * 2KB = 124KB,即逻辑擦写块只有124KB;

用户创建的所有volume信息都被保存到1个物理擦写块上,每个volume信息用 struct ubi_vtbl_record 来表示,如果有3个volume,就有3个 struct ubi_vtbl_record;

为了让1个物理擦写块可以保存所有的volume信息,UBI规定用户能创建的volume最大个数为128个;

为了避免用户创建volume过程中,把新创建的volume信息写入物理擦写块过程中突然掉电导致丢失整个mtd分区的volume信息,ubi使用2个物理擦写块来保存2份一样的volume信息;只要其中1个物理擦写块的volume信息变得无效时,就从另外1个物理擦写块上的volume信息拷贝过来;

3.2 UBI attach

考虑到ubi系统的读写性能,用户交互等原因,保存在raw flash上的组织信息必须要抽取到DDR内存上,这个抽取过程只需要上电初始化时执行一次即可,这个过程被称为ubi的attach;

ubi系统使用struct ubi_device来描述mtd的一个分区上的组织信息,主要成员变量如下所示:

struct ubi_device {
    ......

    int vol_count;  /* number of volumes in this UBI device */
    struct ubi_volume *volumes[UBI_MAX_VOLUMES+UBI_INT_VOL_COUNT];  /* volumes of this UBI device */

    ......

    struct rb_root used;             /* RB-tree of used physical eraseblocks */
    struct rb_root erroneous;        /* RB-tree of erroneous used physical eraseblocks */
    struct rb_root free;             /* RB-tree of free physical eraseblocks */
    int free_count;                  /* Contains the number of elements in @free */
    struct rb_root scrub;            /* RB-tree of physical eraseblocks which need scrubbing */

    struct task_struct *bgt_thread;  /* background thread description object */

    ......

    struct mtd_info *mtd;            /* MTD device descriptor */

    ......
};

vol_count:表示mtd分区上创建了多少个volume;

volumes:数组中的每个元素记录了volume的具体信息;

used:使用红黑树保存所有volume使用的物理擦写块;

erroneous:使用红黑树保存整个mtd分区中有问题的物理擦写块;

free:使用红黑树保存整个mtd分区中没被使用的物理擦写块;

scrub:使用红黑树保存整个mtd分区中需要被scrub的物理擦写块;

bgt_thread:每个mtd分区都会有1个ubi的后台内核线程,用于实现磨损均衡等功能;

mtd:mtd分区描述符;

attach的简要过程(不带fastmap):

上电启动后,ubi系统会扫描需要attach的mtd分区的所有物理擦写块,读取每个物理擦写块上的 struct ubi_ec_hdr 和 struct ubi_vid_hdr结构体:

如果读取物理擦写块失败,且通过mtd接口判断为坏块,则把这个物理擦写块保存到erroneous红黑树上;

如果读取物理擦写块成功,且 struct ubi_vid_hdr结构体表示这个物理擦写块属于某个volume,则把物理擦写块保存到对应的volume,同时还要把物理擦写块保存到used或者scrub红黑树,如果读取物理擦写块时发生位反转且反转位数超过阈值,就保存到scrub红黑树,否则保存到used红黑树;

如果读取物理擦写块成功,但struct ubi_vid_hdr结构体表示这个物理擦写块不属于任何volume,则把物理擦写块保存到free红黑树;

扫描完所有物理擦写块后,创建后台的内核线程bgt_thread;

3.3 问题:UBI如何对raw flash进行正常的读,写,擦除操作?

ubi必须要attach到mtd的分区才能使用,attach过程中获取到mtd分区的描述符 struct mtd_info *mtd,并保存到表示ubi设备的struct ubi_device结构体中;获取到mtd分区描述符后就可以通过内核中的mtd模块:include/linux/mtd/mtd.h 定义的接口来操作raw flash:

int mtd_erase(struct mtd_info *mtd, struct erase_info *instr);
int mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);

3.4 问题:UBI如何处理坏块?

ubi attach到mtd分区或者ubi使用过程中,发现的坏块都集中保存到 erroneous 红黑树中,读写或擦除过程中都不会使用到 erroneous 红黑树中的物理擦写块;

ubi还会保留固定数量的物理擦写块专门用于替换使用过程中发现的坏块;使用mtd接口操作raw flash时,如果发现坏块,需要用户自己跳过坏块;但是用ubi读写raw flash时,ubi内部会自己用保留的物理擦写块替换坏块,用户不用知道是否有坏块;

为替换坏块保留的物理擦写块个数,可以有2种方式配置:

1. 内核配置项:

│     -> Device Drivers
│       -> Memory Technology Device (MTD) support
│         -> Enable UBI - Unsorted block images
|           -> (20)  Maximum expected bad eraseblock count per 1024 eraseblocks

注意:单位是每1024个物理擦写块保留块数;如果配置每1024个物理擦写块保留20个,假设你使用的nand flash容量有2048个物理擦写块,不管ubi attach到的mtd分区大小多少,ubi实际保留的个数都是 20 * (2048 / 1024) = 20 * 2 = 40个;比如ubi attach 到一个只有128个物理擦写块大小的mtd分区,ubi也会给这个mtd分区保留40个物理擦写块用于替换坏块;

2. 通过ubiattach工具的-b参数指定:

$ ./ubiattach --help
ubiattach version 2.1.5 - a tool to attach MTD device to UBI.

Usage: ubiattach [<UBI control device node file name>]
    [-m <MTD device number>] [-d <UBI device number>] [-p <path to device>]
    [--mtdn=<MTD device number>] [--devn=<UBI device number>]
    [--dev-path=<path to device>]
    [--max-beb-per1024=<maximum bad block number per 1024 blocks>]
UBI control device defaults to /dev/ubi_ctrl if not supplied.
Example 1: ubiattach -p /dev/mtd0 - attach /dev/mtd0 to UBI
Example 2: ubiattach -m 0 - attach MTD device 0 (mtd0) to UBI
Example 3: ubiattach -m 0 -d 3 - attach MTD device 0 (mtd0) to UBI
           and create UBI device number 3 (ubi3)
Example 4: ubiattach -m 1 -b 25 - attach /dev/mtd1 to UBI and reserve
           25*C/1024 eraseblocks for bad block handling, where C is the flash
           is total flash chip eraseblocks count, that is flash chip size in
           eraseblocks (including bad eraseblocks). E.g., if the flash chip
           has 4096 PEBs, 100 will be reserved.

-d, --devn=<number>   the number to assign to the newly created UBI device
                      (assigned automatically if this is not specified)
-p, --dev-path=<path> path to MTD device node to attach
-m, --mtdn=<number>   MTD device number to attach (alternative method, e.g
                      if the character device node does not exist)
-O, --vid-hdr-offset  VID header offset (do not specify this unless you really
                      know what you are doing, the default should be optimal)
-b, --max-beb-per1024 maximum expected bad block number per 1024 eraseblock.
                      The default value is correct for most NAND devices.
                      Allowed range is 0-768, 0 means the default kernel value.
-h, --help            print help message
-V, --version         print program version

对于小的mtd分区,如果不想按照内核配置项来保留,可以在attach时候使用ubiattach的-b参数来保留,保留个数的单位也是每1024个物理擦写块保留块数;如果nand flash的容量是2048个物理擦写块,只想给小的mtd分区保留10个物理擦写块,则-b参数指定为 10 * (1024 / 2048) = 10 * 0.5 = 5;

3.5 问题:UBI如何实现磨损均衡?

磨损均衡的目的是让ubi attch的mtd分区中的所有物理擦写块得到轮换使用,避免频繁的擦写某一个或几个物理擦写块导致擦写次数过多出现坏块;

ubi会把mtd分区中所有volume使用的物理擦写块保存到 used 的红黑树中,把mtd分区中所有未被使用的物理擦写块保存到free的红黑树中;used和free红黑树都是以(擦除次数,物理擦写块索引)作为键值,方便根据擦除次数来查找物理擦除块;

ubi系统中每次对物理擦写块进行擦除操作后都会触发磨损均衡处理流程,具体的处理工作会交给后台内核线程bgt_thread;首先从used红黑树中取出擦写次数最少的物理擦写块PEB_used,从free红黑树中取出擦写次数最靠近:free红黑树第一个节点的擦写块的擦写次数(free红黑树擦写次数最少的节点) + 2倍UBI_WL_THRESHOLD(内核配置项,比如配置为4096)次数的物理擦写块PEB_free;

如果PEB_free的擦写次数与PEB_used的擦写次数之差小于UBI_WL_THRESHOLD,则不用进行磨损均衡处理,流程结束;

如果擦写次数之差大于UBI_WL_THRESHOLD:把PEB_used的数据拷贝到PEB_free,并把PEB_free放到used红黑树;把PEB_used擦除后放到free红黑树中,等待被申请使用;即把擦写次数多的PEB换到used红黑树,后续很大概率是只读取不写了,把擦写次数少的PEB换到free红黑树,供后续用户申请,擦除,写入数据;

为了避免频繁的进行磨损均衡处理,需要2个物理擦写块的擦写次数之差大于UBI_WL_THRESHOLD才会真正启动均衡处理;

3.6 问题:UBI如何 处理位反转?

当ubi系统使用mtd_read接口读取物理擦写块的数据时,如果mtd_read返回值是-EUCLEAN,表示nand flash的位反转个数超过阈值了,可能很快会变成坏块;把这个物理擦写块放到scrub红黑树,并在后台内核线程bgt_thread中启动scrub处理,尽快把数据拷贝到好的物理擦写块上,避免变成坏块后导致数据丢失;

scrub处理过程:

从scrub红黑树取出物理擦写块PEB_scrub,从free红黑树取出物理擦写块PEB_free,把PEB_scrub的数据拷贝到PEB_free,并把PEB_free放到used红黑树;因为PEB_scrub并没有真正变成坏块,所以PEB_scrub擦除后还是放到free红黑树,继续使用;

scrub处理过程与磨损均衡的处理过程类似,不一样的是scrub处理不要求2个物理擦写块的擦写次数之差大于UBI_WL_THRESHOLD,不管擦写次数之差是多少,都要进行scrub处理,尽可能的避免数据丢失;

3.7 问题:UBI如何对写数据进行掉电保护?

raw flash更新物理擦写块PEB的数据,必须要先擦除PEB,然后再把新的数据写入PEB;如果刚擦除完PEB还未把新数据写入PEB就掉电了,不仅新数据没写进去,旧数据也丢失了;

UBI具有掉电保护功能的数据更新流程:

假设ubi需要更新逻辑擦写块LEB的数据,假设LEB被map到物理擦写块PEB_old上;ubi并不直接更新PEB_old的数据,而是先申请一个新的PEB_new,先把新的数据写到PEB_new,然后再把LEB重新map到PEB_new上,最后再把PEB_old擦除后放到free红黑树后等待被申请使用;

假设ubi擦除和写入PEB_new的过程中掉电,LEB还是map到原来的PEB_old上,虽然新数据没更新成功,但是老数据还是被保留下来了;

这种做法的缺点是ubi必须要保证有个新的PEB_new可以被用来更新数据操作,即用户可用空间会少一个PEB;

3.8 问题:如何减少大容量raw flash的attach时间?

ubi attach到mtd分区时,必须要扫描读取mtd分区的每一个物理擦除块PEB中的 struct ubi_ec_hdr 和 struct ubi_vid_hdr结构体,获取和重建ubi的volume和LEB到PEB的映射表;因为读取PEB数据需要时间,mtd分区容量越大,即PEB数量越多,attach到mtd分区的时间就越长;

解决问题的基本思路:把需要重建ubi volume和LEB到PEB的映射表数据都保存到raw flash中,每次开机直接从raw flash读取重建需要的数据,避免扫描读取mtd分区所有的PEB;这种处理方式叫做fastmap

ubi使用内部volume:fastmap volume,来保存重建需要的数据;fastmap volume又分两种:UBI_FM_SB_VOLUME_ID 和 UBI_FM_DATA_VOLUME_ID,

1. UBI_FM_SB_VOLUME_ID只使用1个PEB,用于存放 struct ubi_fm_sb (UBI fastmap super block)的数据:

struct ubi_fm_sb {
    __be32 magic;                         /* fastmap super block magic number (%UBI_FM_SB_MAGIC) */
    __u8 version;                         /* format version of this fastmap */
    __u8 padding1[3];                     /* reserved */
    __be32 data_crc;                      /* CRC over the fastmap data */
    __be32 used_blocks;                   /* number of PEBs used by this fastmap */
    __be32 block_loc[UBI_FM_MAX_BLOCKS];  /* an array containing the location of all PEBs of the fastmap */
    __be32 block_ec[UBI_FM_MAX_BLOCKS];   /* the erase counter of each used PEB */
    __be64 sqnum;                         /* highest sequence number value at the time while taking the fastmap */
    __u8 padding2[32];                    /* reserved */
} __packed;

这个volume只会保存在mtd分区最开始的64个PEB中的1个,即只要扫描mtd分区最开始的64个PEB就一定可以找到UBI_FM_SB_VOLUME_ID 类型的volume;通过这个volume保存的struct ubi_fm_sb的数据就可以找到实际存放重建volume和LEB到PEB所需数据的 UBI_FM_DATA_VOLUME_ID类型的volume;

struct ubi_fm_sb中的成员变量说明如下:

used_blocks:表示 UBI_FM_DATA_VOLUME_ID类型的volume使用了多少个PEB;

block_loc:表示 UBI_FM_DATA_VOLUME_ID类型的volume使用的PEB的实际位置;

2. UBI_FM_DATA_VOLUME_ID:最多使用UBI_FM_MAX_BLOCKS(32)个PEB来保存实际存放重建volume和LEB到PEB所需数据;这个volume的数据可以保存到raw flash上任意位置的PEB;

UBI_FM_SB_VOLUME_ID 和 UBI_FM_DATA_VOLUME_ID的关系就像指针和实际内存之间的关系,比如在C/C++中:char * p = (char *)malloc(32),通过指针p就可以访问在堆上分配的32字节的实际内存上的数据;对于UBI,只需要扫描mtd分区最开始的64个PEB,就可以找到 UBI_FM_SB_VOLUME_ID类型的volume,通过这个volume就可以快速定位到UBI_FM_DATA_VOLUME_ID类型的volume,进而可以快速重建ubi attach 到的mtd分区的volume和LEB到PEB的映射;

fastmap要起作用就要求用户创建,删除,修改volume,LEB到PEB的映射变化(比如写数据到raw flash)时,都要及时更新UBI_FM_DATA_VOLUME_ID类型的volume数据并写入raw flash,这样频繁写数据到raw flash会导致读写性能下降和raw flash的坏块增加;

为了避免频繁的把UBI_FM_DATA_VOLUME_ID类型volume数据写入raw flash,ubi会创建fastmap的PEB pool,pool中的PEB个数为mtd分区PEB个数的5%,但最小为8个,最大为256个;ubi需要PEB时,先从pool中申请,当pool中的PEB用完了,才会把UBI_FM_DATA_VOLUME_ID类型volume数据写入raw flash,同时再从free红黑树中填充PEB到pool中;因为pool中的PEB相关的volume和LEB到PEB映射信息没有实时写入raw flash,但是写入了每个PEB中的 struct ubi_ec_hdr 和 struct ubi_vid_hdr结构体,所以raw flash上电attach过程中,除了把fastmap的数据读出来重建volume和LEB到PEB的映射,还需要把fastmap pool中的所有PEB扫描读取 struct ubi_ec_hdr 和 struct ubi_vid_hdr结构体数据,这样才能完整重建volume和LEB到PEB的映射;因为pool最多只有256个PEB,扫描完需要的时间也就是有上限的,attach需要的时间不会随着mtd分区中PEB个数的增加而增加;

如果raw flash是第一次使用,没有保存fastmap的数据,或者保存fastmap数据的PEB出现坏块,ubi会强制重新扫描整个mtd分区的所有PEB;

fastmap功能是内核配置项:

│     -> Device Drivers
│       -> Memory Technology Device (MTD) support
│         -> Enable UBI - Unsorted block images
|           -> UBI Fastmap (Experimental feature)

fastmap的缺点是需要额外的PEB来存储数据,如果mtd分区容量不大,或者对attach的时间长短不敏感,就没必要开启fastmap功能;

四.没有nand flash硬件可以调试UBI系统吗?

可以用软件模拟nand flash来调试UBI系统;

参考链接:http://linux-mtd.infradead.org/faq/nand.html#L_nand_nandsim

4.1 模拟nand flash

# 在ubunut18.04上模拟有3个分区(pagesize=2KB, erasesize=128KB)的nand flash,第1个分区 80xerasesize=10MB, 第2个分区160xerasesize=20MB, 第3个分区784xerasesize=98MB:
sudo modprobe nandsim first_id_byte=0xec second_id_byte=0xa1 third_id_byte=0x00 fourth_id_byte=0x15 parts=80,160,784

# 删除模拟的nand flash:
sudo rmmod nandsim

4.2 使用UBI

#加载ubi驱动:
sudo modprobe ubi
#查看是否加载成功:执行 ls /dev/ubi_ctrl 如果有ubi_ctrl节点,就表示加载成功;

#ubi格式化第1个分区/dev/mtd0: 
sudo ubiformat /dev/mtd0

#ubi attach到第1个分区:
sudo ubiattach /dev/ubi_ctrl -m 0
#执行 ls /dev/ubi0 如果存在说明ubi attach 成功了;

#查看ubi信息,有2种方法:
#1.执行 dmesg 查看内核输出的ubi信息
dmesg
#2.使用ubinfo工具
sudo ubinfo /dev/ubi0 查看ubi信息

#创建容量为3M,名称为test1的动态volume: 
sudo ubimkvol /dev/ubi0 -s 3MiB -N test1
#执行ls /dev/ubi0_0 如果存在节点说明创建成功;

#创建容量为2M,名称为test2的静态volume: 
sudo ubimkvol /dev/ubi0 -s 2MiB -N test2 -t static

上面的 ubiformat, ubiattach, ubinfo, ubimkvol 工具需要在ubuntu18.04上自行安装mtd-utils,或者下载mtd-utils的源码自行编译;

mtd-utils源码下载:

  1. http://git.infradead.org/mtd-utils.git 中查看可获取的mtd-utils版本;

  1. 如果要获取mtd-utils-2.1.5 版本的源码:

wget ftp://ftp.infradead.org/pub/mtd-utils/mtd-utils-2.1.5.tar.bz2

五. 参考资料

http://linux-mtd.infradead.org/doc/ubi.html

http://linux-mtd.infradead.org/faq/nand.html#L_nand_nandsim

  • 7
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值