3

#littlefs原理分析#[六]磨损均衡-开源基础软件社区-51CTO.COM

 1 year ago
source link: https://ost.51cto.com/posts/19419
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

作者:蒋卫峰 李涛

前面已经对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开发技术,欢迎投稿和订阅,让我们一起携手前行共建生态。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK