Android/Linux的FEC浅析
一、什么是FEC以及FEC的作用
FEC 是 Forward Error Correction 的缩写,中文译为前向纠错。
它是一种数据保护机制,通过在原始数据中添加冗余的校验信息,使得接收方在读取数据时,不仅能够检测到错误,还能在不重新读取的情况下,自动纠正一定数量和范围内的错误。
在 Linux 和 Android 的上下文中,FEC 主要被用作一种存储层的容错技术,主要用于系统分区的校验的场景
dm-verity 是 Linux 内核的一个设备映射器目标,主要用于只读分区(如 Android 的 system、vendor 分区)的完整性验证,防止设备启动时分区被篡改。通过dm-verity,系统分区被哈希树保护。在启动时,内核会逐块验证分区的哈希值,如果发现某个块的哈希值与预期不符,则说明该块数据被篡改或损坏。
标准的 dm-verity 在检测到错误时,会简单地返回 I/O 错误。对于 system 分区,这通常会导致设备无法启动,以确保安全。
为了增加鲁棒性,Android 在构建 dm-verity 镜像时,可以可选地加入 FEC 编码。它会为数据块生成 Reed-Solomon 纠错码。
-
当 dm-verity 检测到某个数据块损坏时,它不会立即失败。
-
它会先尝试使用 FEC 的冗余信息来重建和修复这个损坏的数据块。
-
只有在 FEC 也无法纠正该错误时,dm-verity 才会最终报告 I/O 错误。
二、FEC是如何工作的
FEC(前向纠错)修复错误的过程像解方程,它利用额外的线索来还原丢失或损坏的数据。其核心是一种叫做 里德-所罗门码(Reed-Solomon)的数学算法
工作过程类比
假设我们想发送一组数字:[3, 5, 7]。
-
编码(添加冗余)
使用一个简单的规则来生成两个额外的校验数字。假设规则是:
P(x) = 3 + 5x + 7x²-
当 x=4 时,
P(4) = 3 + 5*4 + 7*16 = 135 -
当 x=5 时,
P(5) = 3 + 5*5 + 7*25 = 203
现在,存储或发送的完整数据包不再是
[3, 5, 7],而是[3, 5, 7, 135, 203]。这里的135和203就是冗余的 FEC 校验数据。 -
-
传输(可能出错)
假设在读取数据时发生了错误。我得到的数据变成了:[3, *, 7, 135, 203]。第二个数字5丢失或损坏(标记为 *)。 -
解码(修复错误)
虽然丢失了一个原始数据,但我们还有两个完整的校验数据和另外两个原始数据。这变成了一个数学问题:我们有一个方程
P(x) = a + bx + cx²,其中:-
a=3,b=?,c=7 -
当
x=4时,P(4)=135 -
当
x=5时,P(5)=203
现在有了一个方程组:
-
3 + b*4 + 7*16 = 135 -
3 + b*5 + 7*25 = 203
在这个简单的例子里,我们甚至只需要其中一个方程就能解出未知数
b:-
从第一个方程:
3 + 4b + 112 = 135->4b + 115 = 135->4b = 20->b = 5。
这样就成功修复了损坏的数据! 丢失的数字
5被准确地计算出来了。 -
在技术实现中,这个过程要复杂得多,但原理相通:
-
数据分块与编码:
-
系统将待保护的数据(例如一个 4KB 的文件系统元数据块)分割成多个符号
-
FEC 算法(如 Reed-Solomon)会取一定数量的数据符号(例如
k个),然后通过复杂的数学运算(在伽罗瓦域上进行),生成m个额外的校验符号。 -
最终,
k个数据符号和m个校验符号共同组成一个 FEC 码字。 -
在 Android 的 dm-verity 中,这些校验数据会存储在专门的空间里;
-
-
错误检测:
-
当需要读取数据时, dm-verity首先会检查数据的完整性。
-
如果校验失败,则表明该数据块中存在错误。
-
-
错误纠正:
-
一旦检测到错误,系统就会触发 FEC 纠错流程。
-
它会从存储介质中读取整个 FEC 码字(包括剩余的正确数据符号和所有校验符号)。
-
FEC 解码器开始工作。它会尝试解决一个数学问题:“给定这些部分已知的数据和校验符号,最有可能的原始数据是什么?”
-
解码器能定位错误发生的位置(
?在哪里)并确定其正确的值(?应该是什么)。
-
-
返回结果:
-
成功:如果错误数量在 FEC 的纠错能力范围内,解码器将计算出正确的数据,并将其返回给上层应用。整个过程对应用是透明的,它只会感觉到一次稍慢的读取。
-
失败:如果错误数量超过了 FEC 的设计纠错能力(例如,校验符号太少,无法解出方程),则解码器会宣告失败,系统最终会返回一个 I/O 错误给上层。
-
