#littlefs原理分析#[六]磨损均衡

时间:2022-11-24 10:59:55

作者:蒋卫峰 李涛

前言

前面已经对littlefs的原理分析了5篇文章,内容包括了:

  • littlefs整体的存储结构

  • commit机制

  • fetch操作

  • 目录操作

  • 文件读写操作

本文是littlefs原理分析系列最后一篇文章,主要介绍littlefs中与磨损均衡相关的策略,同时也会对其中的块分配算法进行介绍。

littlefs有以下防止磨损相关的措施:

  1. 写时坏块的检测和写入恢复
  2. 均匀地进行块的分配:由块分配算法实现
  3. 定期重分配元数据所在块

1. 写时坏块的检测和写入恢复

littlefs中当进行文件、目录元数据等的写入时,最后会调用函数lfs_bd_flush将数据最终写入到磁盘。lfs_bd_flush函数写入完后,会将内存中写入的数据和磁盘上的数据进行比较。如果数据不一致,则可能是坏块。

方法如下:

  • 写入时通过回读磁盘上的数据进行验证,来检测坏块
  • 检测到坏块后,清除坏块,重新分配块,然后重新写入

lfs_bd_flush函数检查数据是否一致部分的分析如下:

// 当将缓存中的数据回写到磁盘时,检测坏块
lfs_bd_flush(lfs_t *lfs,
|       lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) 
|   ...
|
|   // 调用lfs_bd_cmp比较磁盘上的数据是否与写入的数据相同
|   // 如果不同则可能遇到了坏块
|-> if (validate) {
|       lfs_bd_cmp(lfs,
|               NULL, rcache, diff,
|               pcache->block, pcache->off, pcache->buffer, diff); 
|   }
|
|-> ...

如在文件写入数据时,在函数lfs_file_flush中,检测到坏块时会重新分配块再进行写入操作:

lfs_file_flush(lfs_t *lfs, lfs_file_t *file)
|-> ...
|
|-> while (true) {
|       // 调用lfs_bd_flush写入数据,并比较数据是否写入正确
|       int err = lfs_bd_flush(lfs, &file->cache, &lfs->rcache, true);
|       if (err) {
|           // 检测到坏块则跳转到relocate
|           if (err == LFS_ERR_CORRUPT) {
|               goto relocate;
|           }
|           return err;
|       }
|       break;
|
|   relocate:
|       // 重新分配块并再次进行写入操作
|       LFS_DEBUG("Bad block at 0x%"PRIx32, file->block);
|       err = lfs_file_relocate(lfs, file);
|       if (err) {
|           return err;
|       }
|   }

2. 块分配

2.1 lookahead buffer

littlefs中使用一个lookahead buffer来管理和分配块。lookahead buffer是一个固定大小的bitmap,记录一片区域内块分配的信息。

lookahead buffer图例如下,其中假设总共有64个块,lookahead buffer的大小为8,lookahead buffer对应块中现分配了文件A、D和目录B、C的块:

#littlefs原理分析#[六]磨损均衡

lookahead buffer相关数据结构如下:

struct lfs_free {
    lfs_block_t off;     // lookahead所有block整体的偏移
    lfs_block_t size;    // lookahead中块的总数
    lfs_block_t i;       // 在lookahead_size中的索引,表示当前位于第几个block
    lfs_block_t ack;     // 所有剩余空闲block个数
    uint32_t *buffer;    // lookahead的bitmap块管理缓存区
} free;

2.2 查找已分配的块

lookahead buffer只记录了一片区域内块分配的信息,当需要知道其他区域块分配的情况时,就需要进行扫描文件系统来查找已分配的块。如lookahead buffer中已经没有空闲块、需要推移lookahead buffer来查找文件系统中的其他空闲块。

扫描和查找已分配的块的过程如下:

  1. 将lookahead buffer位置推移一个lookahead_size,并将lookahead buffer清0。

  2. 从超级块开始遍历文件系统中所有目录和文件,以遍历所有已分配的块。如果块位于lookahead buffer所管理区域,则将lookahead buffer中相应位置为1。

lookahead buffer只用固定大小的bitmap存储已分配块的信息,是littlefs中的一种权衡,这样虽然更耗费时间,但有效节省了RAM空间资源。

代码分析如下:

lfs_alloc(lfs_t *lfs, lfs_block_t *block)
|   ...
|
|   // 当lookahead buffer中没有空闲块时,需进行扫描
|
|   // 1. 推移lookahead buffer
|-> lfs->free.off = (lfs->free.off + lfs->free.size)
|            % lfs->cfg->block_count;
|   lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, lfs->free.ack);
|   lfs->free.i = 0;
|
|   // 2. 将lookahead buffer清0
|-> memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size);
|
|   // 3. 遍历文件系统进行扫描和查找
|-> lfs_fs_rawtraverse(lfs, lfs_alloc_lookahead, lfs, true);
|
|-> ...

其中,lfs_fs_rawtraverse函数会从超级块开始遍历整个文件系统,对整个文件系统中所有已经分配的块调用回调函数lfs_alloc_lookahead。lfs_alloc_lookahead函数分析如下:

// lfs_fs_rawtraverse函数传入到lfs_alloc_lookahead函数的参数
// 分别为lfs结构体指针p,和块号block
lfs_alloc_lookahead(void *p, lfs_block_t block)
|-> lfs_t *lfs = (lfs_t*)p;
|
|   // 获取块号相对lookahead buffer的偏移
|-> lfs_block_t off = ((block - lfs->free.off)
|           + lfs->cfg->block_count) % lfs->cfg->block_count;
|
|   // 若该块处于lookahead buffer所管理的范围内,
|   // 则设置bitmap对应位,表示该块已分配
|-> if (off < lfs->free.size) {
|       lfs->free.buffer[off / 32] |= 1U << (off % 32);
|   }
|   return 0;

2.3 块分配算法

块分配算法的过程总结:首先尝试从lookahead buffer中找到下一个空闲块,若没有则将lookahead buffer位置推移一个lookahead_size,执行上一小节中的扫描和查找文件系统过程,再尝试从lookahead buffer中找到下一个空闲块,以此循环进行。

以下为几次分配和扫描的示例:

boot...         lookahead:
                fs blocks: fffff9fffffffffeffffffffffff0000
scanning...     lookahead: fffff9ff
                fs blocks: fffff9fffffffffeffffffffffff0000
alloc = 21      lookahead: fffffdff
                fs blocks: fffffdfffffffffeffffffffffff0000
alloc = 22      lookahead: ffffffff
                fs blocks: fffffffffffffffeffffffffffff0000
scanning...     lookahead:         fffffffe
                fs blocks: fffffffffffffffeffffffffffff0000
alloc = 63      lookahead:         ffffffff
                fs blocks: ffffffffffffffffffffffffffff0000
scanning...     lookahead:         ffffffff
                fs blocks: ffffffffffffffffffffffffffff0000
scanning...     lookahead:                 ffffffff
                fs blocks: ffffffffffffffffffffffffffff0000
scanning...     lookahead:                         ffff0000
                fs blocks: ffffffffffffffffffffffffffff0000
alloc = 112     lookahead:                         ffff8000
                fs blocks: ffffffffffffffffffffffffffff8000

2.4 均匀分配方法

介绍了块分配算法后,现在回过来介绍块分配算法中与磨损均衡相关的策略。

littlefs中使用了一个简单的策略来实现均匀地分配:

  • 使用lookahead buffer线性地分配块,这样在一次运行中块分配是循环磁盘均匀进行的
  • 每次挂载文件系统时,将lookahead buffer推移一个随机的偏移量,这样在多次运行过程中,只要这个随机偏移量是均匀的,那么整体的分配也是均匀的

相关函数分析:

lfs_mount(lfs_t *lfs, const struct lfs_config *cfg)
|-> lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg)
    |-> ...
    |
    |   // 1. 计算随机数
    |-> lfs_dir_fetchmatch(...)
        |-> ...
        |
        |   // 使用crc计算随机数
        |-> lfs->seed = lfs_crc(lfs->seed, &crc, sizeof(crc));
        |
        |-> ...
    |
    |   // 2. 随机对lookahead buffer进行偏移
    |-> lfs->free.off = lfs->seed % lfs->cfg->block_count;

3. 定期重分配元数据所在块

littlefs中会定期进行元数据对应块的重分配,以防止元数据块的磨损。

每次元数据commit过程中因空间不足,而进行compact或split操作时,revision count也会随着更新。当revision count为block_cycles的整数倍时,会进行元数据对应块的重分配。其中,block_cycles为用户配置的值。

相关函数分析:

lfs_dir_compact(lfs_t *lfs,
|       lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount,
|       lfs_mdir_t *source, uint16_t begin, uint16_t end) 
|-> ...
|
|   // revision count为block_cycles的整数倍时,进行元数据对应块的重分配
|-> if (lfs->cfg->block_cycles > 0 &&
|       (dir->rev % ((lfs->cfg->block_cycles+1)|1) == 0)) {
|       ...
|
|       // we're writing too much, time to relocate
|       tired = true;
|       goto relocate;
|   }
|
|-> ...
|
|   relocate:
|-> ...

总结

本文介绍了littlefs中与磨损均衡相关的策略以及块分配算法,到这里littlefs文件系统原理分析系列文章已经结束。小编也是希望通过对littlefs文件系统的仔细分析,让相关读者更深入了解OpenHarmony LiteOS-A内核的文件系统的原理,而且littlefs文件系统也不仅仅是在OpenHarmony系统上使用,它也是一个广泛使用的小型文件系统,相信掌握它的原理对嵌入式开发者有着“鼎力相助”的作用。

更多原创内容请关注:深开鸿技术团队

入门到精通、技巧到案例,系统化分享OpenHarmony开发技术,欢迎投稿和订阅,让我们一起携手前行共建生态。

想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com/#bkwz​