第二章 介绍
UBI 是通用闪存管理层,具有与逻辑卷管理器(LVM)类似的功能。基本理念是:UBI层提供多个逻辑分卷的管理,分卷可以包含静态数据或动态内容(文件系统)。
LVM 原理分析
LVM 在每个物理卷(PV)头部都维护了一个MetaData,叫做卷组描述域(VGDA,Volume Group Description Area),每个VGDA中都包含了整个VG(Volume Group)的信息,包括每个VG的布局配置、PV 的编号、LV的编号,以及每个PE到LE的映射关系。同一个VG中的每个PV头部的信息是相同的,这样有利于发生故障时进行数据恢复。
LVM 对上层文件系统提供 LV 层,隐藏了操作细节。对文件系统而言,对 LV 的操作与原先对 Partition 的操作没有差别。当对 LV 进行写入操作时,LVM 定位相应的 LE,通过 PV 头部的映射表,将数据写入到相应的 PE 上。
LVM 实现的关键在于在PE 和LE间建立映射关系,不同的映射规则决定了不同的LVM存储模型。LVM 支持多个PV的Stripe和Mirror,这点和软RAID的实现十分相似。
LVM 对上层文件系统提供抽象层,隐藏了操作细节,对文件系统而言,对LV的操作与原先对分区( Partition)的操作没有差别。当对LV进行写入操作时,LVM定位相应的LE,通过PV头部的映射表,将数据写入到相应的PE上。
用同样类型来管理一个或者多个flash设备上的多个逻辑分卷,需要如下功能来支持:
UBI不是一个传统的为了仿真块设备而设计的闪存转换层。UBI从文件系统以及用户空间接口封装闪存芯片管理。传统的闪存转换层可以很容易的在UBI分卷上创建。
UBI 解决了在 FTL 和闪存文件系统中冗余实现的常见闪存处理机制。这使得 FLASH 文件系统开发人员能够专注于文件系统设计,同时参与 UBI 的持续开发和改进。
与当今可用的混合解决方案相比,FLASH 管理和文件系统、FTL 和其他可能用户之间的分离提供了更好的分层概念。除了替换冗余和单一用途实现外,这在可测试性和错误分析方面提供了优势。
UBI分卷
UBI 卷是不同数据内容的容器。UBI 客户端只能访问数据内容,而不能访问 UBI 元数据(metadata用于存放UBI需要的数据)。
UBI 可以处理两种卷类型:
• static volumes
• dynamic volumes
静态分卷
静态卷仅包含静态数据,例如引导代码、操作系统映像、初始内存磁盘(initial-ramdisk)或只读文件系统。在写入静态卷的块之前,要写入块的数据是完全已知的,并在以后的操作中以只读的方式处理。
静态分卷具有:
需要静态卷具有逻辑擦除块的线性(连续)使用,以便简化一致性检查并允许快速访问数据内容。这尤其适用于用例:bootloader必须从UBI设备加载例如内核的映像。静态卷不是所有的块都就位,则无法使用该卷。
动态分卷:
动态卷包含可在操作期间更改的数据。动态卷是支持可写文件系统的必要要求。
动态卷具有:
动态卷的用户必须实现自己的数据完整性机制。UBI 无法提供此功能,因为写入 UBI 标头时不知道块的内容。逻辑擦除块的使用模式不受限制,UBI 通过将它们视为空,即读取此类块返回一个请求大小的全FF的buffer,而不访问物理的FLASH芯片,写入未分配的块会导致在写入数据内容之前进行分配。
3. MTD集成
上图显示UBI怎样适配到MTD软件架构,注意一下,几乎所有的服务,像可读写以及基于文件系统的只读二进制数据都是基于UBI支持的。由于 MTD 的分层结构,对 MTD 设备的原始访问可用。需要更改MTD的完整基础设施,以避免这种情况。
一个 UBI 实例处理一个 MTD 设备,这可以是物理设备、MTD 分区或由多个 MTD 设备组成的连接 MTD 设备。串联支持可通过内核中的 mtdconcat 获得。MTD NAND 闪存层还能够连接多个大小相等的 NAND 芯片。
请注意,在串联设备上使用 UBI 需要支持equvivalent bootloader。
请注意,在MTD 设备的分区上并非一定会使用UBI。但可能基于已经存在的MTD基础上,在某些情况下,例如,必须从 UBI 块管理中排除 FLASH 的特殊区域,可以使用分区执行此操作。
简单分区
Partition Physical erase blocks
Bootloader 0 - 3
UBI 4 - max. blocks
这将允许在 UBI 分区上的不同卷上进行磨损平定,并确保不会动到bootloader。另一种可能性是在 UBI 标头中用"不可移动"标志标记bootloader程序块,因此 UBI 不会将这些块包括在磨损水平中。这需要 UBI 感知bootloader设计,并且当引导代码需要从块的偏移 0 启动时,可能根本不可能。
复杂分区
方案 1
Partition Physical erase blocks
Bootloader 0 0 - 3
UBI 4 - 63
Bootloader 1 64 - 67
UBI 68 - max. blocks
方案 2
Partition Physical erase blocks Chip
Bootloader 0 - 3 0
UBI 4 - max. blocks 0
Bootloader 0 - 3 1
UBI 4 - max. blocks 1
Chapter 4. UBI design
擦除块分配
UBI 卷的每个擦除块对应于一个物理闪存擦除块。尽管对UBI用户来说逻辑擦除块是连续的,实际上对应的物理擦除快可能是乱序分布在flash芯片上的。
基本上UBI使用一个表,称为擦除块分配表(EAT),其将逻辑擦除块映射到物理擦除块。当一个物理擦除块被分配给分卷时,EAT中的相应条目已更改。并且,如果一个逻辑擦除块使用的过于频繁而且UBI决定为其分配另一个(不磨损)物理擦除块,EAT中的相应条目也会被更改。
在基本的UBI实现中,擦除块分配表是在RAM中保存并维护的。该EAT表示在初始化时扫描擦除块时建立的。这没有很好的弹性,但是它很简单的实现,而且对使用在初次项目的FLASH是可接受的。
增强
当然实现更加全面的EAT管理方式是可能的,并且在flash上维护EAT。
当基于闪存的 EAT 不一致或不可用时,扫描建立EAT的机制必须保证作为一个回退选项。
UBI管理一个内部的EAT分卷。该EAT分卷是一个动态UBI分卷。由于磨损水平原因,EAY分卷使用的物理擦除块必须变化,但应保存在靠近 FLASH 设备的开头或结尾的区域,以限制要扫描的块数,以便(快速)找到EAT卷。
基于闪存 EAT 管理的挑战是一致性、访问速度和低闪存更新频率的平衡。接下来的算法确定了一种可能的方案。UBI在flash上写入一个EAT表,其中包括实际块引用和在 UBI 设备初始化期间必须扫描的一些擦除块引用。这些擦除块的内容在扫描之前是未知的。这些块是表写入FLASH 时对 UBI可用块池中的块的引用。这些块是UBI为UBI客户端的新用法而提供的那些块。在扫描期间UBI 添加或更新 FLASH 表中的块引用。在以下情况时,必须在 FLASH 上更新该表:
擦除块标头
基本的闪存管理基于擦除块。UBI 需要同等大小的擦除块。对于块管理,UBI 在每个擦除块的开头使用标头,这会略微减小擦除块的可用大小。标头用于两个目的:
因此,UBI 中有两种擦除块标头类型:
请注意,两个擦除块标头是独立写入闪存,即,UBI要求对擦除块的开头执行两个写入操作。例如,当物理擦除块未分配给任何卷时,它只有一个擦除计数标头,但一旦分配给卷,就必须添加卷标识符。
擦除块标头具有固定位置,具体取决于 FLASH 类型。在 NOR 上,标头写在连续地址上的擦除块的开头。在NAND闪存设备上,擦除计数标头被写入块偏移为0的位置,且不带ECC,然后卷标头写入擦除块中第一页的最后一个子项。在小型页面设备(512字节/页)上,偏移量是256字节。在大型页面设备(2048字节/页)上,偏移量是2048-ECC大小。ECC大小取决于硬件/软件ECC配置,并从NAND闪存驱动程序检索。
图 4-1.NAND标头
在 NAND 闪存上进行此布局有两个原因。
为了减少识别擦除块的扫描时间,建议的布局只需要读取block的第一页的一个ECC大小部分。在大型页面 NAND 设备上,这会将读取的字节数从2072减少到280,假设ECC size为256字节,页布局为8*(256字节数据) + 8*(3字节ECC)。
NAND知道文件系统需要页对齐的可擦除块大小,因为写入NAND闪存必须对齐页面。JFFS2使用NAND页面大小的写入缓冲区来累积较小的写入块。做这个更改需要很大的更改,为了避免丢失 (NAND页面大小) - (UBI标头大小) FLASH空间而做这些更改并不值得。
通用标头格式
所有 UBI 标头均以大端格式编写。这允许在具有不同大小端系统和所有UBI支持工具的系统上使用可移动介质-例如用于分析原始FLASh内容,创建量产镜像-可独立写入目标机器。
标头结构中的所有保留字段都填充 0,以允许更高版本的UBI处理由早期版本创建的UBI卷。
擦除计数器格式
struct ubi_ec_hdr {
ubi32_t magic;
uint8_t version;
uint8_t padding1[3];
ubi64_t ec;
ubi32_t vid_hdr_offset;
ubi32_t data_offset;
uint8_t padding2[36];
ubi32_t hdr_crc;
} __attribute__((packed));
Magic 用于标识擦除计数标头的魔数,该魔数值为0x55424923,在ASCII中为: "UBI#".
version UBI版本号,用于标识创建此标头的UBI版本。
ec 此擦除块的擦除数。
vid_hdr_offset 卷头在擦除块中的位置。这是一个常量值,具体取决于 FLASH 类型,但它允许解
码原始闪存转储,而无需进一步的信息。
data_offset 数据在擦除块中的位置。
hdr_crc 擦除计数标头的CRC保护。
卷标识符格式
struct ubi_vid_hdr {
ubi32_t magic;
uint8_t version;
uint8_t vol_type;
uint8_t copy_flag;
uint8_t compat;
ubi32_t vol_id;
ubi32_t lnum;
ubi32_t leb_ver;
ubi32_t data_size;
ubi32_t used_ebs;
ubi32_t data_pad;
ubi32_t data_crc;
uint8_t padding1[12];
uint8_t ivol_data[UBI_VID_HDR_IVOL_DATA_SIZE];
ubi32_t hdr_crc;
} __attribute__((packed));
magic 用于标识分卷标头的魔数. 该魔数值为0x55424921,在ASCII中为: "UBI!".
version UBI版本号,用于标识创建此标头的UBI版本。
vol_type 分卷类型。
lnum 此擦除块的逻辑块编号。
leb_ver 此擦除块的版本号。
compat 与此擦除块关联的标志。
vol_id 此擦除块所属的卷ID。
hdr_crc 标头的CRC保护。
静态卷的卷信息包含:
data_size 此擦除块中的数据大小。此字段与静态卷的最后一个逻辑块相关,因为最后一个块通常不包含到块可用大小的内容。因此该字段对于防止访问超出存储数据的边界是必要的。
data_crc 此擦除块的数据CRC。
used_ebs 卷中已使用的总块数。此信息对于静态卷的一致性检查是必需的。
动态卷的卷信息包含:
data_crc 此擦除块的数据CRC。
擦除计数器更新原子性
当必须擦除擦除擦除块时,当前擦除计数将保留在RAM中,并且擦除完成后递增的擦除计数将写回FLASH。在擦除块被擦除后但是擦除计数器被完全写入之前,操作被打断,擦除计数器就丢失了。稍后发现此情况时,受影响的块的擦除计数器将设置为所有块的平均擦除计数。擦除计数标头损坏时也是如此。
请注意,可以添加日志以保证原子擦除计数器更新。
分卷
UBI分卷号的范围是0~65535,header数据结构中的volume number成员允许有更大的范围,但是没有真实案例使用过更多的分卷号;
除了用户访问的分卷,UBI还维护内部分卷用来存储UBI想关的信息,例如卷信息,基于闪存的擦除块分配表。
0~65279的分卷号是给用户使用的,65280 – 65535的分卷号是为UBI内部分卷保留的。内部分卷名以“ubi-”开头。
用户分卷没有顺序的限制。用户可以选择任意空闲的分卷号,例如0,3,8,300。用户可以根据不同的目的使用有弹性的分卷分配。
请注意,对用户分卷来说当前实现仅支持 0 到 127 的卷号。
布局分卷
分卷号65280的分卷(被称为布局分卷)包含了UBI的布局信息,例如分卷的数量,他们的大小跟属性。这个分卷的名字是“ubi-layout”。
布局分卷需要两个erase blocks。数据是冗余存储的。分卷信息存储为所有可用用户分卷的分卷信息结构的连续列表。
分卷信息存储在以下结构中。
struct ubi_vol_tbl_record {
ubi32_t reserved_pebs;
ubi32_t alignment;
ubi32_t data_pad;
uint8_t vol_type;
uint8_t padding1;
ubi16_t name_len;
uint8_t name[UBI_VOL_NAME_MAX + 1];
uint8_t padding2[24];
ubi32_t crc;
} __attribute__ ((packed));
当有分卷创建,删除或者调整大小时,布局分卷需要更新。当其中这些操作之一被执行时,新的布局分卷被写入到flash。当数据被成功的写入,原始布局分卷被擦除,逻辑擦除块(always nr. 0))的版本号被加一。下一步讨论的移动静态内容擦除块的鲁棒性方面也适用于此机制。
有关分卷的信息 ( 保留的, 使用的尺寸 - 和UBI设备上的可用空间可以通过UBI设备和UBI分卷的sysfs接口访问.
鲁棒性
当layout分卷有缺陷时,UBI会从扫描信息派生卷大小。每个卷的保留块数设置为为每个卷找到的最大逻辑擦除块数。这允许系统进入运行状态。UBI向系统日志发出适当的警告,并通过sysfs接口提供状态信息。
FLASH 空间需求
Spare erase blocks(备用擦除块)
UBI 需要保留多个备用擦除块,以确保操作的使用寿命。这些备用模块减少了可用闪存的总大小。
UBI 的最低要求是一个备用擦除块,用于磨损水平和更新机制。
在某些闪存类型(例如 NAND)上,需要保留大量备用块,以便进行不良块处理。最小数是初始坏块的数量。最大数量是系统设计人员为了UBI保持完全运行而要保留的块数。这是每个UBI设备坏块的总数。在计算卷大小时,无需考虑坏块。在配置的最大坏块数量之下,UBI保证功能的完整性。
坏块参数
坏块的最大数量与FLASH类型和闪存大小有关,因此使用可以保存特定设备上可接受的最大坏块数的参数来扩展MTD信息结构是一个自然选择。
这还需要MTD设备级联机制(mtdconcat.c)汇总串联设备的这些参数。
应提供一个命令行选项来覆盖设备驱动程序值,以使模板驱动程序可用于特定硬件并可配置。
鲁棒性
当坏块数超过指定最大值时,UBI拒绝对所有卷的写入访问。需要手动交互。解决方案是增加坏块的最大数量。仅当UBI设备上有可用空间时,才可能这样做。如果所有可用空间都已经被使用,那么整个分卷必须在增加最大坏块数量前被删除或者重新调整大小。
通过UBI设备的sysfs接口,始终可以得到坏块的实际数量。
当最大和实际坏块数的增量小于2时,UBI还会向系统日志发出适当的警告消息。
建议通过监控应用使用UBI设备的系统接口监控坏块的实际数量。这样就允许service的早期信息个性化的确保设备在达到最大数量的坏块,并且设备切换到受限操作状态之前,更换或更改参数。
空间计算
分卷管理
UBI分卷管理基于空间核算。可供用户卷使用的擦除块总数不能超过为卷保留的擦除块的总和。即使卷没有使用预留空间,UBI也不会实现内存过量分配机制。
UBI 实现与卷管理相关的三个功能:
分卷创建
只要有足够的空闲空间,就可以创建新的分卷。创建者请求新卷的大小,UBI将其与可用于内容存储的每个纪元块的字节数进行对齐。
为了支持 UBI 顶部的块设备层,必须提供一个可选参数,该参数可以调整最小不可用空间 - 最小不可用空间是 2 + 大小(ubi 标头)到更大的大小,从而确保擦除块的可用空间是块设备子块大小。
分卷删除
当分卷没有被使用时。可以被删除。该分卷中被分配的擦除块将归还到空闲块池。擦除操作必须完成,才能将卷调整大小传播到分卷layou中。
分卷改变大小
当分卷没有被使用时,可以改变大小。
如果分卷被扩展,所需的空间必须在UBI设备中是空闲的;
当卷大小减小时,卷中的已用块数必须小于或等于新大小。请注意,默认情况下UBI不会减小动态卷的大小,除非将从卷中要删除的逻辑擦除块的数量范围仅包含未使用的块。一个专有的参数允许覆盖该默认设置。然后,UBI将逻辑块的内容从将要从卷中删除的block编号范围移动到具有较低编号的未使用的逻辑擦除块,然后再调整卷的大小。必须先完成移动操作,然后才能将卷调整大小传播到布局卷中。
逻辑块
UBI分卷由逻辑块组成。分卷的最大逻辑块数是由在创建或调整大小时给出的大小来指定的。
分卷中的逻辑块可以使用从0~最大块数-1的所有块数。分卷中的逻辑块编号是唯一的。
对于短时间块移动、擦洗和磨损调平功能,可以创建逻辑块数的第二个实例。UBI确保无法对此类块进行写入访问。
块管理
块分配
UBI使用延迟块分配来确保优化磨损水平。当写入访问遇到尚未分配的逻辑块编号时,将分配该块并写入到分卷标头中。未为任何卷分配的所有擦除块都保存在空闲块池中。
块擦除
下述动作可以触发块擦除:
在大多数情况下,块擦除是异步操作,并推迟到后台操作。同步的块擦除需求必须在需要它们的功能部分中标记出来。
块数据搬移
下述动作可以触发块数据搬移:
在大多数情况下,块数据搬移是异步操作,并推迟到后台操作。同上,同步的处理需求必须在需要它们的功能部分中标记出来。
当UBI因为磨损水平/擦洗的考虑决定将一个逻辑擦除块的数据移动到另一个物理擦除块时,它需要:
静态分卷
静态分卷的块更新机制比较简单。复制擦除块的卷标头,并递增擦除块的版本号。标头的CRC被更新,但是数据CRC保持不变。接下来块数据被拷贝到新的块。当数据完成拷贝时,原始的擦除块被擦除。
如果在传输所有数据之前中断块内容的复制,接下来UBI会检测到卷中同一逻辑块的两个版本。UBI首先尝试使用版本号更高的那一个。但是由于数据传输的终端,数据CRC是无效的,UBI回退到版本号比较低的块。不完整的块将被擦除,并且可以启动新的移动操作。
如果移动操作在清除原始擦除块之前中断,接下来UBI会检测到卷中同一逻辑块的两个版本。UBI首先尝试使用版本号更高的那一个。在这种情况下该数据块的数据CRC是正确的,该低版本号的块可以被擦除。
搬移操作并不会堵塞数据的读操作。在完成整个复制内容之前,可以从原始擦除块中读取数据。
动态分卷
对于动态卷,需要稍作不同的机制。UBI 阻止对移动过程的逻辑擦除块进行写入访问。整个数据区的CRC校验会被生成。该校验值以及一个递增的版本号被写到新的擦除块分卷标头,并且更新标头的CRC。接下来数据内容被拷贝。请注意,空数据(包含所有的0xFF)不会被拷贝,但是会计入CRC计算。完全复制块内容后,将擦除原始擦除块,并再次启用对逻辑块的写入访问。
如果在传输所有数据之前中断块内容的复制,接下来UBI会检测到卷中同一逻辑块的两个版本。UBI首先尝试使用版本号更高的那一个。但是由于数据传输的终端,数据CRC是无效的,UBI回退到版本号比较低的块。不完整的块将被擦除,并且可以启动新的移动操作。
如果移动操作在清除原始擦除块之前中断,接下来UBI会检测到卷中同一逻辑块的两个版本。UBI首先尝试使用版本号更高的那一个。在这种情况下该数据块的数据CRC是正确的,该低版本号的块可以被擦除。在清除原始块之前,必须禁用对逻辑块的写入访问。
搬移操作并不会堵塞数据的读操作。在完成整个复制内容之前,可以从原始擦除块中读取数据。在移动操作完全完成之前,不允许对移动内容的逻辑块进行写入访问。即,原始擦除块必须已被擦除,然后才能再次授予写入访问权限。在移动操作期间允许对卷的其他擦除块进行写入访问。
擦洗(Scrubbing)
擦洗是一种安全机制,用于处理位翻转。当发生位翻转且 ECC算法可以恢复原始数据内容时,受影响的块的内容将根据上述算法移动到空闲块。当内核或用户空间启动的读取操作检测到位翻转时,擦洗操作在后台进行。
下图显示了发生位错误时如何处理文件系统卷的 UBI 块。UBI 块 A) 需要复制到空闲块中以消除位错误。版本号增加,CRC被计算并添加到新的UBI标头中。由于此特定UBI块的数据由使用它的文件系统控制,因此CRC通常未使用(如 A))。但是当拷贝/擦除处理应当被中断时,块被擦洗,此时数据CRC被运算使之能鉴别最后一个有效的版本的块。
图 4-2.文件系统分卷中的UBI块擦写
这里显示的算法可确保在清理过程被中断的任何情况下,存在UBI块的有效副本可以通过模块的版本号和/或关联的数据CRC进行标识。
UBI初始化
第一个简单的UBI实现执行全闪存扫描,以找出布局分卷擦除块的位置,以构建EAT表。当 UBI 扫描闪存介质时,它会读取擦除块标头并在主内存中生成所有必要的数据结构。
基于扫描的机制需要在引导加载程序和内核中进行单独的扫描。从技术上讲,将引导加载程序扫描表交给内核是可能的,但这需要跨不同体系结构和硬件建立更统一的引导加载程序内核接口。这样的接口是没有什么效用的,因为这种尝试将是一个单一的目的费力活,而不是一个有用的通用解决方案。
未来的增强功能
为了避免扫描完整的闪存,有必要实施基于闪存的EAT机制。
后台操作
UBI使用后台线程或利用可用的后台Linux工作线程执行以下操作:
磨损水平
磨损水平是在擦除计数器的帮助下在整个闪存设备中完成的。
默认选择条件是使用擦除计数最低的块。
UBI跟踪所有擦除块的最大和最小擦除计数。为了避免永久磨损,引入了5000次擦除周期的阈值。当任意块被擦除时检查磨损水平。当新擦除的块是擦除计数最高的块,并且擦除周期的增量与擦除计数最低的块的增量已达到阈值,则UBI选择具有低擦除计数的长期存储块并使用块移动函数将块内容复制到具有高擦除计数的块。复制完成后,具有低擦除计数的块将被删除并排队到空闲块池中。如果空闲块数量很少,这可能会减慢 UBI 客户端的擦除块分配速度。
空闲/未分配的擦除块按擦除计数器顺序保存在排序列表中,以确保当有分配请求时,I/O选择一个块。
增强
UBI客户端可能提供回调函数,该函数在尝试移动块内容之前由UBI调用。函数的原理是,客户端可能会否决块内容的移动。原因之一可能是客户端将此块用于写入,或者已预定为垃圾回收块。在这种情况下,块内容的移动没有意义,可以推迟到以后的时间,或者在不久的将来通过垃圾回收将块变为可用。
可以实现一个函数来发出逻辑擦除块的预期用途的信号。UBI 客户端可以发出信号,以告知该块是用于长期存储还是短期存储。这将帮助实现更高效的磨损水平算法。
分卷更新
UBI卷的更新动作从用户空间发起。更新机制写入没有元信息的原始数据内容。元信息 (即卷标头 ) 由UBI动态创建。更新工具打开ubi分卷并发出启动_更新的ioctl命令。start_update参数是将要写入分卷的字节总数。这是必要的,用来提供健壮性与更新中断以及计算静态卷中总共使用的块数量。UBI检查请求的数据长度是否适合分卷。
在更新动态UBI卷的情况下,首先写入包含保留的VOLUME_UPDATE卷ID和逻辑块编号0的卷更新标头的块。更新的分卷数存储在此块的数据负载中。此块的存在表示更新正在进行。UBI最终会擦除分配给卷的这个块。在写入新内容之前,必须完成该块的擦除。
更新静态卷时,不需要写分卷更新标记。如果可以找到所有使用的擦除块,并且其标头和数据CRC正确,则可确保数据的完整性和正确性。
在静态卷的情况下,现在数据内容被写入,并且UBI为逻辑擦除块创建卷标头,并计算数据CRC。
成功写入动态分卷的所有数据后,更新卷块将被擦除。
请注意,start_update参数为0字节是有效的。这将创建更新正在进行的块并擦除现有逻辑擦除块。清除所有现有块后,将删除更新正在进行的标记。这对于清空卷很有用。
当卷更新被中断时,具有保留更新卷ID的块位于FLASH上,并且使受保护卷的所有其他最终存在的逻辑擦除块无效。必须启动新的更新。任何可能的尝试访问这种类型的分卷的UBI客户端,会被错误代码通知,并且访问会被拒绝。也可以参考“编程接口”章节。在卷布局中,卷仍然存在,但已用块数为零。UBI会自动擦除此类卷的最终现有块,但不会擦除指示卷正在更新的块。用户交互是解决此问题所必需的。指示卷正在更新的块可以通过新的更新命令或删除分卷来删除。
使用额外的擦除块来指示更新正在进行似乎是一种浪费,但对于动态内容卷,在允许使用此类卷之前,没有其他方法可以确保更新操作的完整性。一旦实现了日志/超级块的增强功能,这种情况可能会更改。另一方面,标记正在进行的更新是完全合理的,因为此类更新在设备的生存期内很少发生,并且它允许基于卷完整性的简单扫描识别,例如在引导加载程序中。
请注意,额外块是每个UBI设备的开销,无论如何都保留用于磨损水平和块移动目的的额外块。这限制了并发的分卷更新数量只能是一个。这不是一个大问题,因为卷更新通常是在代码更新情况下完成的,因为时间不是关键因素。此外,更新的限制因素是FLASH设备本身,它不允许并行擦除/编程操作。
对卷进行部分更新是不可能的。
分卷读出
可以从用户空间读取 UBI分卷的数据内容。读取只能在未使用的卷上执行,也可以在只读客户端的卷上执行。
第五章 编程接口
内核空间
• read
• write
• erase
• get_volume
• put_volume
• Enhanced: move_modify
内核空间接口通过逻辑块数和逻辑块块内的偏移来访问卷,而不是提供完全偏移,由于以下原因,该偏移将转换为逻辑块数和块内的偏移量。
基于FLASH的文件系统无论如何都必须注意擦除块边界,并且通常在块上操作。现有在用的MTD接口需要使用到基于块的偏移地址。所以分开的块数和块内offset是可用的。当然UBI也可以处理全局offset,但是由于UBI减少了擦除块的净数据使用大小,这回导致需要一个偏移(64 位)/未使用的块空间的除法。在32位的CPU上64 / 32位除法是关键实现。否则,将限制只支持32位偏移量,从而将UBI限制为4GiB最大设备大小。
在UBI顶层实现中,块设备会将UBI擦除块划分为较小的块,这些块暴露在块设备接口中。有一个明确要求,可用UBI块大小的大小必须为这些子块大小的倍数。当块设备可写入时,子块的大小也受 NAND 页面大小的影响。最小合理的子块大小将为 512 字节,这允许使用纯32位操作来计算逻辑块数。这将设备限制增加到2 T字节,这似乎是一个合理的限制。
read
read函数带有如下参数:
• volume descriptor
• block number
• offset
• length
• buffer pointer
返回值是读到的字节数或者一个错误码。
当一个动态分卷上的UBI客户端想要读取一个未被分配的逻辑擦除块时,则UBI直接填充所需数量字节的0xFF到data buffer中,而不通过访问FLASH芯片。
下面的错误代码可能被返回:
-EIO 发生了致命的I/O错误,例如,设备被移除,超时。
-EINVAL 一个无效的块编号,偏移或者长度请求。
UBI不提供read_ecc版本,因为这与UBI客户端无关,并且与ecc相关的问题在UBI本身内处理。
write
write函数带有如下参数:
• volume descriptor
• block number
• offset
• length
• buffer pointer
返回值是写下去的字节数或者一个错误代码。当offset是0且没有物理擦除块被分配给逻辑擦除块时,则UBI自动分配一个物理擦除块并且写分卷标头。该函数仅对动态内容分卷有效。
下面的错误代码可能被返回:
-EIO 发生了致命的I/O错误,例如,设备被移除,超时。
-EROFS 试图写一个只读分卷。
-EINVAL 一个无效的块编号,偏移或者长度请求。
写入操作是同步的。UBI保证在函数没有返回错误码的情况下,函数返回时数据已经写入到FLASH。
硬件级驱动程序检测到的写入错误(例如NAND驱动程序的写入验证)在从写入函数返回之前由UBI处理。在这种情况下,UBI 会根据块移动机制将现有块内容移动到一个空闲的擦除块,并在块内容移动操作完成后将数据缓冲区中的数据写入此新块。
UBI不提供write_ecc版本,因为这与UBI客户端无关,并且与ecc相关的问题在UBI本身内处理。
erase
erase函数带有下面的参数:
• volume descriptor
• block number
erase函数安排逻辑擦除块的擦除操作。UBI确保物理擦除块在下一次写入访问相同的逻辑块编号之前已擦除,该逻辑块编号分配了一个新的物理擦除块。该函数仅对动态内容分卷有效。
下面的错误代码可能被返回:
-EIO 发生了致命的I/O错误,例如,设备被移除,超时。
-EROFS 试图写一个只读分卷。
-EINVAL 一个无效的块编号请求。
get_volume
get_volume函数带有下面的参数:
• ubi device id
• volume id
• mode read/write – readonly
在其他函数被使用前,必须先调用该函数。该函数标记该分卷是被使用的。每个卷只允许一个用户。
成功时,函数返回指向 UBI卷描述结构的指针。否则返回一个错误码。
下面的错误代码可能被返回:
-ENODEV 被请求的分卷不存在。
-EROFS 尝试对只读卷进行 R/W 访问。
-EBUSY 分卷已经在使用状态。
-EUCLEAN 分卷已经损坏。
put_volume
put_volume函数带有下面的参数:
• volume descriptor
当结束使用一个分卷时调用该函数。该函数标记分卷为未使用状态。
Enhanced: move_update
move_update函数带有下面的参数:
• volume descriptor
• block number
• offset
• length
• buffer pointer
该函数使用UBI基本的块移动算法将一个逻辑擦除块的内容移动到一个空闲的块上。必须扩展块移动算法,以便动态更新块内容。
此功能允许实现块内容的可靠更新,这是r/w块设备仿真的一个前期准备。
用户空间UBI接口
用户空间的接口是在sysfs中实现的。I/O操作的设备节点需要先使用udev机制在用户空间中被创建。
Sysfs接口提供了UBI设备和分卷的大部分只读信息。
操作功能可通过字符设备访问,这些字符设备必须通过 udev 机制创建。
sysfs 接口以人类可读的方式公开有关 UBI 设备和卷的所有状态信息。与基于ioctl的二进制信息交换,这有几个优点。Sysfs接口支持脚本级的分析与监控,且不需要保持内核与用户空间之前的数据结构变化的同步。
枚举可用的分卷id例子如下:
#ls /sys/devices/ubi/0/volumes
sysfs接口
Deviceinfo(sysfs)
• nr. volumes
• nr. bad blocks
• nr. used blocks
• nr. free blocks
• blockbitmap
• stats - erase count statistics, bitflips
• debug switch
Volumeinfo (sysfs)
• name
• size
• type/flags
• used blocks
• blockbitmap
• status
Examples
以Linux buildroot 4.4.155为例:
# ls -F /sys/devices/virtual/ubi/ubi20/
avail_eraseblocks eraseblock_size mtd_num total_eraseblocks
bad_peb_count max_ec power/ ubi20_0/
bgt_enabled max_vol_count reserved_for_bad uevent
dev min_io_size subsystem@ volumes_count
可以通过sysfs查看上述信息。
UBI device I/O functions
UBI设备提供如下的ioctl函数:
• create_volume
• delete_volume
• resize_volume
create_volume
create_volume ioctl 带有如下参数:
• volume id
• volume type
• volume size
• volume name
• minimum subblock size
volume type可以是dynamic/static。minimum subblock size可以是0到系统默认值之间选择或者用于调整UBI客户端的子块大小的值,例如块设备层。
create_volume ioctl 是一个同步操作。
分卷必须不存在,并且分卷的大小必须适合ubi设备的可用大小。
下面的错误代码可能被返回:
-EEXIST 分卷已经存在。
-EINVAL 给出的参数无效。
-ENOSPACE 请求大小对设备无效。
delete_volume
delete_volume ioctl 带有如下参数:
• volume id
分卷必须存在,且没有在使用中。
delete_volume ioctl 是一个同步操作。当函数正确的返回时,FLASH上该分卷以及所有分卷上的数据都已经被删除了;
下面的错误代码可能被返回:
-EBUSY 分卷正在使用中,无法被删除。
-EINVAL 给出的参数无效。
resize_volume
resize_volume ioctl 带有如下参数:
• volume id
• volume size
分卷必须存在,且没有在使用中。如果分卷被扩展,则所需的空间必须是在UBI设备中的空闲空间。当分卷大小减小时,卷中的已用块数必须小于或等于新大小。
下面的错误代码可能被返回:
-EBUSY 分卷正在使用中,无法改变大小。
-EINVAL 给出的参数无效。
-ENOSPACE 请求大小对设备无效。
UBI volume I/O functions
UBI 分卷提供如下函数:
• start_update
UBI 设备提供如下的文件fI/O函数:
• open
• llseek
• read
• write
• close
start_update ioctl
start_update ioctl 带有如下参数:
• data size
data size必须小于或等于分卷的保留大小。当size是有效值时,接下来分卷上存在的擦除块被擦除。这是一个同步操作,以确保在写入新的image数据时没有之前的分卷的残余数据保留在FLASH上。
此调用是使能写操作文件I/O函数对卷的写入访问所必需的。
下面的错误代码可能被返回:
-EBUSY 另外的参数正在操作所以分卷无法被更新。
-EINVAL 给出的参数无效。
-ENOSPACE 请求大小对设备无效。
open
open接口是标准的open系统调用。成功调用会返回一个文件描述符。
下面的错误代码可能被返回:
-EBUSY 分卷正在使用且无法被打开。
-ENODEV 分卷是无效的。
llseek
llseek接口是标准的lseek系统调用。
下面的错误代码可能被返回:
-EINVAL 给出的参数无效。
-EUCLEAN 分卷被破坏了。
read
read接口是标准的read系统调用。
下面的错误代码可能被返回:
-EINVAL 给出的参数无效。
-EUCLEAN 分卷被破坏了。
write
write接口是标准的write系统调用。分卷中的数据内容通过一个连续的二进制串流写入。包含串流数据最后一个字节的write调用是一个同步操作以确保数据被完整的写到FLASH中。
有两种可能的写模式:
动态分卷写模式是一个增强。分卷更新写模式是一个基本要求。
分卷更新写模式
分卷更新对动态/静态分卷都是允许的。
在写之前start_update ioctl立即被调用。完整的分卷数据必须被顺序写入。当写入的数据到达start_update ioctl声明的大小时,写操作被终止。
下面的错误代码可能被返回:
-EINVAL 给出的参数无效。
增强:动态分卷写模式
该写模式支队动态分卷有效。
应用必须llseek到对应的需要写入的offset地址。应用必须知道FLASH的基本操作限制。写入到非空的位置是不允许的。写超出逻辑擦除块的边界也是不允许的。
下面的错误代码可能被返回:
-EINVAL 给出的参数无效。
-EUCLEAN 分卷被破坏了。
-PERM 当请求在没有上一个更新命令的静态卷上写入时返回。
第六章 MTD修改
MTD代码需要做一个小的修改来提供最佳的UBI支持。
Maximum bad block parameter
如上文所述,必须扩展mtd信息结构,以提供一个参数,该参数可通知UBI关于UBI必须处理的最大坏块数。这将减少可用的闪存空间,因为UBI必须为这些空间保留备份块。
第七章 UBI kernel clients
JFFS2
JFFS2需要在UBI之上做一些修改来使用。主要的修改如下:
• 接口封装
• 块擦除
• 坏块处理
• 写失败处理
接口封装
大多数的JFFS2 MTD访问函数被封装成了宏定义,所以只需要替换宏定义。少量剩余的开源编写的mtd访问函数必须封装到现有的宏定义中。
封装接口用UBI结构代替mtd结构,因此JFFS2必须在编译时配置在UBI之上使用。运行时切换是不必要的,并且很难实现。
块擦除
MTD上运行的JFFS2可以自己处理块擦除。所需的改变非常小。JFFS2调用UBI擦除函数并安排块进行擦除,而不是将块排队到擦除(挂起)列表。块被立即放回到空闲块列表中。UBI保证在擦除一个块之前不能重复使用该块的逻辑块编号。这意味着在擦除发生之前,将阻止对计划擦除的逻辑擦除块的写入。为了在JFFS2没有空间压力时,避免这种情况发生,需要将擦除块排列在空闲块列表末尾并从顶部拉出块以使用。
坏块处理
JFFS2不再需要保持追踪坏块了。坏块处理是在UBI层处理,并且当JFFS2使用在UBI层之上时,可以停用JFFS2本身的坏块处理相关的函数。
写失败处理
与写入失败处理相关的 JFFS2 函数可以暂时保留,但这些函数不会调用,因为 UBI 将处理写入失败后的恢复。
增强
可以进一步增强JFFS2。UBI知道由于延迟分配JFFS2使用哪些块,以及哪些块是空的。此信息可供JFFS2使用,并可用于构建空闲块列表而无需扫描块。
增强客户端
块设备仿真
UBI 顶部的块设备仿真层受益于通用UBI基础结构:
静态只读块设备
在静态 UBI 卷之上的只读块设备仿真相当简单。数据内容通过kernel的API读取,并且块仿真只需提供基本功能。
读/写块设备
在UBI动态分卷上的可读写的块设备仿真比在FLASH raw设备上实现起来要简单一些。UBI已经为client提供了许多基本功能。
尽管仍然必须实现缓存写入与必要的擦除功能,但是UBI已经为此提供了函数,上面讨论的增强的move_modify内核接口允许在中断时不会丢失一个完整的逻辑擦除块的实现。
第八章 从UBI启动
本章介绍启动一个已知是UBI系统时要考虑的一般事项。特别是描述了UBI对引导加载程序设计的影响。
当处理器启动时,他们需要被初始化并被带入一个更复杂的软件,例如操作系统,可以被执行的环境。boot-strap阶段使用的环境是非常受限的,并且其资源位于处理器体系结构预先确定的位置。
在传统的嵌入式系统中经常使用NOR FLASH来存储boot-strap代码,并且需要映射到连续内存范围。NAND FLASH 不会自动为boot-strap过程所需的前两条指令提供连续内存范围。相反,需要将特殊硬件作为芯片本身的宏添加,例如,PowerPC 440EP中使用的NDFC,使用FPGA的外部解决方案,或利用供应商特定的解决方案,如三星OneNAND或M系统磁盘芯片。
需要注意的是,即使在一个已知的UBI系统中,FLASH擦除块中包含该初始boot-strap代码,必须无法被移动,就像UBI对其他任何FLASH擦除块一样。删除或更新此内存时需要特别注意,因为当此数据破坏时,系统可能无法再启动。为了克服这一限制,硬件设计必须提供监视机制,用于在预定义的内存意外无效时切换到其他内存位置。
NOR FLASH系统
包含boot-strap代码的擦除块必须是固定的,使用一个非UBI分区。这里系统设计可以像U-BOOT一样为bootloader预留足够所需的数据块。FLASH更新这些特殊的区域需要被设计成渝UBI分卷的更新不一样。
表 8-1. NOR系统分区
从UBI中排除的擦除块(上表中0-4)不是由于磨损水平或擦洗造成的。如果发生位错误或需要更新模块,则在执行的过程中系统不能被关闭。如果系统应该能够从断电或中断中恢复,它必须提供系统特定的硬件机制与监视器和方法来使用存储在其他位置的引导代码的冗余副本。
为了能够从静态UBI卷加载操作系统,代码必须包含UBI扫描过程以及UBI加载程序。
NAND FLASH系统
NAND上存储boot-strap代码的擦除块经常是编号为0的擦除块。NAND芯片制造商保证这个块是一个好的块。
因为在基于 NAND 的系统中,初始代码的连续内存是有限的,复杂的boot代码需要被分为两部分。这些部分被称为IPL(初始程序加载)和SPL(二级程序加载)。将APL存储在一个静态的UBI分卷是很有好处的,甚至可以是冗余的,允许安全更新过程,并获取 UBI 的优势。用户不需要让我们想到磨损水平,坏块处理和擦洗这些代码。
扫描NAND芯片上的UBI数据,并使用2~3K字节的ECC数据来更正UBI的错误数据使可能的。系统设计人员可以考虑直接使用操作系统映像,而不是使用SPL,如果他能够在IPL中执行必要的系统初始化。
在这种系统中NAND block 0需要被定义在一个固定的位置。
表 8-2. NAND系统分区
IPL与SPL
我们假设大多数的NAND实现将采用分离的IPL和SPL。我们认为一些大小限制的设计将选择直接加载操作系统映像,而不是使用SPL。以下示例应说明如何设置具有冗余 SPL 和 OS 映像的系统。
分卷布局示例
示例布局显示固定擦除块 0。块0包含由监视机制选择的IPL的冗余版本。UBI 卷编号从此处开始 2,用于SPL的第一个冗余副本。SPL的第二个副本存储在卷 3中。卷4和5用于SPL配置数据,如引导参数或平台描述数据(PDD)。卷 6和7用于操作系统二进制映像。所有列出的UBI卷都是静态的。后续卷(例如 8、9 和 10)可用于文件系统。这些将是动态UBI卷。
图 8-1 分卷布局示例
IPL操作示例:PPC44x使用NAND
Bootloader中的UBI支持
Bootloader需要被扩展以能处理UBI管理的FLASH设备。实现可能无法提供全面的 UBI 支持,但必须实现基本功能。
设备扫描
在初始引导加载程序中,需要扫描UBI设备,以便能够使用UBI卷加载下一步引导代码或操作系统。建议将初始扫描结果传递给SPL以加快启动过程。
UBI设备的扫描可通过完全闪存扫描执行,或者通过分析基于闪存的擦除块转换表进行。由于基于FLASH的擦除块表不需要存在或有效,因此始终可以进行完全扫描。这是UBI设计要求之一。
完全扫描读取所有 FLASH 擦除块的标题,并创建可用 UBI 卷的列表。扫描可以分3个步骤执行。首先,将每个标头读入RAM数组,并检查UBI魔数的可用性以及标头CRC的一致性。计算每个卷的擦除块数。此信息用于创建包含最终加载顺序表中的分卷偏移量的表。此表是在扫描的最后一步中创建的。对于每个找到的分卷,image部分的擦除块编号按加载顺序存储。
为了优化扫描时间,对于使用 NAND的系统可以省略读取OOB区域。数据是否有效的信息是从数据CRC的正确性中得出的。
由于目前尚未在 UBI 中设计此机制,因此尚未描述基于 FLASH 的擦除块转换表的处理。SPL 或初始引导加载程序中的扫描结果可以存储在基于 RAM 的结构中,该结构列出所有找到的卷、每个卷的已用块数以及按加载顺序对每个卷的逻辑块的引用。
扫描结果表
在分离的IPL/SPL实现中该表信息是在IPL中生成的,因为IPL需要该表信息来加载SPL。SPL可以重新使用该表去加载例如,操作系统映像的不同版本。
读取分卷
Boot代码支持读取UBI分卷可以被限制在仅支持金泰分卷以保持它的简单性。动态分卷raw数据的读当然也是可能的,但是使用这部分内容需要了解其数据结构的细节。
静态分卷的读操作支持用来从FLASH加载二进制的image例如kernel,initrd或者boot参数等等数据原型到RAM中。
读取支持应注意更新的中断以及块移动操作。如果更新中断,无法检索卷内容。在块移动操作中断的情况下,应用UBI设计文档中所述的鲁棒性机制。
写分卷
对UBI分卷的写入支持需要实现以下功能:
• 坏块识别
• 分卷布局分析
• 擦除计数保留块擦除
• UBI逻辑擦除块的写操作
• UBI静态分卷的更新操作
• 保留块的可知性