垃圾回收机制GC——SSD如何在“不能覆写“的世界里清理空间?

📅 2026/7/25 22:41:40
垃圾回收机制GC——SSD如何在“不能覆写“的世界里清理空间?
摘要NAND闪存有一个致命特性——写前必须擦除且擦除的最小单位是块Block不能像HDD那样直接覆盖单个扇区。当空闲块不足时SSD必须在后台执行垃圾回收Garbage Collection将有效数据搬出、再擦除整个块。本文深入解析GC的工作原理、触发机制、Victim Block选择策略、前后台冲突以及TRIM命令如何大幅提升GC效率。一、为什么SSD需要垃圾回收1.1 NAND的不可覆盖困境这是理解GC的核心前提。HDD写入数据时磁头可以直接覆盖旧数据——想改哪个扇区就改哪个。但NAND闪存完全不同┌─────────────────────────────────────────────────────┐ │ HDD vs NAND 写入方式对比 │ ├──────────┬──────────────────┬───────────────────────┤ │ 操作 │ HDD机械硬盘 │ NAND闪存 │ ├──────────┼──────────────────┼───────────────────────┤ │ 写入 │ 直接覆盖 │ 只能写空页空白Page │ │ 修改 │ 直接覆盖原位置 │ 写入新位置 标记旧页无效│ │ 删除 │ 标记删除 │ 标记无效不立即释放 │ │ 擦除 │ 不需要 │ 按Block整体擦除几百页 │ │ 覆写前提 │ 无 │ 必须先擦除整个Block │ └──────────┴──────────────────┴───────────────────────┘关键概念层级Block块 擦除的最小单位通常包含 256~1024 个 Page Page页 写入/读取的最小单位通常 4KB~16KB也就是说哪怕你只想修改Block里一个Page的数据也必须把整个Block的数据搬走、擦除、再重新写入。这就是GC存在的根本原因。1.2 页的四种状态在SSD内部每个Page都处于以下状态之一┌──────────────┬──────────────────────────────────┐ │ 页面状态 │ 说明 │ ├──────────────┼──────────────────────────────────┤ │ Free空闲 │ 从未写过数据可以直接编程Program│ │ Valid有效│ 包含最新有效数据FTL映射表有记录 │ │ Invalid无效│ 数据已过期或被更新但尚未擦除 │ │ Bad坏块 │ 物理损坏永久不可用 │ └──────────────┴──────────────────────────────────┘随着用户不断写入和更新数据Invalid页会越来越多Free页越来越少。当Free页耗尽时SSD就没有空间写入新数据了——GC的作用就是把Invalid页清理掉回收出Free Block。二、垃圾回收的工作原理2.1 GC的基本流程四步走整个过程可以概括为挑选→搬移→擦除→归还Step 1: 选择一个 Victim Block受害者块 ↓ 该块中包含大量 Invalid Page Step 2: 将块中仍然 Valid 的 Page 搬到其他 Block ↓ 这个过程叫数据 rescue救援 Step 3: 擦除整个 Victim Block ↓ 所有 Page 恢复为 Free 状态 Step 4: 将回收的 Block 加入 Free Block Pool ↓ 可以接受新的写入请求用代码逻辑表示defgarbage_collect():# Step 1: 选择 Victim Blockvictimselect_victim_block()# 选择策略见下文# Step 2: 搬移有效数据forpageinvictim.pages:ifpage.statusVALID:new_locationallocate_free_page()write_data(new_location,page.data)update_ftl_mapping(page.lpn,new_location)page.statusINVALID# Step 3: 擦除整个 Blockerase_block(victim)# Step 4: 归还到空闲池victim.statusFREE free_block_pool.append(victim)# 统计这次GC的成本valid_pages_movedcount_valid_pages(victim)gc_costvalid_pages_moved/PAGES_PER_BLOCKreturngc_cost# 越接近0越好2.2 一个具体的例子假设一个Block包含8个Page其中5个已经无效用户更新了数据3个仍然有效GC 前: ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐ │Valid │Invalid│Valid │Invalid│Invalid│Valid │Invalid│Invalid│ │ 旧A │ 旧B │ 旧C │ 旧D │ 旧E │ 旧F │ 旧G │ 旧H │ └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘ Step 1-2: 搬移3个Valid页到新位置 ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐ │ →搬走 │ │ →搬走 │ │ │ →搬走 │ │ │ │ 旧A │ 旧B │ 旧C │ 旧D │ 旧E │ 旧F │ 旧G │ 旧H │ └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘ Step 3: 擦除整个Block ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐ │ Free │ Free │ Free │ Free │ Free │ Free │ Free │ Free │ └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘ Step 4: 加入 Free Block Pool ✅关键观察GC并不是免费的——它需要额外搬移有效数据产生内部写入。搬移的页越多GC的成本越高。这就引出了GC策略的核心问题如何选择Victim Block使得搬移成本最低三、Victim Block选择策略GC策略的好坏直接影响SSD的性能和寿命。业界有几种经典策略3.1 Greedy贪心策略规则选择Invalid页最多即Valid页最少的Block。Block A: 7 Valid 1 Invalid → GC成本 7/8 87.5% ❌ 太高 Block B: 2 Valid 6 Invalid → GC成本 2/8 25% ✅ 贪心选中 Block C: 4 Valid 4 Invalid → GC成本 4/8 50%优点缺点每次GC回收效率最高忽略了页的年龄冷热搬移数据量最少可能导致热数据被反复搬移实现简单长期来看WAF不一定最优3.2 FIFO先进先出策略规则选择最早被写入的Block最老的Block。Block A: 写入时间 T100s, 5 Invalid → 最老优先回收 Block B: 写入时间 T500s, 3 Invalid Block C: 写入时间 T800s, 7 Invalid优点缺点实现极其简单可能选中Valid页很多的Block不会遗漏老数据GC效率不稳定3.3 Cost-Benefit收益成本策略规则综合考虑Block的Invalid比例和年龄选择收益/成本比最高的。Benefit (Invalid Pages / Total Pages) × (当前时间 - 最后修改时间) Cost Valid Pages需要搬移的代价 Score Benefit / Cost → 选最高的这是理论上最优的策略但计算开销较大。3.4 策略对比总结┌──────────────┬──────────┬──────────┬──────────────┐ │ 策略 │ GC效率 │ 实现复杂度 │ 适用场景 │ ├──────────────┼──────────┼──────────┼──────────────┤ │ Greedy │ ★★★★ │ ★★ │ 消费级SSD │ │ FIFO │ ★★ │ ★★★★★ │ 轻量级固件 │ │ Cost-Benefit │ ★★★★★ │ ★★ │ 企业级SSD │ │ 混合策略 │ ★★★★★ │ ★★★ │ 主流方案 │ └──────────────┴──────────┴──────────┴──────────────┘实际固件中的做法现代SSD通常采用混合策略——先用Greedy快速筛选候选集再用Cost-Benefit做精细排序。部分厂商还会结合温度感知Temperature-aware GC将冷数据Block优先回收。四、前台GC与后台GC4.1 两种GC模式┌─────────────────────────────────────────────────────────────┐ │ GC 的两种执行模式 │ ├──────────────────────┬──────────────────────────────────────┤ │ 前台 GC │ 后台 GC │ │ (Synchronous GC) │ (Asynchronous GC) │ ├──────────────────────┼──────────────────────────────────────┤ │ Free Block 耗尽时 │ SSD 空闲时自动执行 │ │ 被迫停止主机写入 │ 不影响主机正常读写 │ │ 主机感知到延迟 │ 用户完全无感知 │ │ 性能严重下降 │ 提前准备空闲块避免前台GC │ │ SSD变卡的元凶 │ 高端SSD的核心竞争力 │ └──────────────────────┴──────────────────────────────────────┘4.2 为什么用久了SSD会变慢很多用户反映SSD用了一段时间后明显变慢GC是重要原因之一全新 SSD: Free Block 充足 → 所有写入直接落入空闲页 → 无需GC → 速度飞快 使用一段时间后: Free Block 减少 → 写入时需要等待GC回收 → 前台GC触发 → 延迟飙升 典型延迟变化: ┌──────────┬────────────────┐ │ 场景 │ 写入延迟 │ ├──────────┼────────────────┤ │ 全新状态 │ 20-50 μs │ │ 轻度使用 │ 50-100 μs │ │ 重度使用 │ 200-500 μs │ │ 空间耗尽 │ 1000 μs │ ← 前台GC 磨损均衡同时进行 └──────────┴────────────────┘4.3 后台GC的触发时机聪明的固件会在SSD空闲时偷偷执行GC触发条件典型: ① 主机无 I/O 请求超过阈值如 50ms 空闲 ② Free Block 数量低于高水位线如 20% 总块数 ③ SSD 温度低于阈值避免高温下GC加剧发热 ④ 电池/超级电容供电充足防止GC中途断电丢数据 退出条件: ① 收到新的主机 I/O 请求 → 立即暂停GC优先服务主机 ② Free Block 恢复至高水位线以上 ③ 温度超过安全阈值时间轴示意: 主机I/O: ████░░░░░░░░████░░░░████████░░░░░░ 后台GC: ░░░░▓▓▓▓▓▓▓░░░░░▓▓░░░░░░░░▓▓▓▓▓▓ █ 主机写入/读取 ▓ 后台GC ░ 空闲关键洞察好SSD和差SSD的差距很大程度上体现在后台GC的调度策略上。高端固件能让用户几乎感受不到GC的存在而低端固件则频繁触发前台GC导致卡顿。五、TRIM命令GC的效率倍增器5.1 没有TRIM时的问题当用户在操作系统中删除文件时传统流程无TRIM: 用户删除文件 ↓ 操作系统: 在文件系统中标记此文件已删除 ↓ 但是SSD完全不知道哪些数据已经不需要了 ↓ 结果: GC时SSD必须把所有看起来有效的页都当作有效数据搬移 ↓ 大量本可以丢弃的数据被反复搬移 → 写放大严重 ↑↑↑5.2 TRIM的工作原理TRIM在NVMe中称为Dataset Management的Deallocate是操作系统主动告诉SSD这些数据不需要了的机制带TRIM的流程: 用户删除文件 ↓ 操作系统: 标记文件已删除 向SSD发送TRIM命令 ↓ SSD固件: 将对应的逻辑页标记为 Invalid ↓ GC时: 这些页直接被跳过不需要搬移 ↓ 擦除效率大幅提升写放大显著降低 ✅5.3 TRIM的实际效果┌────────────────────────────┬────────────────────┐ │ 场景 │ GC 写放大因子 │ ├────────────────────────────┼────────────────────┤ │ 无 TRIM空间紧张 │ WAF ≈ 5-10x │ │ 无 TRIM空间充裕 │ WAF ≈ 2-4x │ │ 有 TRIM稳态运行 │ WAF ≈ 1.1-1.5x │ │ 有 TRIM Over-Provisioning│ WAF ≈ 1.0-1.2x │ └────────────────────────────┴────────────────────┘5.4 确保TRIM生效的配置Linux系统# 检查SSD是否支持TRIMlsblk--discard/dev/nvme0n1# 检查fstrim定时器是否启用systemctl status fstrim.timer# 手动执行TRIM建议定期运行sudofstrim-v/# 挂载时启用discard实时TRIM但有性能开销# /etc/fstab 中添加 discard 选项UUIDxxx / ext4 defaults,discard01# 更好的方案使用systemd定时器定期批量TRIM# 无需mount选项discard减少实时开销systemctlenablefstrim.timer systemctl start fstrim.timerWindows系统# 检查TRIM是否启用默认开启fsutil behavior query DisableDeleteNotify# 如果返回 0表示TRIM已启用# 如果返回 1手动启用fsutil behaviorsetDisableDeleteNotify 0# 手动优化等同于TRIMOptimize-Volume-DriveLetter C-ReTrim-Verbose⚠️重要提醒RAID阵列中的TRIM支持取决于控制器和驱动。Intel RST、Windows Storage Spaces通常支持但某些硬件RAID卡可能不透传TRIM命令需要特别确认。六、GC与磨损均衡、FTL的协作关系GC不是孤立运行的它和磨损均衡WL、FTL闪存转换层紧密协作┌──────────────────────────────────────────────────┐ │ FTL 核心模块 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ 地址映射 │ │ 磨损均衡 │ │ 垃圾回收 GC │ │ │ │ (L2P) │←→│ (WL) │←→│ │ │ │ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │ │ │ │ │ │ │ └──────────────┼───────────────┘ │ │ ↓ │ │ ┌──────────────┐ │ │ │ 块管理器 │ │ │ │ (Block Mgr) │ │ │ └──────┬───────┘ │ │ ↓ │ │ ┌──────────────┐ │ │ │ NAND 驱动 │ │ │ │ (NAND Flash) │ │ │ └──────────────┘ │ └──────────────────────────────────────────────────┘三者协作时的典型冲突┌──────────────────────────────────────────────────┐ │ 资源竞争场景 │ ├──────────────────────────────────────────────────┤ │ │ │ GC 需要: 空闲块来搬移数据 NAND带宽来擦除 │ │ WL 需要: 空闲块来搬移数据 NAND带宽来写入 │ │ 主机写入: 空闲块来接受新数据 NAND带宽来编程 │ │ │ │ → 三者共享同一块NAND带宽和空间都有限 │ │ → 固件必须在三者之间做出调度权衡 │ │ → 这就是为什么固件开发是SSD最核心的技术壁垒 │ └──────────────────────────────────────────────────┘七、当日知识点小结知识点关键要点GC存在的根本原因NAND写前必须擦除且擦除单位是Block非PageGC四步流程选择Victim Block → 搬移Valid页 → 擦除Block → 归还Free PoolVictim Block策略Greedy效率优先、FIFO简单、Cost-Benefit最优但复杂前台GC vs 后台GC前台触发性能灾难后台执行用户无感知是高端SSD的标志TRIM的作用操作系统主动告知SSD哪些数据已无效大幅降低GC搬移量TRIM配置Linux用fstrim.timerWindows默认启用RAID需确认透传GC与WL/FTL协作三者共享NAND资源调度权衡是固件核心壁垒SSD变慢的原因Free Block不足→前台GC频繁触发→写入延迟飙升 思考题一块SSD有1000个Block每个Block 256个Page。当SSD进入稳态Steady State时假设平均每个Block有60%的页是无效的。如果使用Greedy策略GC一次平均需要搬移多少个Valid页如果改用随机选择策略GC一次平均需要搬移多少个页两者的GC效率差距有多大在Linux系统中为什么社区更推荐用fstrim.timer定期批量TRIM而不是在/etc/fstab中启用discard挂载选项从性能、I/O模式和SSD寿命的角度分析。假设一块消费级SSD的OPOver-Provisioning比例从默认的7%提升到28%类似企业级分析这对GC频率、写放大、可用容量和SSD寿命分别有什么影响。是否存在最佳OP的平衡点️ 推荐标签SSD固态硬盘垃圾回收Garbage CollectionNAND闪存TRIM写放大FTL存储技术固件算法作者持续更新中关注获取每日SSD硬核知识