You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

106 lines
11 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 50 | 数据完整性(下):如何还原犯罪现场?
讲完校验码之后你现在应该知道无论是奇偶校验码还是CRC这样的循环校验码都只能告诉我们一个事情就是你的数据出错了。所以校验码也被称为检错码Error Detecting Code
不管是校验码,还是检错码,在硬件出错的时候,只能告诉你“我错了”。但是,下一个问题,“错哪儿了”,它是回答不了的。这就导致,我们的处理方式只有一种,那就是当成“哪儿都错了”。如果是下载一个文件,发现校验码不匹配,我们只能重新去下载;如果是程序计算后放到内存里面的数据,我们只能再重新算一遍。
这样的效率实在是太低了,所以我们需要有一个办法,不仅告诉我们“我错了”,还能告诉我们“错哪儿了”。于是,计算机科学家们就发明了**纠错码**。纠错码需要更多的冗余信息,通过这些冗余信息,我们不仅可以知道哪里的数据错了,还能直接把数据给改对。这个是不是听起来很神奇?接下来就让我们一起来看一看。
## 海明码:我们需要多少信息冗余?
最知名的纠错码就是海明码。海明码Hamming Code是以他的发明人Richard Hamming理查德·海明的名字命名的。这个编码方式早在上世纪四十年代就被发明出来了。而直到今天我们上一讲所说到的ECC内存也还在使用海明码来纠错。
最基础的海明码叫**7-4海明码**。这里的“7”指的是实际有效的数据一共是7位Bit。而这里的“4”指的是我们额外存储了4位数据用来纠错。
首先你要明白一点纠错码的纠错能力是有限的。不是说不管错了多少位我们都能给纠正过来。不然我们就不需要那7个数据位只需要那4个校验位就好了这意味着我们可以不用数据位就能传输信息了。这就不科学了。事实上在7-4海明码里面我们只能纠正某1位的错误。这是怎么做到的呢我们一起来看看。
4位的校验码一共可以表示 2^4 = 16 个不同的数。根据数据位计算出来的校验值,一定是确定的。所以,如果数据位出错了,计算出来的校验码,一定和确定的那个校验码不同。那可能的值,就是在 2^4 - 1 = 15 那剩下的15个可能的校验值当中。
15个可能的校验值其实可以对应15个可能出错的位。这个时候你可能就会问了既然我们的数据位只有7位那为什么我们要用4位的校验码呢用3位不就够了吗2^3 - 1 = 7正好能够对上7个不同的数据位啊
你别忘了单比特翻转的错误不仅可能出现在数据位也有可能出现在校验位。校验位本身也是可能出错的。所以7位数据位和3位校验位如果只有单比特出错可能出错的位数就是10位2^3 - 1 = 7 种情况是不能帮我们找到具体是哪一位出错的。
事实上如果我们的数据位有K位校验位有N位。那么我们需要满足下面这个不等式才能确保我们能够对单比特翻转的数据纠错。这个不等式就是
K + N + 1 <= 2^N
在有7位数据位也就是K=7的情况下N的最小值就是4。4位校验位其实最多可以支持到11位数据位。我在下面列了一个简单的数据位数和校验位数的对照表你可以自己算一算理解一下上面的公式。
![](https://static001.geekbang.org/resource/image/ec/1d/ec8b6bff509e1abb7453caa36a4a711d.jpeg)
## 海明码的纠错原理
现在你应该搞清楚了,在数据位数确定的情况下,怎么计算需要的校验位。那接下来,我们就一起看看海明码的编码方式是怎么样的。
为了算起来简单一点,我们少用一些位数,来算一个**4-3海明码**也就是4位数据位3位校验位。我们把4位数据位分别记作d1、d2、d3、d4。这里的d取的是数据位data bits的首字母。我们把3位校验位分别记作p1、p2、p3。这里的p取的是校验位parity bits的首字母。
从4位的数据位里面我们拿走1位然后计算出一个对应的校验位。这个校验位的计算用之前讲过的奇偶校验就可以了。比如我们用d1、d2、d4来计算出一个校验位p1用d1、d3、d4计算出一个校验位p2用d2、d3、d4计算出一个校验位p3。就像下面这个对应的表格一样
![](https://static001.geekbang.org/resource/image/6d/bc/6d7cf44bb41df6361e82dcd4979dc4bc.jpeg)
这个时候你去想一想如果d1这一位的数据出错了会发生什么情况我们会发现p1和p2和校验的计算结果不一样。d2出错了是因为p1和p3的校验的计算结果不一样d3出错了则是因为p2和p3如果d4出错了则是p1、p2、p3都不一样。你会发现当数据码出错的时候至少会有2位校验码的计算是不一致的。
那我们倒过来如果是p1的校验码出错了会发生什么情况呢这个时候只有p1的校验结果出错。p2和p3的出错的结果也是一样的只有一个校验码的计算是不一致的。
所以校验码不一致,一共有 2^3-1=7种情况正好对应了7个不同的位数的错误。我把这个对应表格也放在下面了你可以理解一下。
![](https://static001.geekbang.org/resource/image/3e/21/3edee00788294bb96cde11dace2a7721.jpeg)
可以看到,海明码这样的纠错过程,有点儿像电影里面看到的推理探案的过程。通过出错现场的额外信息,一步一步条分缕析地找出,到底是哪一位的数据出错,还原出错时候的“犯罪现场”。
看到这里,相信你一方面会觉得海明码特别神奇,但是同时也会冒出一个新的疑问,我们怎么才能用一套程序或者规则来生成海明码呢?其实这个步骤并不复杂,接下来我们就一起来看一下。
首先我们先确定编码后要传输的数据是多少位。比如说我们这里的7-4海明码就是一共11位。
然后我们给这11位数据从左到右进行编号并且也把它们的二进制表示写出来。
接着我们先把这11个数据中的二进制的整数次幂找出来。在这个7-4海明码里面就是1、2、4、8。这些数就是我们的校验码位我们把他们记录做p1p4。如果从二进制的角度看它们是这11个数当中唯四的在4个比特里面只有一个比特是1的数值。
那么剩下的7个数就是我们d1-d7的数据码位了。
然后对于我们的校验码位我们还是用奇偶校验码。但是每一个校验码位不是用所有的7位数据来计算校验码。而是p1用3、5、7、9、11来计算。也就是在二进制表示下从右往左数的第一位比特是1的情况下用p1作为校验码。
剩下的p2我们用3、6、10、11来计算校验码也就是在二进制表示下从右往左数的第二位比特是1的情况下用p2。那么p3自然是从右往左数第三位比特是1的情况下的数字校验码。而p4则是第四位比特是1的情况下的校验码。
![](https://static001.geekbang.org/resource/image/a7/9d/a7d5e958f9d46938e494710e090f469d.jpeg)
这个时候,你会发现,任何一个数据码出错了,就至少会有对应的两个或者三个校验码对不上,这样我们就能反过来找到是哪一个数据码出错了。如果校验码出错了,那么只有校验码这一位对不上,我们就知道是这个校验码出错了。
上面这个方法,我们可以用一段确定的程序表示出来,意味着无论是几位的海明码,我们都不再需要人工去精巧地设计编码方案了。
## 海明距离:形象理解海明码的作用
其实,我们还可以换一个角度来理解海明码的作用。对于两个二进制表示的数据,他们之间有差异的位数,我们称之为海明距离。比如 1001 和 0001 的海明距离是1因为他们只有最左侧的第一位是不同的。而1001 和 0000 的海明距离是2因为他们最左侧和最右侧有两位是不同的。
![](https://static001.geekbang.org/resource/image/fb/78/fb388f965a7a7631925a32cc4610ff78.jpeg)
于是你很容易可以想到所谓的进行一位纠错也就是所有和我们要传输的数据的海明距离为1的数都能被纠正回来。
而任何两个实际我们想要传输的数据海明距离都至少要是3。你可能会问了为什么不能是2呢因为如果是2的话那么就会有一个出错的数到两个正确的数据的海明距离都是1。当我们看到这个出错的数的时候我们就不知道究竟应该纠正到那一个数了。
在引入了海明距离之后,我们就可以更形象地理解纠错码了。在没有纠错功能的情况下,我们看到的数据就好像是空间里面的一个一个点。这个时候,我们可以让数据之间的距离很紧凑,但是如果这些点的坐标稍稍有错,我们就可能搞错是哪一个点。
在有了1位纠错功能之后就好像我们把一个点变成了以这个点为中心半径为1的球。只要坐标在这个球的范围之内我们都知道实际要的数据就是球心的坐标。而各个数据球不能距离太近不同的数据球之间要有3个单位的距离。
![](https://static001.geekbang.org/resource/image/d6/34/d65bdde974ee99b6187eac90e4b5a234.jpeg)
## 总结延伸
好了纠错码的内容到这里就讲完了。你可不要小看这个看起来简单的海明码。虽然它在上世纪40年代早早地就诞生了不过直到今天的ECC内存里面我们还在使用这个技术方案。而海明也因为海明码获得了图灵奖。
通过在数据中添加多个冗余的校验码位海明码不仅能够检测到数据中的错误还能够在只有单个位的数据出错的时候把错误的一位纠正过来。在理解和计算海明码的过程中有一个很重要的点就是不仅原来的数据位可能出错。我们新添加的校验位一样可能会出现单比特翻转的错误。这也是为什么7位数据位用3位校验码位是不够的而需要4位校验码位。
实际的海明码编码的过程也并不复杂,我们通过用不同过的校验位,去匹配多个不同的数据组,确保任何一个数据位出错,都会产生一个多个校验码位出错的唯一组合。这样,在出错的时候,我们就可以反过来找到出错的数据位,并纠正过来。当只有一个校验码位出错的时候,我们就知道实际出错的是校验码位了。
## 推荐阅读
这一讲的推荐阅读,还是让我们回到教科书。我推荐你去读一读《计算机组成与设计:软件/硬件接口》的5.5章节,关于可信存储器的部分。
另外如果你想在纠错码上进一步深入你可以去了解一下纠删码也就是Erasure Code。最好的学习入口当然还是[Wikipedia](https://en.wikipedia.org/wiki/Erasure_code)。
## 课后思考
7明码除了可以进行单个位的纠错之外。还能做到可以检测Detection到两个位的出错。也就是说虽然我们不知道是哪两个比特错了但是我们还是知道数据是错了的。为什么能够做到这一点呢
你可以好好思考一下,然后在留言区写下你的答案。如果有收获,你也可以把这篇文章分享给你的朋友。