使用RAM和Linux NBD构建一套FTL验证方案

目录

本文简单介绍在PC中使用RAM模拟Nand Flash相关操作, 在该虚拟Flash上集成已有的FTL,并使用Linux NBD 配合FTL实现一个可被OS访问的块设备。通过虚拟Nand Flash、 FTL、NBD、Linux块设备和文件系统,本文构建了一套可用 于测试与验证FTL设计的方案。

git仓库地址:https://github.com/dwniaoniao/nandsim.git

1. 写在开头

真实的Flash芯片是多种多样的,有在块/页组织方式的不同, 在操作方式存在不同,品质/工艺存在差异,坏块分布不同, 擦写次数不同;在真实Flash上调试FTL时有很多麻烦:读/写慢, 反复测试耗时,容易损耗芯片等。因此,可以考虑使用基于RAM 的读写来模拟Flash的行为提高调试和开发效率。在虚拟的Flash 上测试和验证通过后,再在真实的Flash上继续测试FTL的可靠性 和性能。

本文在此提出用RAM模拟Flash的方案不仅在FTL设计阶段可用于 初步验证算法,还可以在FTL实现完成后模拟成在Linux中的块 设备,方便使用现有的文件系统如Fat/Ext4等执行格式化并进行 文件读写测试。此外,还可在FTL运行过程中随时读取虚拟Flash 中的数据而无需中断运行。

2. 总体架构

本文所提出的方案,总体架构按层次如下所示:

+----------------------------------------------------------+
|                  File Systems(fat/ext/...)               |
+----------------------------------------------------------+
|                    Block Device /dev/nbd0                |
+----------------------------------------------------------+
|                         NBD Server                       |
+----------------------------------------------------------+
|   FTL   |  nand_erase  |  nand_read  |  nand_write       |
+----------------------------------------------------------+
|          Virtual NAND Flash Service (nandsim)            |
+----------------------------------------------------------+
  • 最底层的nandsim使用RAM模拟对Flash的读/写/擦除操作,以常驻 服务的方式运行,使用Linux进程间交互的机制为上层的FTL/工具 提供操作虚拟Flash的接口;
  • 基于nandsim提供的接口,FTL和调试所用的工具以类似真实Flash 的方式对虚拟Flash进行读/写/擦除等操作;此外,FTL对上层实现对 Flash读写的抽象,调试用的工具可以在系统运行的过程中直接操作 虚拟Flash而无需中断系统运行;
  • NBD服务是系统访问块设备与FTL之间的桥梁,内核对块设备的读写 请求通过用户态的NBD服务转发给FTL;
  • /dev/nbd0 是加载Linux NBD内核模块后产生的块设备,对该块设备 的请求将会转发到NBD服务,由NBD服务完成实际的数据读写并返回 结果;
  • 在文件系统看来/dev/nbd0就如同一个标准块设备,可以对其执行 格式化/挂载/读写文件等操作。

3. 使用RAM模拟Nand Flash

3.1 最初尝试

我在实现自己的FTL时,最开始也使用了在PC上使用RAM模拟Flash操作 的方法,在FTL需要对Flash进行读/写/擦除操作时,使用读写RAM buffer 的方式替代,如下所示:

uint8_t flash_buf[FLASH_CAP];

void flash_erase(uint16_t blk)
{
    memset((void *)(flash_buf + BLK_TO_ADDR(blk)), 0xff, BLOCK_SIZE);
}

void flash_program(uint8_t *buf, uint16_t psec, uint16_t len)
{
    memcpy((void *)(flash_buf + SEC_TO_ADDR(psec)), (void *)buf, (size_t)len);
}

void flash_read(uint8_t *buf, uint16_t psec, uint16_t off, uint16_t len)
{
    memcpy((void *)buf, (void *)(flash_buf + SEC_TO_ADDR(psec) + off), (size_t)len);
}

这种方法确实在可以在FTL设计时无需操作真实的Flash,但也 存在一些问题:

  • 模拟Flash操作是在FTL中实现的,后续如果想继续添加模拟Flash 相关的其他功能如错误bit、坏块标记、模拟断电等将会使FTL实现 变得复杂,不方便最终迁移到真实Flash;
  • 调试不方便:查看虚拟Flash中的内容需要调试器并且经常要中断 系统运行。

3.2 新的方案

基于以上方法存在的不足,对新的方案要求如下:

  • 将模拟Flash操作的逻辑实现与FTL分隔开,抽象成服务的形式供 FTL使用;
  • 虚拟Flash服务不仅可用于FTL,还可以配合辅助工具,在FTL运行 过程中对虚拟Flash进行事实访问与分析而无需中断系统运行。

最初接触到Linux内核中的nandsim,nandsim可以在内核中模拟Flash芯片, 并用于测试JFFS2或UBIFS等MTD文件系统。一开始打算基于它来实现 我的方案,但后面发现其使用不够灵活。于是我自己尝试基于Linux 进程间交互机制实现了一套客户/服务架构的用户态程序,nandsim作为 单一的服务节点常驻运行,处理来自客户端FTL和辅助工具操作虚拟Flash 的请求。通过这种分离式设计,底层Flash行为模型与上层FTL逻辑实现 相互独立,同时也为运行时监控和扩展功能预留了接口。

目前我的虚拟Flash服务nandsim仅简单模拟Flash读/写/擦除行为, 可自定义配置块/页数目和块/页/OOB大小,辅助工具nand_erase, nand_read和nand_write可以在nandsim运行起来之后操作虚拟Flash。 它们的实现可以参考本文开头给出的项目仓库。

后续可以考虑为我的nandsim加入以下功能:

  • 模拟错误bit;
  • 模拟坏块;
  • 模拟掉电。

4. 添加FTL

项目仓库包含的FTL架构与设计可参考本站文章: 用STM32F103和SPI Flash实现一个U盘, FTL实现中对Flash的读/写/擦除操作均使用虚拟Flash服务提供的接口。

5. 本地NBD服务

本系统基于Linux NBD机制实现了一个本地NBD服务,该服务 在用户态运行,通过与内核NBD驱动建立连接,监听本机系统对 块设备/dev/nbd0的读写请求,当内核收到对该块设备的访问 操作时,会将请求封装为NBD报文协议转发至用户态NBD服务, NBD服务将逻辑块读写请求交由FTL处理。

本地NBD服务的实现可参考本文开头给出的项目仓库。

6. 块设备测试

通过以上所述的虚拟Flash,FTL,NBD服务,实现了一个可供 系统访问的块设备,此处介绍部署方法和简单测试。

1. 运行虚拟Flash服务:
~/.../nandsim/build >>> ./nandsim
Total size: 16777216
Total size with OOB: 16777216
Block size: 65536
Block size with OOB: 65536
Page size: 512
Page size with OOB: 512
OOB size: 0
Block number: 256
Page number: 32768
Page per block: 128

以上操作使用RAM模拟一个具有256个块,每块128个页,页大小为512字节, 没有OOB,总容量为16MB的Flash。

2. 加载NBD内核模块:
sudo modprobe nbd nbds_max=1

加载完毕后生成块设备/dev/nbd0。

3. 运行本地NBD服务:
sudo ./nbd_server

使用lsblk查看块设备,可见/dev/nbd0容量为15.4M:

nbd0         43:0    0  15.4M  0 disk
4. 块设备直接读写
~/.../nandsim/build >>> dd if=/dev/urandom of=input.bin bs=1K count=8
8+0 records in
8+0 records out
8192 bytes (8.2 kB, 8.0 KiB) copied, 7.0933e-05 s, 115 MB/s
~/.../nandsim/build >>> ls -lh input.bin
-rwxr-xr-x 1 jiawei jiawei 8.0K Feb 14 16:48 input.bin
~/.../nandsim/build >>> sudo dd if=input.bin of=/dev/nbd0 bs=512 seek=0 status=progress conv=sync
16+0 records in
16+0 records out
8192 bytes (8.2 kB, 8.0 KiB) copied, 5.0584e-05 s, 162 MB/s
~/.../nandsim/build >>> sudo dd if=/dev/nbd0 of=output.bin bs=512 seek=0 count=16 conv=sync
16+0 records in
16+0 records out
8192 bytes (8.2 kB, 8.0 KiB) copied, 0.000193719 s, 42.3 MB/s
~/.../nandsim/build >>> cmp input.bin output.bin
5. 文件系统格式化、挂载和读写
~ >>> sudo mkfs.vfat -F 16 /dev/nbd0
mkfs.fat 4.2 (2021-01-31)
~ >>> sudo fsck.vfat -v /dev/nbd0
fsck.fat 4.2 (2021-01-31)
Checking we can access the last sector of the filesystem
Boot sector contents:
System ID "mkfs.fat"
Media byte 0xf8 (hard disk)
       512 bytes per logical sector
      2048 bytes per cluster
         4 reserved sectors
First FAT starts at byte 2048 (sector 4)
         2 FATs, 16 bit entries
     16384 bytes per FAT (= 32 sectors)
Root directory starts at byte 34816 (sector 68)
       512 root directory entries
Data area starts at byte 51200 (sector 100)
      7879 data clusters (16136192 bytes)
32 sectors/track, 2 heads
         0 hidden sectors
     31616 sectors total
Checking for unused clusters.
/dev/nbd0: 0 files, 0/7879 clusters
~ >>> sudo mount -t vfat /dev/nbd0 /mnt -o uid=$(id -u),gid=$(id -g),umask=022
~ >>> echo "hello" > /mnt/hello.txt
~ >>> sudo sync
~ >>> echo "hello" > /mnt/hello.txt
~ >>> cat /mnt/hello.txt
hello