第四章:WL磨损均衡——让SSD里的每个“格子”都活到老

各位同学好,我是老周。今天咱们聊聊磨损均衡(Wear Leveling,简称WL)。

说实话,做SSD固件这么多年,我见过太多因为磨损均衡没做好,导致整盘提前报废的案例。有个项目我印象特别深——客户反馈某批SSD用了不到半年,写入性能直接掉到原来的十分之一。查到最后,问题就出在WL算法上。

嗯,咱们今天就把这块彻底讲透。

4.1 为什么需要磨损均衡?

先问个问题:NAND Flash的擦写次数是有限的,对吧?SLC大概10万次,MLC 3000-5000次,TLC 1000-1500次,QLC就更少了。

那问题来了——如果系统一直往同一个Block写数据,这个Block很快就挂了。而其他Block可能还“年轻力壮”。

说白了,磨损均衡就是让所有Block的擦写次数尽量接近。别让某个Block“过劳死”。

核心目标:所有Block的擦写次数方差最小化。

4.2 动态磨损均衡 vs 静态磨损均衡

这里有两个概念,我当年也花了不少时间才真正搞明白。

4.2.1 动态磨损均衡

动态磨损均衡,说白了就是“谁年轻谁干活”。

每次要写数据时,FTL(Flash Translation Layer)会挑一个擦写次数最少的Block来写。这样新数据总是往“年轻”的Block上放。

听起来很合理对吧?但有个坑——

冷数据怎么办?

比如你存了一张照片,写进去之后再也不动了。这个Block的擦写次数就一直停在1。而其他Block可能已经被擦了1000次。

这就是动态磨损均衡的局限性:它只考虑了“写”的过程,没考虑“不动”的数据。

4.2.2 静态磨损均衡

静态磨损均衡就是来解决这个问题的。

它的思路是:把那些长期不动的“冷数据”搬走,让“年轻”的Block去存冷数据,把“年老”的Block腾出来给热数据用。

我在项目中遇到过这样一个场景:某款监控设备,每天写入量很大,但系统分区里的数据几乎不变。如果不做静态磨损均衡,系统分区的Block擦写次数永远是1,而数据分区的Block可能已经快挂了。

我的建议:消费级SSD可以只做动态磨损均衡,但企业级SSD必须上静态磨损均衡。否则,你等着被客户骂吧。

4.3 冷热数据分离

冷热数据分离,是磨损均衡的前置条件。

你想想看,如果不知道哪些数据是“热”的、哪些是“冷”的,你怎么做均衡?

常见的做法是:

  • 按LBA地址分:连续写入的LBA范围认为是热数据
  • 按写入频率分:每个LBA维护一个计数器,超过阈值就是热数据
  • 按时间窗口分:最近N分钟内写入次数多的就是热数据

我个人习惯用第二种方法,配合一个老化机制。比如:

// 冷热数据判断伪代码
#define HOT_THRESHOLD  10   // 10次写入算热
#define COLD_THRESHOLD 3    // 3次以下算冷

typedef struct {
    uint32_t lba;
    uint32_t write_count;
    uint32_t last_write_time;
} LBA_HOT_COLD;

bool is_hot_data(LBA_HOT_COLD *entry) {
    // 最近1小时内写入超过10次
    if (entry->write_count > HOT_THRESHOLD) {
        return true;
    }
    return false;
}

嗯,这里要注意:计数器不能无限增长,要定期衰减。否则所有数据最终都会变成“热数据”。

4.4 磨损均衡阈值设置

阈值设置,是WL算法里最考验经验的地方。

设得太小,均衡效果不明显;设得太大,搬数据开销太大,影响性能。

我一般这样设:

参数 推荐值 说明
动态均衡阈值 最大擦写次数 - 最小擦写次数 > 100 触发动态均衡
静态均衡阈值 最大擦写次数 - 最小擦写次数 > 500 触发静态均衡
冷数据搬移阈值 数据超过24小时未更新 搬移冷数据
均衡周期 每100次写入检查一次 避免频繁检查

我曾经踩过的坑:阈值设得太激进,结果系统一直在搬数据,写入放大率飙升到5以上。后来把静态均衡阈值从200调到500,问题解决了。

4.5 磨损均衡的整体流程

下面这张图是我自己画的,把整个流程串起来了:

磨损均衡核心流程 写入请求到达 冷热数据分离判断 是否触发均衡? 直接写入 动态/静态均衡 搬移冷数据 写入新数据 完成

4.6 实际项目中的经验总结

最后,分享几条我这些年总结的经验:

  1. 不要追求绝对均衡。 所有Block擦写次数完全一样是不可能的,误差在10%以内就算合格。
  2. 冷数据搬移要“温柔”。 一次别搬太多,否则写放大率会爆炸。我一般每次搬不超过4个Block。
  3. 阈值要动态调整。 随着SSD寿命的消耗,阈值可以适当放宽。比如新盘阈值设100,用了80%寿命后可以调到200。
  4. 别忘了坏块管理。 磨损均衡和坏块管理要联动。坏块多了,剩余Block的压力就大,阈值要相应调整。

一个小技巧:在固件里加一个调试接口,可以实时查看所有Block的擦写次数分布。开发阶段特别有用,能帮你快速定位问题。

好了,磨损均衡这块就讲到这里。内容不少,但核心就一句话:让所有Block“同甘共苦”,别让任何一个提前退休。

专注资料整理