第四章: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 实际项目中的经验总结
最后,分享几条我这些年总结的经验:
- 不要追求绝对均衡。 所有Block擦写次数完全一样是不可能的,误差在10%以内就算合格。
- 冷数据搬移要“温柔”。 一次别搬太多,否则写放大率会爆炸。我一般每次搬不超过4个Block。
- 阈值要动态调整。 随着SSD寿命的消耗,阈值可以适当放宽。比如新盘阈值设100,用了80%寿命后可以调到200。
- 别忘了坏块管理。 磨损均衡和坏块管理要联动。坏块多了,剩余Block的压力就大,阈值要相应调整。
一个小技巧:在固件里加一个调试接口,可以实时查看所有Block的擦写次数分布。开发阶段特别有用,能帮你快速定位问题。
好了,磨损均衡这块就讲到这里。内容不少,但核心就一句话:让所有Block“同甘共苦”,别让任何一个提前退休。