一个比较轻松的CRC错误校验算法指南(全文翻译)

以下是我翻译的一篇关于CRC算法原理的英语文档,如果读者有什么疑问,欢迎随时在留言区咨询~~

如果有需要原始文档的,可以在留言区留下你的邮箱,我会在第一时间发给你。


一个比较轻松的CRC错误校验算法指南



[文档版本:3.00][最后更新1996.9.24]


第一章:前言
1.1关于作者和版权
1.2摘要
  这个文档解释并且在精确细节上实现了CRC(Cyclic Redundancy Codes)校验.至少对我
我来说,我感觉很多关于CRC校验的文章在对于CRC的具体实现上的表述都是有些模糊的。
这个文档做了一个尝试,就是提供一个简单没有废话的关于CRC的解析,并且绝对关注了有关
于高速CRC实现的所有细节。除此之外,这篇文档提出了一个名为Rocksoft^tm CRC
算法模型的参数化CRC模型。这个算法模型可以通过设置参数来使其运行地像很多CRC算法
一样,并且可以作为一个描述特殊算法的不错参考。文档中也提供了一个用C语言来实
现的低速CRC算法模型。最后有一个部分提供了两种高速CRC实现的方法,并且提供了一个
CRC查表程序。


第二章:介绍 错误校验
  错误校验技术的目的是使得数据传输的接收者能够在一个有噪音的传输通道中来判断它
所接收的一个数据包是否被损坏。为此,数据发送方就会构建一个数值(也就是校验值)
来作为数据包的一个部分,并且这个数值是附加在数据包后面的。这样数据接收方在接收
到数据包后使用同样的功能计算校验值并且通过和附加在包后面的校验值进行比较来判断
接收到的数据包是否正确。举例来说,如果我们选择的校验值方案是简单的计算数据包中
的所有字节的和然后对256取模的值,这样的话可能会出现下面的情况。所有的数字都是十
进制。
数据包 :6 23 4
数据包含有校验值:6 23 4 33
经过传输的数据包:6 27 4 33
  在上面的例子中,数据包的第二个字节由于在传输通道中被损坏而由23变为了27。然而,
数据接受者可以通过比较已经收到的校验值(33)和通过计算得到的校验值37(6+27+4)
来进行对错判断。如果校验值本身被损坏,那么一个本来正确的数据包可能会被视为一个被
损坏的数据包。然而这时一个安全方面的失误。一种危险的失误情况是当数据包或者校验值
被某种方式损坏后其在传输中任然能保持数据的一致性。不幸的是这种可能性是完全不能被
避免的,最好的解决方法就是通过增加校验值的数据位来减少这种可能性。(比如讲校验
值从一个字节变为两个字节)
  目前出现的其他错误校验技术涉及到执行复杂的注入冗余信息的数据传输。本文旨在
解决CRC算法,这是一种在保证数据完整性的情况下添加一个校验值在尾部的错误校验算法。
    <原始完整数据><校验值>


第三章:对于复杂性的需求
  在上一章的校验值例子中,我们认识到了如何通过计算简单求和并对256取模得到的校验
值来检测一个被损坏了的数据:
数据包 :6 23 4
数据包含有校验值:6 23 4 33
经过传输的数据包:6 27 4 33
  这个算法的问题所在就是它太简单了,如果一个值被随机的损坏了,那么就有1/256的可
能这个错误不会被检测出来。举例来说:
数据包 :6 23 4
数据包含有校验值:6 23 4 33
经过传输的数据包:8 20 5 33
  为了加强这个校验值,我们可以通过将8位寄存器修改为16位的寄存器。(同时,对于算
术和就需要对65536取模而不是对256取模)。这样一来很明显地失败率就从1/256减少到
了1/65536。这个基本上来说是个不错的主意,它在这种情况下失败的原因是因为这个被使
用的公式随机的不够充分。不管校验值的数据位有多少,数据包中的每个字节都会对其有影
响。举例来说,在上文的第二个例子中,进行算术和计算的寄存器可能有1M字节宽,但是
错误的数据任然可能无法被检测出来。这个问题只能通过用一个更复杂的公式来替换简单算
术求和的公式来解决,这样每个被计算的字节都会对最后的校验值产生影响。
  因此,我们可能看到至少有两个方面影响着我们来组织一个健壮的校验值功能:
1校验值数据位宽度
  一个校验值的数据位有足够的宽度,那么其就会提供一个足够低的失败率(举例:32
位校验值得失败率为1/2^32)
2公式
  一个好的公式会使得每个被计算的字节都会修改校验值中的某些数据位。


注意:术语“校验值”是被用来大概地描述早先的算术和公式,但是现在被一个更为一般
并且包括更复杂公式的算法所替代,比如CRC。CRC算法很好的满足了前面的第二个特征,
并且它还可以被设置从而可以在不同的校验值宽度下进行操作。


第四章 CRC算法背后的基本思想
  我们如何继续我们寻找比算术和复杂很多的功能,各种各样的想法向我们袭来。我们
可以通过圆周率的位数来组织校验码,或者通过哈希计算来将所有参与运算的字节融合
在一起。我们甚至可以在线保存一个巨大的电话簿,并且利用每一个被计算的字节以及
寄存器字节来确定一个新的可以成为下一个寄存器值得电话号码。这种可能性是无限的。
  然而,我们不需要想那么多;第二种算术方法就足够了。当加法没有足够的健壮性来
组织一个有效的校验值。那么就该除法出场了,只要除法差不多和校验值寄存器的数据位
宽度差不多就行。
  CRC算法的基本思想是简单地将一个数据包看做一大串连续的二进制位,然后用另外一
个固定的二进制数据作为除数来和它进行运算,最后将运算进行到最后留下的结果作为校
验值。在接收到数据包后,接收者可以进行相同的除法运算进而来比较运算结果和收到的
校验值。
  举例来说:和之前的例子一样假设数据包包含两个字节的数据(6,23)。这两个数字
可以被转换成十六进制数据0617并且可以被看做二进制数据0000-0110-0001-0111。假设
我们使用的校验值长度为一个字节并且除数为1001,那么校验值就是0000-0110-0001-0111
和1001做除法运算后得到的结果。在这种情况下,很明显这种计算也可以使用32位的寄存
器进行,在一般情况下这是混乱的。所以我们会进行你们在学校学习的除法运算,但是这
次,是使用的二进制除法:
          ...0000010101101 = 00AD = 173 = QUOTIENT
         ____-___-___-___-
9= 1001 ) 0000011000010111 = 0617 = 1559 = DIVIDEND
DIVISOR   0000.,,....,.,,,
          ----.,,....,.,,,
           0000,,....,.,,,
           0000,,....,.,,,
           ----,,....,.,,,
            0001,....,.,,,
            0000,....,.,,,
            ----,....,.,,,
             0011....,.,,,
             0000....,.,,,
             ----....,.,,,
              0110...,.,,,
              0000...,.,,,
              ----...,.,,,
               1100..,.,,,
               1001..,.,,,
               ====..,.,,,
                0110.,.,,,
                0000.,.,,,
                ----.,.,,,
                 1100,.,,,
                 1001,.,,,
                 ====,.,,,
                  0111.,,,
                  0000.,,,
                  ----.,,,
                   1110,,,
                   1001,,,
                   ====,,,
                    1011,,
                    1001,,
                    ====,,
                     0101,
                     0000,
                     ----
                      1011
                      1001
                      ====
                      0010 = 02 = 2 = REMAINDER
用十进制来表达就是1559除以9后得到173,余数为2。
  尽管输入的数据包的每一位对于商的影响不是那么重要,一个4位的余数在计算的过程
中也是多次变化,如果在数据包(也就是被除数)中添加了更多的字节,这个余数的值
会从根本上变化的很快。这就是为什么除法运算可以使用而加法不行的原因。
  为了避免你的疑惑,在使用这个4位的校验值时传输的数据包是这样的:06172(十六进制)
其中0617是数据而2是校验值。接收者会让0617除以9来判断余数是否为2.


第五章 多项式算法
  在上一个部分中描述的除法方案和一种叫做CRC校验和方案非常相似,但是事实上CRC方
案有点难以理解,所以我们需要通过将其放入到一些数字系统中做研究进而来理解他们。
  在处理与CRC算法相关的事情的时候你总会听到一个词“多项式”。一个给定的CRC算法
会被看做使用了一个特定的多项式,一般而言一个CRC算法会被视作通过一个多项式公式
来进行操作。那这到底是什么意思捏?
  前面部分描述的被除数,除数,商以及余数都不是被看做正整数,他们都是被视为一个
有二进制系数的多项式。这个是将每个数字看做一个二进制数的串,而这些串就是多项式
的系数。举例来说:一个普通的十进制数23就是十六进制的17或者二进制的10111,然后和
其相关的多项式就是:
    1*x^4 + 0*x^3 + 1*x^2 + 1*x^1 + 1*x^0
或者简单点:
    x^4 + x^2 + x^1 + x^0


使用这种技术,数据包和除数就可以使用多项式来表示,进而我们就可以做和之前一样的
计算,除非这里充满了X。举例来说,假设我们想将1101和1011相乘。我们可以通过将多
项式相乘来得到结果。
(x^3 + x^2 + x^0)(x^3 + x^1 + x^0)
= (x^6 + x^4 + x^3
+ x^5 + x^3 + x^2
+ x^3 + x^1 + x^0) = x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + x^0
  在这种情况下,为了得到正确答案,我必须假装x就是2,并且从3*x^3传播二进制进位:
进而得到
x^7 + x^3 + x^2 + x^1 + x^0
  这就像除了基本运算之外的普通运算被抽象地带进了一个显式计算而不是隐式计算。除
此外还有什么意义呢?
  意义就在于如果我们假装我们不知道x的值是多少,我们就不能进行进位运算。我们不知
道3*x^3和x^4+x^3是相等的,因为我们不知道x的值是2。在这个真多项式算术中所有系数
之间的关系都是未知的。所以每一项的系数都有效的成为了强类型。x^2的系数和x^3的系数
是有效的不同类型。
  当系数和每个权位完美分开后,数学家想出了所有不同种类的多项式算术通过简单的改变
系数工作的方式。在这些方案中,有一个是特别相关的,就是多项式算术就是系数对2进行
模运算并且没有进位。所有的系数必须不是0就是1并且计算中不考虑进位。这个被称为“
多项式算术模2运算”。所以回到之前的例子:
(x^3 + x^2 + x^0)(x^3 + x^1 + x^0)
= (x^6 + x^4 + x^3
+ x^5 + x^3 + x^2
+ x^3 + x^1 + x^0)
= x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + x^0
在其他计算中,3*x^3通过进位机制以及x=2来进行传播。在“多项式模2运算”中,我们不知
道x是什么,没有进位,并且所有的系数都必须进行模2运算,所以,结果成为了:
= x^6 + x^5 + x^4 + x^3 + x^2 + x^1 + x^0
“读者需要注意多项式计算和多精度计算之前的相似性(4.3.1),当基数b替换了x。这个
重要的不同是多项式计算中的x^k的系数u_k就和它的相邻位x^(k-1)以及”x^k没有了关系。
这样以来从一个位置向另外一个位置进位的想法就不可能了。事实上进行模运算的多项式计
算基本上和通过基数b进行多精度计算是一样的,前提是没有进位机制。
  因此多项式模2运算就是没有进位的二进制模2运算。当多项式在更多的CRC错误校验算法
分析工具中提供了有用的数学机制,为了解释他们没有提供额外的深入讨论和东西而且丢弃
了这个文档中赞成直接操纵同构的计算系统:没有进位的二进制计算。


第六章 没有进位的二进制计算
  省略了多项式,我们可以集中精力在真正的计算上了,CRC计算期间的所有计算其实就是
没有进位的二进制计算。通常这个被称作多项式计算,但是如同我在文档其他地方声明的
多项式空闲区一样,我们将必须称其为CRC计算。因为这个算法是CRC计算的关键,我们最好
习惯它,我们开始吧:
  在CRC算法中将两个数字相加和普通的二进制加法唯一的不同是没有进位。这意味着每个
相应的位都决定了相应的输出位,这种影响不受其他位的位置影响。举个例子:
 10011011
+11001010
 --------
 01010001
 --------
对于每个数据位只有四种情况:
0+0=0
0+1=1
1+0=1
1+1=0(没有进位)


减法运算也是一样的:
 10011011
-11001010
 --------
 01010001
 --------
0-0=0
0-1=1 (借位)
1-0=1
1-1=0


  事实上,CRC算法中的加法和减法都是和异或操作一样的,异或操作是它本身的逆。
这个有效的减少了第一级的操作数目斌给前单个操作就是它本身的逆。这是这个算法非常
方便的属性。


  通过给加法和减法修改规则,这个算法丢弃了最高位大小的概念。当可以清楚的知道
1010是比10大的,但是1010就不再被看做是比1001大了。为了证明这个,需要注意你可以
从1010中得到1001,这个过程既可以是加法也可以是减法:
1001 = 1010 + 0011
1001 = 1010 - 0011
这里没有先后顺序的概念。


  已经定义了加法,我们就可以进行乘法和除法了。乘法是非常简单的,就是第一个数字
的和,按照第二个数字移动。
   1101
x  1011
   ----
   1101
 1 101.
 0000..
1101...
-------
1111111 注意:这个和用到了CRC加法
-------


  除法会有点麻烦因为我们需要知道一个数何时变成另外一个数。为了明白这个,我们引用
了之前关于大小的定义:X比Y大与或者等于Y如果X的最高位大于或者等于Y的最高位。这里
是完整的除法计算过程:
           1100001010
       _______________
10011 )11010110110000
       10011,,.,,....
       -----,,.,,....
        10011,.,,....
        10011,.,,....
        -----,.,,....
         00001.,,....
         00000.,,....
         -----.,,....
          00010,,....
          00000,,....
          -----,,....
           00101,....
           00000,....
           -----,....
            01011....
            00000....
            -----....
             10110...
             10011...
             -----...
              01010..
              00000..
              -----..
               10100.
               10011.
               -----.
               01110
               00000
               -----
                1110 = 余数


  这个就是这样,然而在我进行进一步的运算前,我们很有必要来多进行这种运算训练从
而来适应它。
  我们已经练习过加减运算,并且发现它们其实是一样的。虽然如此,我们需要注意在这个
计算方法中A+0=A并且A-0=A,这个明显的属性在后面会非常有用。
  在处理CRC的乘法和除法时,对整除和不可整除的概念有个认识是非常重要的。
  如果一个数字B可以被数字A整除那么意味着在CRC算法中我们可以通过对一系列经过变换
的B进行异或操作从而得到A。举例来说,如果A是0111010110而B是11,那么我可以像下面
这样由A得到B:
  0111010110
= .......11.
+ ....11....
+ ...11.....
  .11.......
  
  然而,如果A是0111010111,那么就不可能通过一系列B的变换的异或来得到它(你能看出
为什么? 看后面的)所以这就是说明A在CRC算法中不能被B整除。
  这样我们可以看出CRC算法主要就是一个对一个特定值的很多移位变换值进行异或运算的
计算方法。


第七章 一个完整的例子
  在定义了CRC算法之后,我们现在可以设计一个CRC计算方法,其实它的全部内容就是简单
的除法。这个部分完成了具体的细节并且给了一个例子。
  为了演示一个CRC计算,我们需要选择一个除数。在数学领域中这个除数被称为生成多项
式或者就是多项式,并且它是CRC算法的关键参数。一般来说将其称为除数会更加容易理解,
但是因为多项式的这个说法在这个领域是如此的根深蒂固,所以如果不提到它可能会导致
疑惑出现。作为妥协,我们在提到多项式时都会称其为poly,就将这种数字看做一种鹦鹉
吧。“你好,poly”。
  你可以给一个CRC算法选择任何一个多项式。但是,有些多项式天生比其他的要好,所以
选择一个经过了测试的多项式会是很明智的。后面的部分会重点关注这个事情。
  多项式的宽度(最高位的位置)在整个计算过程中是非常重要的。典型的说,选择16位
或者32位的宽度是为了简化在现代计算机中的计算。多项式的宽度就是最高位所在的位置。
举例来说,10011的宽度是4而不是5。举这个例子的目的就是因为我们要选择10011作为多项
式。
  选择好了多项式后,我们就可以进行计算了。这个就是数据和多项式之间的简单除法运算
(在CRC算法中)。唯一的不同在于在进行CRC计算之前要先将4个0(10011长度为4)附加到
原始数据后面。所以我们有了:
原始数据:1101011011
多项式  :10011
附加了4个0的数据:11010110110000


  现在我们简单地对数据和多项式进行CRC除法运算。这个和之前的除法运算一样:
            1100001010 = 商 (可有可无的数据)
        _______________
10011 ) 11010110110000 = 经过添加后的数据 (1101011011 + 0000)
=多项式 10011,,.,,....
        -----,,.,,....
         10011,.,,....
         10011,.,,....
         -----,.,,....
          00001.,,....
          00000.,,....
          -----.,,....
           00010,,....
           00000,,....
           -----,,....
            00101,....
            00000,....
            -----,....
             01011....
             00000....
             -----....
              10110...
              10011...
              -----...
               01010..
               00000..
               -----..
                10100.
                10011.
                -----.
                01110
                00000
                -----
                 1110 = 余数= 校验值
  除法运算后有一个商,其实这个商被我们所丢弃,倒是这个余数是我们所需要的,其实
这个余数就我们校验值,它结束了这个计算。
  一般来说,校验值之后会被附加到数据后面,然后含有校验值的数据在被传输出去,这
样我们的传输数据就是11010110111110
  在接收端,接收者可以做以下两件事情中的一件:
  1.将数据和校验值分开,然后在给数据附加4个0后进行CRC计算,将得到的校验值与收到
的校验值进行比较。
  2.直接对收到的数据进行校验计算,判断计算结果是否为0。


  这两个选项是等同的。然而,在下个部分中,我们会默认选择方法2,因为这种方法在
数学计算上更为简洁。
  CRC算法操作的一个总结:
  1.选择一个宽度W和一个多项式G(宽度为W)
  2.附加W个0到数据的后面,记为M'
  3.用M'对G做CRC除法运算,得到的余数就是校验值
  这就是这里所有的内容。


第八章:选择一个多项式
  多项式的选择有点黑科技,读者可以参考Tanenbaum81 130-132页,那个里面非常清楚
地讨论了这个问题。这个部分仅仅是想让那些觉得通过自己来想出自己的多项式是一件挺
简单的事情的人认识到问题的复杂性。如果你不在乎为什么一个多项式可以工作得比其他
的好而是只想找出一个高速的实施方案,那么你可以直接从本部分的尾部的表中选出一个
多项式然后去看下一部分吧。
  
  首先需要注意的是经过传输的数据是多项式的整数倍。注意
1.T的后面W位就是经过组合后的数据(原始数据添加了W个0)和多项式进行除法运算后的
余数。
2.加法和减法是一样的,所以加上其余的将值提升到下一个倍数。
现在注意如果传输的数据在传输过程中被损坏了,那么我们会收到T+E,其中E是错误向量
(+是CRC加法(即异或))。在收到了数据后,接收者使用T+E来除以G。因为T对G取模结
果为0,那么(T+E) mod G = E mod G。所以,我们选择的多项式的检测特定错误类型的容量
是被G的倍数的集合所决定的,因为任何导致E是G的倍数的损坏就不会被检测出来。我们的
任务就是找出所有G的类型,并且这些类型的倍数尽可能不和线路噪音一样。所以让我们来
检查我们预测的线路噪音。


单个位的错误
  单个位的错误意味着 E=1000...0000。我们可以确定这种错误总是可以被检测出来通过保
证G中至少有两个位被置为1。任何G的整数倍的数据都可以通过转换和加法来得到,而通过
单个位的转换和相加一个单个值来构建一个值,因为尾部的两个位会一直存在。


两个位的错误
  为了检测出100...0001000...000(有两个位置1)中的所有错误,选择一个G,这个G不能被11
101,1001,10001,100001等整除。我不清楚为什么是这样(我们没有纯数学背景),但是
Tanenbaum帮助我们明确了这样的G是确实存在的,并且引用含有1位被置为1的G作为例子
证明G不会被任何形式为1...1数据整除(...为32767个0)。


奇数位的错误
  我们可以通过选择一个偶数为的G来检测奇数位的E中的所有错误。看到这里需要注意:
  1CRC乘法就是一个简单的对一个常数和寄存器中的值进行异或操作
  2异或操作时一个简单的位翻转操作
  3如果你将一个偶数位的数据和寄存器中的值进行异或操作,那么寄存器中奇数位的1将
  不变。举例来说: E = 111,尝试把所有的三位比特通过和两个偏移之一的数做异或运
算来都填写为0(即 E = E XOR 011 和 E = E XOR 110)。这个和聚会中的玻璃酒杯是一
样的,你可以叫一个人通过不断地翻转两个酒杯从而得到翻转三个酒杯的目的。使用的比
较多的大多数CRC多项式都包含一个偶数位的1。(注意:Tanenbaum理论指出了所有的奇数
位上的错误都可以通过使得G变为11的倍数来捕获)


爆发的错误
  一个爆发的错误是这样的:E=000...000111...11110000..00。E包含了所有的0除了其
中某个部分出现了很多个1。这个可以被重新构造成为E=(10000...00)(1111111...111)
左边有z个0并且右边有n个1。为了检测出这种类型的错误,我们可以通过简单的设置G 的
最低位为1来做到。通过这样做保证了左边的部分不能成为G的因数。然后,由于G比右边的
部分宽,那么这个错误就会被检测出来。你可以去查看Tanenbaum来得到一个更为清晰的
解释。我对这个有点不懂。Tanenbaum断言爆发错误的长度比W长的概率为(0.5)^W.


  这个决定了选择多项式的正确方法。
  一些常使用的多项式:
  16位:(16,12,5,0)                             [X25 标准]
        (16,15,2,0)                             [CRC-16]
  32位:(32,26,23,22,16,12,11,10,8,7,5,4,2,1,0) [以太网]


第九章:一个简单的CRC实现
  理论部分结束了,我们现在来做实现。最开始,我们实现一个绝对简单的并没有在速度上
做文章的低速实现。然后我们会逐步的升级代码直到它变成一个我们能懂且喜欢的紧凑的
表驱动代码,并且我们中的一部分人能够理解他。


  要实现一个CRC算法我们首先就要实现CRC除法。为什么我们不能简单的就使用我们所使
用的机器上面的除法,这个原因有两个:首先是我们需要在CRC算法中做除法运算。第二个
就是除法的数据长度可能有10M字节长,当下处理器没有那么大的寄存器。所以为了实现
CRC算法,我们必须通过一个除法寄存器来填充一个数据。因此,我们必须对数据信息绝对
精确。在接下来的所有例子中数据都会被看做一个字节流(每个都是8位的),其中第7位
是最高位。组成这些字节的数据位流将会是一个以MSB为头然后知道第0位的位流,紧接着
跟上第二个字节的MSB,然后一直这样继续下去。
  当明白了这个后,我们就可以描述一个CRC的实现了。为了举例方便,假设多项式的W=4
并且多项式为10111。为了演示除法,我们需要一个4位的寄存器:
       3   2   1   0   Bits
       +---+---+---+---+
Pop!<--|   |   |   |   | <----- 加强了的数据
       +---+---+---+---+
       1   0   1   1   1 = 多项式
(余数:加强了的数据是在原始数据尾部添加了W个0的数据)


为了演示除法需要做一下工作:
  将寄存器填全部填写为0
  通过在尾部添加W个0来加强原始数据
  While (more message bits)
    Begin
    将寄存器向左移动一位,然后读取加强数据的下一位到寄存器的第0个位置。
    If (a 1 bit popped out of the register during step 3)
      Register = Register XOR Poly.
    End
    最后寄存器中包含的就是余数。


(注意:在实际应用中,if语句可以在检测最高位数据前被用来检测寄存器最高位数据)


  我们可以将这个算法称为“SIMPLE”。


  这个可能看起来有点乱,但是我们真正做的其实是通过多项式减去数据中的各种权值直
到只剩下余数,如果你不懂这个的话就去看看上文中出现过的例子。
  需要明确的一点是以上的算法需要能够在W为任何长度时工作。


第十章 一个表驱动的实现
  上面那个叫“SIMPLE”的例子是一个很好的开始,因为它是和原理直接对应的,还有就
它的确很简单。然而,因为它进行的是位操作,所以对于实现的代码会很笨拙(即使是用
C来实现)而且运行效率低(因为在实现中对于每个位都要进行一次循环)。为了提高运算
速度,我们需要找到一个使得最小处理单位比位要大的算法。候选的数目有半字节(4位)
,字节(8位),字(16位),长字(32位)或者更多位,不管位数是多少,只要我们能够
实现就行。在这些可选项中,4位最好是避免选择的,因为它不能直接和一字节对应。任何
加速的方法允许我们至少要操作一个字节,并且事实上大多数表驱动算法一次都是操作一
个字节的。
  为了方便讨论,我们现在讲多项式的宽度由4位变成32位。我们的寄存器也是一样宽度的
除了每个块代表一个字节而不是一个位,并且这个多项式是33位的(一个是隐式的1还有32
个激活的位)(W=32)。
                3    2    1    0    Bytes
           +----+----+----+----+
Pop! <--   |    |    |    |    | <----- Augmented message
           +----+----+----+----+
          1<------32 bits------>


那个叫“SIMPLE”在这里任然是可以应用的。让我们看看这个是如何工作的。假设“SIMPLE”
算法的是活跃的并且32位寄存器中的高8位有数值:
t7 t6 t5 t4 t3 t2 t1 t0
  在“SIMPLE”算法的循环中,t7决定着多项式是否会和整个寄存器进行异或操作。如果t7
为1,那么就会发生异或运算,否则就不会。假设多项式的高8位为g7 g6 ... g0,然后在
下一次循环之后,高位的字节就会变成这样:
        t6 t5 t4 t3 t2 t1 t0 ??
+ t7 * (g7 g6 g5 g4 g3 g2 g1 g0) [Reminder: + is XOR]


  新的最高位(就是将会决定在下一轮循环中会如何处理的那个位)现在拥有的值为
t6+t7*g7。这里需要注意的重要的事情是从信息的角度来看,所有需要被用来计算新的最
高位的的信息都存在于原始数据的最高两位中。相似的,下一个可以被计算的最高位仅仅
来自于高三位t7,t6,t5。实际上,一般来说经过k此循环后的最高位的寄存器值可以通过
寄存器中的高k位来计算的。我们把这暂时看做理所应当的。
  考虑下这种情况,我们使用高8位的寄存器值来计算接下来8次循环后的最高位值。假设
我们通过已经计算好的值来驱动接下来的8个循环(这个值可以通过存储在一个字节寄存器
中实现并且逐次移出每一位)。接下来我们需要注意三件事情:
  *现在寄存器的最高位就无所谓是多少了。因为无论多项式经过多少次移位后和高8位进
行异或运算,这里都会在下一个8循环中被移出到右手边。
  *剩下的位将会向左移动移位并且最右边的寄存器的字节会被转换到下一个字节中。
  *当所有这些继续时,寄存器的值会受到一系列的预先计算好的控制字节的异或运算的
   影响。


  现在考虑一个常量异或和不同偏移值对寄存器的影响。举例来说:
  0100010 Register
  ...0110 XOR this
  ..0110. XOR this
  0110... XOR this
  -------
  0011000
  -------


  这里很关键的只要你愿意你就可以将一个常数异或到一个寄存器中,最后,这里会出现
一个值,这个值在和原始数据进行异或运算时会和其他的异或运算有相同的结果。
  现在你大概已经知道解决方法了。通过把所有的小块整合到一起,我们就有了这样一个
算法:
  While (加强的数据没有被用完)
  Begin
  检测寄存器的最高字节
  通过寄存器中的最高位字节来计算控制字节
  在与控制字节保持一致的情况下,对所有将会被异或到寄存器中的各种偏移的多项式进
  行求和。
  将寄存器中的数据左移一个字节,然后读取一个新的字节道最右边的寄存器中。
  将经过求和的多项式的和异或到寄存器中。
  End


  如它所被理解的那样这个算法并没有比那个“SIMPLE”算法好很多。然而,这个算法说
明了大多数计算都是可以被预先计算并且可以用来组成一个表格。因此,上面的算法可以被
精简成为:
  While (加强的数据没有被用完)
  Begin
  Top = top_byte(Register);
  Register = (Register << 24) | next_augmessage_byte;
  Register = Register XOR precomputed_table[Top];
  End


  到这里,如果你理解了这个,那么你就抓住了表驱动CRC算法的核心。上面的这个算法是
一个很有效率的算法,其中仅仅只需要一个转换,一个与运算,一个异或运算还有一个表
用来查找每个字节。画图来说明,就是下面这样:
                3    2    1    0 Bytes
           +----+----+----+----+
+-----<    |    |    |    |    | <----- Augmented message
|          +----+----+----+----+
|                    ^
|                    |     
|                   XOR
|                    |
| 0        +----+----+----+----+
v          +----+----+----+----+
|          +----+----+----+----+
|          +----+----+----+----+
|          +----+----+----+----+
|          +----+----+----+----+
|          +----+----+----+----+
+----->    +----+----+----+----+
           +----+----+----+----+
           +----+----+----+----+
           +----+----+----+----+
           +----+----+----+----+
        255+----+----+----+----+


算法:
1.把寄存器的值向左移动一个字节,然后读取另外一个新字节到寄存器中。
2.使用刚刚从寄存器中移出的最高字节来索引256个32位值得表
3将表中的值异或到寄存器中
4如果还有加强数据就返回到1


在C语言中,算法的主循环是这样的:
  r=0;
  while (len--)
 {
  byte t = (r >> 24) & 0xFF;
  r = (r << 8) | *++;
  r^=table[t];
 }


len是加强数据的字节长度,p是指向加强数据包的指针,r是寄存器,t是温度,表示一个
计算好的表格。这个表格可以被修改为下面这样(虽然不便于阅读):
  r=0;
  while (len--)
  r = ((r << 8) | *++) ^ t[(r >> 24) & 0xFF];


这就是一个很清晰、有效率地循环,虽然这个对于不是精通于CRC策略的粗略查看者并不明显。
我们将成这个为“TABLE”算法。


第十一章 一个轻微修改的表驱动实现
  尽管有行的简洁之美。
  r=0;
  while (len--)
  r = ((r << 8) | *++) ^ t[(r >> 24) & 0xFF];


那些追求完美的极客们是不会不管他的。你看见的问题是这个循环是基于加强数据来进行
操作的,而且为了使用这个代码,你必须在用指针指向它之前在数据尾部添加W/8个0才行。
依赖于运行时的环境,这个可能不是问题,但是如果数据块是通过其他的方式传递给我
们的,那么就会有大问题了。一个可选的方法就是简单地在上面的循环后针对每个0字节
加入以下东西:
  for (i=0; i<W/4; i++)
    r = (r << 8) ^ t[(r >> 24) & 0xFF];
这个对我来说足够理智。然而,从长远来看(你必须承认的是对于代码有很大益处)我们
可以重新组织这个小循环,进而就可以同时避免在加强的信息后面添加0字节以及可以和
上面一样明确的在尾部计算0字节。为了解释这个优化,我们回到之前给出过的计算图表
处:
              3    2    1    0 Bytes
         +----+----+----+----+
+-----<  |    |    |    |    | <----- Augmented message
|        +----+----+----+----+
|        ^
|        |
|        XOR
|        |
|       0+----+----+----+----+
v        +----+----+----+----+
|        +----+----+----+----+
|        +----+----+----+----+
|        +----+----+----+----+
|        +----+----+----+----+
|        +----+----+----+----+
+----->  +----+----+----+----+
         +----+----+----+----+
         +----+----+----+----+
         +----+----+----+----+
         +----+----+----+----+
      255+----+----+----+----+


算法:
1将寄存器中的值向左移动一个字节,然后读取一起新字节到寄存器中
2使用刚刚被移出寄存器的那个字节来索引表中的256个32位数据
3将表中的数据异或到寄存器中
4如果加强数据还有数据就返回到1


向左:注意以下事实


尾部:
  那W/4个被添加在数据尾部的0字节将会和其他自己一样从右边被写入到寄存器中。但是
它们的值对于寄存器中的值没有影响,因为1和0做异或运算结果还是1。那四个字节永远不
会从左边被传输出去因为他们对还有0的地方可能会有一定的影响。所以添加在数据后面
的W/4个0字节的唯一作用就是用来驱动另外W/4个字节的计算。进而便于真实数据的尾部
通过所有的方式通过寄存器。
头部:
  如果寄存器的初始值是0,前四次的循环迭代就会仅仅对从右边来的前四个字节有影响。
这是因为第一个32位的控制位都是0所以就没有数据被异或到寄存器中去了。即使初始值
不是0,在算法中前4个字节的循环迭代将会仅仅对数据的前4个字节数据转换到寄存器中,
然后将它们和一些常量做异或运算(这是寄存器初始化值得一个功能)。


这些事实,结合了异或运算的以下属性:
(A xor B) xor C = A xor (B xor C)


意味着数据字节并不需要真正的穿过W/4字节的寄存器。所不同的是,它们可以在被需要
用来索引查找表格前被异或到最高位字节中。这个导致了下面这个版本的算法。
+-----<Message (non augmented)
|
v       3    2    1    0    Bytes
|       +----+----+----+----+
XOR----<|    |    |    |    |
|       +----+----+----+----+
|                 ^
|                 |
|                XOR
|                 |
|      0+----+----+----+----+
v       +----+----+----+----+
|       +----+----+----+----+
|       +----+----+----+----+
|       +----+----+----+----+
|       +----+----+----+----+
|       +----+----+----+----+
+-----> +----+----+----+----+
        +----+----+----+----+
        +----+----+----+----+
        +----+----+----+----+
        +----+----+----+----+
     255+----+----+----+----+


算法:
1将寄存器向左移动一个字节,然后读取一个新的字节到寄存器中。
2将刚刚从寄存器中移出的值和下一个数据字节做异或运算然后去表中进行索引(0~255)
3将表中的值异或到寄存器中
4如果加强数据还有数据的话返回第一步


注意:这个算法的寄存器初始值必须是之前算法经过4次填写表格后的寄存器值。
注意:如果之前的算法使用了0,那么这个表格会是这样。新算法也一样。


这是两个相同的算法,并且标定的结果也是相同的,C代码如下:
  r=0;
  while (len--)
  r = (r<<8) ^ t[(r >> 24) ^ *++];


这个就是你们当前可能发现内部表驱动CRC实现的代码。为了方便一致,一些FF可能需要
在这里或者那里进行与运算,但是事实上,以上的循环是IT,我们将称这个为直接查表算
法。


  在我尝试理解这个东西时,我尝试得到了“SIMPLE”算法和表驱动版本。然而,当我把
我的代码和真正实现中的代码做对比时,我曾经被完全的欺骗了为什么字节都被异或到了
寄存器的错误一端。花了很长时间我才明白其实他们的代码和我的实际上是一样的。我写
这篇文档的一部分理由是在除法和我之前的表驱动代码的联系不明显,而当你开始从寄存器
的“错误端”取出数据时,任何这样的联系都会被相当好地擦除。这些看来都是错误的。


  如果你理解到了这一步,你就不仅仅只是理解了这个策略,这个练习,这个优化的练习,
而且你将会理解你将要遇到的真正的代码。那个代码会更加复杂吗?的确会更加复杂。


第十二章 “反射的”表驱动实现


  尽管上面的代码差不多已经优化到它可以尽可能优化的程度了,但是这个并不会阻止有
进取心的人将算法变得更为复杂。为了理解这个是如何发生的,我们需要进入到硬件的世
界。


  定义:如果一个值/寄存器以中心为基准进行了翻转,那么这个值就被反射了。举例来说
0101的4位反射值就是1010。


0011的反射值是1100
0111-0101-1010-1111-0010-0101-1011-1100的反射值是
0011-1101-1010-0100-1111-0101-1010-1110


  原来UARTS(那些方便的小芯片驱动串行IO)在传输数据时的习惯是先传输LSB最后传输
MSB。这个惯例带来的额一个影响是硬件工程师在构建硬件CRC计算器是操作的是数据本身
的反射。数据都是以同样的顺序传输,但是每个字节中的位都被互换了。第0位现在成为
了第7位,第1位现在成为了第6位...现在如果这个惯例被限制在硬件领域那么就不会有什
么影响。然而在某些情况下这些展现在软件层面的CRC值必须通过程序员来写一些代码进而
使得其能和硬件兼容。


  在这种情况下,一个正常理智的软件工程师会在计算前简单地将每个字节进行反射。然而
看来一个正常理智的软件工程师在早先的规则被打破后就会显得很无力。因为除了对字节进
行反射,无论谁负责处理字节并且反射这个世界,导致下面的反射算法和之前的是一模一
样的,此时除了输入数据其他所有东西都是被反射的。


Message (non augmented) >-----+
|
Bytes 0 1 2 3 v
+----+----+----+----+ |
|     |   |    |    |>----XOR
+----+----+----+----+ |
          ^           |
          |           |
         XOR          |
          |           |
+----+----+----+----+0|
+----+----+----+----+ v
+----+----+----+----+ |
+----+----+----+----+ |
+----+----+----+----+ |
+----+----+----+----+ |
+----+----+----+----+ |
+----+----+----+----+<-----+
+----+----+----+----+
+----+----+----+----+
+----+----+----+----+
+----+----+----+----+
+----+----+----+----+255


注意:
1除了每个入口都被反射了,这个表和之前算法里面的那个是一模一样的。
2寄存器中的初始值和之前算法里面的是一样的,不同的是它们被反射了。
3字节信息的处理顺序和之前是一样的(即数据本身没有被反射)
4数据字节本身不需要被反射,因为所有东西都已经被反射了。


在计算的最后,寄存器中存储的是最后的CRC值的反射。实际上,我不知道是谁编造了这
个,因为看起来CRC算法的硬件实现使用了反射的校验值所以产生了一个被反射的CRC也就
是对的。实际上,如果是混乱的,那么反射这个世界可能是一个好的工程方法。


我们称这个为“REFLECTED”算法。


无论这个是否有意义,使用了反射的算法在整个世界里面的FTP站点中的影响是差不多一半
的CRC实现使用到了反射而另一半没有使用反射。这个真是无法理解。特别地,对我来说
那些运行了填写错误端反射表驱动实现方式的临时读者将会有谁会有曾经联系到二进制模2
除法的概念的机会。


  这将会变得更加让人疑惑不是吗? 是的


第十三章 “颠倒的”多项式
  如果反射的实现方式不够,这里还有一种概念可以让解决方法更为让人疑惑。这个概念
就是颠倒的多项式。
  现在的结果是好的多项式的反射也会是一个好的多项式!举例说:如果G=11101是一个
好的多项式值,那么10111也将会是一个好的多项式值。作为结果,似乎每一次组织制定了
一个特别好的多项式,这些在现实世界中不能单独地离开多项式反射。他们只能必须使用
它,作为结果,标准多项式的集合就有了一个相应的反射值,这个反射值集合也在被使用。
为了避免疑惑,我们将会称这些为“颠倒的”多项式。


X25 standard:   1-0001-0000-0010-0001
X25 reversed:   1-0000-1000-0001-0001
CRC16 standard: 1-1000-0000-0000-0101
CRC16 reversed: 1-0100-0000-0000-0011


需要注意的是这里是整个多项式都被反射或者翻转,而仅仅是最后的W位。这是一个重要
的不同点。在之前部分中描述过的反射算法,其中所使用的多项式其实和非反射算法中是
一模一样的。所有发生的事情是所有的字节都被有效的反射了。像这样的,算法中所有的
16位或者32位的数字都需要被反射。相反地,包含隐藏在最高位的整个多项式,这就导致
翻转一个多项式的后16或32位和反射一个多项式就是不同的。
  这个会导致的结果就是一个反射的算法和一个使用反射的多项式的原始算法是不一样的。
实际上,如果它们是对偶的话这个造成的疑惑就会少些。
  如果这个看起来有点不清楚的话,不要担心,因为我们马上就会将其分类。在那之前只
有一个部分。


第十四章 初始值和最终值
  除了之前已经知道了的复杂度,CRC算法和其他的不同之处表现在两个其他方面:
  1寄存器的初始值
  2和最终的寄存器值进行异或运算的值


  举例来说,CRC32算法将它的寄存器初始化为FFFFFFFF并且把最终的寄存器值和FFFFFFFF
做异或运算。
  大多数CRC算法将它们的寄存器的值初始化为0。然而,一些算法在初始化时将其初始化
为非0值。在策略中(即对于数据没有假设),初始值对于CRC算法的健壮性没有影响。初始
值仅仅提供了一个固定的起始点,进而寄存器可以以此为基础来进行操作。然而,实际中,
一些数据比其他的更好,所以讲CRC算法中的寄存器的值初始化为没有死角的值将会更为
明智。一个死角意味着一个数据的序列将不会导致寄存器中的值改变。特别地,任何将寄存
器的值初始化为0的CRC算法都会有一个死角,这个死角会在它开始运行并将要不能统计0的
个数时显现出来。在一个真的数据中0字节最先运行时很常见的,因此将算法的寄存器值
初始化为非0值是很明智的。


第十五章 绝对地定义算法
  现在我们已经了解了表驱动CRC算法的所有不同方面。由于这些算法有很多种变种,这就
必要来给他们专门发布一个命名了。这个部分就是做这个工作:


  我们知道CRC算法间的不同在于:
  1多项式的长度不同
  2多项式的值
  3寄存器的初始值
  4每一位数据在参与运算前是否被反射了
  5算法是否是通过寄存器填写输入字节还是通过从寄存器一端和数据做异或运算然后直接
放入到表中。
  6最后的寄存器值是否会被翻转(如同在反射的版本中一样)
  7和最后的寄存器进行异或运算的值


  为了能够讨论特殊的CRC算法,我们需要比之前更为精细的定义他们。出于这个理由,下
一个部分尝试提供一个变量都被定义好了的CRC算法模型。为了参考特殊的算法,我们需要
简单地制定算法的一些参数即可。


第十六章 一个参数化的CRC模型
  在这个部分,我们定义了一个精确的参数化的CRC算法模型,为了起个更好的名字,我
们将会称之为Rocksoft^tm CRC算法(为什么是这个?因为Rocksoft^tm可以做些免费广告)。
  这个算法模型的最重要的地方就是他仅仅关注功能,而忽略了所有的实现细节。这个训练
的目的就是排除了这些算法实现的复杂性从而来组织一个能精确参考特殊CRC算法的方式。为
此,这个模型必须尽可能简单和精确,还有尽可能少的造成疑惑。
  这个Rocksoft^tm基本上是以之前提过的直接表算法为基础的。然而,这个算法必须有更多
的参数从而能够使它和真实世界中那形形色色的算法表现的一致。
  为了使得这个算法表现的更像反射算法,我们提供了一个位选项来决定是否需要给输入
字节进行反射,还有一个位选项是用来决定是否需要反射输出的校验值。通过将反射加入
到输入输出的框架中,我们避免了将反射与非反射的概念混淆的疑惑。
  一个额外的参数允许算法的寄存器被初始化为一个特殊的值。一个进一步的参数会在最后
结果返回前和它进行异或操作。
  通过将所有东西整合起来,我们最后得到的算法的参数表:


名字
  这个是算法的名字,一个字符串
宽度
  这个是算法中描述的位的数目,这个比多项式的宽度少一位。
多项式
  这个参数是多项式。这是一个二进制值并且需要被转换成一个十六进制数字。它的最高位
需要被隐藏。举例来说,如果多项式时10110,那么你应该转换成06(十六进制)。这个参数
很重要的一个方面就是它代表了一个非反射的多项式。这个参数的最低位在除法期间总是
除数的最低位,不管这个算法模型是否被反射。
初始化
  这个参数指明了在算法开始时寄存器的初始值。这个值就是在直接表算法中被赋值给寄存器
的那个值。在表算法中,我们可能认为寄存器的初始值为0,然后这个值在经过N轮迭代后
被异或到寄存器中。这个参数是一个十六进制数字。
输入
  这是一个位参数,如果他为假,那么所有的输入字节都会被看做第7位位MSB以及第0位
作为LSB。如果这个参数为真,那么所有字节在参与计算前会先进行翻转操作。
输出
  这是一个位参数,如果它为假,寄存器中最后的记过会被直接填写到XOROUT状态中,否则
如果参数为真,那么最后的寄存器值会先被反射。
异或输出
  这是一个长度为W为的值,并且这个值需要被指定成为十六进制。它需要在寄存器的值被
作为官方校验值返回前和最后的寄存器值做异或运算(在REFOUT后面)。
校验
  这个区域严格来说应该不是定义的一个部分,并且由于这个部分和其他部分的不一致性,
其他部分是优先的。这个部分可以看做是算法具体实现的一个小的验证器。这个部分包含了
ASCII字符串“123456789”在通过特定算法后得到的校验值。


  当这些参数并定义后,现在这个算法就可以准确的描述一个特殊的CRC算法了。这里有一个
CRC-16算法的流行定义例子。
Name : "CRC-16"
Width : 16
Poly : 8005
Init : 0000
RefIn : True
RefOut : True
XorOut : 0000
Check : BB3D


第十七章 参数设置的标准目录
  现在,我想给出一个经常使用的CRC算法的规范列表。然而,到目前为止我所接触的所有
算都是比较模糊的进而导致这个想法是不可能的。我能提供的是一个我所听说过的各种CRC
算法的多项式列表:
X25 standard : 1021 [CRC-CCITT, ADCCP, SDLC/HDLC]
X25 reversed : 0811
CRC16 standard : 8005
CRC16 reversed : 4003 [LHA]
CRC32 : 04C11DB7 [PKZIP, AUTODIN II, Ethernet, FDDI]
  
  如果谁能够将整个模型参数的集合和这些标准中的任意一个绑定到一起的话,我都会很
有兴趣。然而,附近的一个程序可能说明接下来的规格。有任何人可以确定或者否定他们
(或者提供校验值(一个我不会再写代码或者计算过程中被困扰的值))。
Name : "CRC-16/CITT"
Width : 16
Poly : 1021
Init : FFFF
RefIn : False
RefOut : False
XorOut : 0000
Check : ?


Name : "XMODEM"
Width : 16
Poly : 8408
Init : 0000
RefIn : True
RefOut : True
XorOut : 0000
Check : ?


Name : "ARC"
Width : 16
Poly : 8005
Init : 0000
RefIn : True
RefOut : True
XorOut : 0000
Check : ?


这里是CRC-32算法的规范,它被用于PKZip,AUTODIN II,Ethernet,以及FDDI。


Name : "CRC-32"
Width : 32
Poly : 04C11DB7
Init : FFFFFFFF
RefIn : True
RefOut : True
XorOut : FFFFFFFF
Check : CBF43926


第十八章 一个模型算法的实现


  这里是模型算法的C语言实现。这个实现包含了一个头文件和一个实现文件。如果你是
连续滚动地来读这个文件,你可以通过搜索“Roll Your Own”来快速浏览这个代码。
  为了确保下面的代码有在工作,将其设置为上文给出的CRC-16或者CRC32来确保程序在
输出字符串“123456789”时能够产生指定的校验值。


crcmodel.h
http://www.repairfaq.org/filipg/LINK/crcmodel.h
crcmodel.c
http://www.repairfaq.org/filipg/LINK/crcmodel.c


第十九章 生成你自己的表驱动实现


  尽管我在理解和定义CRC算法的时候有很多抱怨,他们告诉实现的技术任然很琐碎。仅仅
有两种形式:正常和反射。正常的在Refin=FALSE和Refot=FALSE下左移转换和覆盖算法的
情况。在那些参数都为真时,反射右移和覆盖算法。(如果你想一个参数为真而其他参数
都为假,那么你需要自己指出。)多项式被嵌入到查询表中(这个还需要讨论)。其他的
参数,初始化和XorOt可以被定义为宏。以下是32位的正常形式(16位的是一样的)。
unsigned long crc_normal ();
unsigned long crc_normal (blk_adr,blk_len)
unsigned char *blk_adr;
unsigned long blk_len;
{
unsigned long crc = INIT;
while (blk_len--)
crc = crctable[((crc>>24) ^ *blk_adr++) & 0xFFL] ^ (crc << 8);
return crc ^ XOROT;
}
Here is the reflected form:
unsigned long crc_reflected ();
unsigned long crc_reflected (blk_adr,blk_len)
unsigned char *blk_adr;
unsigned long blk_len;
{
unsigned long crc = INIT_REFLECTED;
while (blk_len--)
crc = crctable[(crc ^ *blk_adr++) & 0xFFL] ^ (crc >> 8));
return crc ^ XOROT;
}


  注意:我有仔细地检查上面的两个代码块,但是我没有实际编译和测试他们。如果你能够
通过运行你根据之前提供的参考模型编写的代码来让他正确的话,那么无论你如何编码都是
没有问题的。上面的代码块只是一个粗糙的指导。参考模型是明确的指导。注意:如果你
不怎么关注速度,那么使用参考模型代码就行。


第二十章 生成一个查找表
  在之前部分中的正常和翻转代码块中,缺少的就是查找表。这个查找表可以在运行时通
果之前给的模型包中的cm_tab功能计算出来,或者也可以被提前计算出来后在加入到c代码
中去。在任意一种情况中,需要注意的是这个查找表仅仅和多项式还有RefIn(和RefIn)
参数有关。从根本上说,是多项式决定了这个表,如果你想使用反射的形式,你也可以生
成一个反射的表。
  下面的程序生成了任何想要的16位或者32位查找表。如果你不想看代码,你可以直接跳
到单词“Summary”处。
C程序链接:
http://www.repairfaq.org/filipg/LINK/crctable.c


第二十一章 总结
  这个文档提供了CRC算法的详细解释,并且解释了它们的策略,同时按照难度循序渐进
地从简单的为转换到字节转换表驱动实现。CRC算法各式各样不同的实现让之前已经解释
过的东西变得让人疑惑。一个参数化的算法模型并定义了用来精确的定义某个CRC算法,同
时还提供了它们的参考实现方式。最后,提供了一个生成CRC表的程序。


第二十二章 修正
  如果你任务这个文档中有任何地方是不清楚或者错误的,或者你有任何其他信息以及如何
提升这篇文档的建议,请跟我联系。特别地,如果有谁能够给我提供一套参数将Rocksoft(tm)
CRC模型算法变为标准CRC算法的话,我会和乐意。邮件联系我,Ross N. Williams
ross@guest.adelaide.edu.au


第二十三章 名词解释


CHECKSUM
  一个被计算出来作为数据包一部分的数字。“Check-Sum”的字面意思按理说应该就是讲数据
包中的所有元素进行加和操作。也许这就是早期校验和的计算方法。然而,当下更多更为复杂
的公式都有被使用,但是“checksum”还是被使用着。


CRC
  这个是Cyclic Redundancy Code的简称。而“checksum”看起来更像是应用于非加密校验数
据单元。“CRC”像是仅仅由于基于多项式除法想法的算法而被保留下来。


G
  这个符号在本文档中被用来代替Poly。


MESSAGE
  表示已经被校验过的输入数据。这个通常以字节流的形式被组织起来。是每个字节的最高位
还是最低位被看做MSB或者LSB都是有算法的参数决定的。


POLY
  这是CRC算法中多项式的另外一个称呼。


POLYNOMIAL
  CRC算法中的多项式时CRC算法实现中除法运算里面的除数。


REFLECT
  一个二进制数据以他的中心进行交换。举例来说:1101的反射是1011


ROCKSOFT(tm) MODEL CRC ALGORITHM
  一个参数化的算法,目的是作为描述CRC算法的实例化参考。典型的CRC算法都是同过引用一
个多项式来指出。然而,为了能够组织一个精确的实现方法,我们必须知道包含初始化值在内
的多个参数。


WIDTH
  CRC算法的宽度是其多项式宽度减1。举例来说,如果多项式时11011,那么宽度就是4位。这
个宽度通常被设置为8位的倍数。


第二十四章 参考文献
  
  [Griffiths87] Griffiths, G., Carlyle Stones, G., "The Tea-Leaf Reader Algorithm:
An Efficient Implementation of CRC-16 and CRC-32", Communications of the ACM, 30
(7), pp.617-620. Comment: This paper describes a high-speed table-driven implement
ation of CRC algorithms. The technique seems to be a touch messy, and is superceded
 by the Sarwete algorithm.


  [Knuth81] Knuth, D.E., "The Art of Computer Programming", Volume 2: Seminumerical Algorithms, Section 4.6.
  [Nelson 91] Nelson, M., "The Data Compression Book", M&T Books, (501 Galveston Drive, Redwood City, CA 94063), 1991, ISBN: 1-55851-214-4. Comment: If you want to see a real implementation of a real 32-bit checksum algorithm, look on pages 440, and 446-448.
  [Sarwate88] Sarwate, D.V., "Computation of Cyclic Redundancy Checks via Table Look-Up", Communications of the ACM, 31(8),
pp.1008-1013. Comment: This paper describes a high-speed table-driven implementation for CRC algorithms that is superior to the tea-leaf algorithm. Although this paper describes the technique used by most modern CRC implementations, I found the appendix of this paper (where all the good stuff is) difficult to understand.
  [Tanenbaum81] Tanenbaum, A.S., "Computer Networks", Prentice Hall, 1981, ISBN: 0-13-164699-0. Comment: Section 3.5.3 on pages 128 to 132 provides a very clear description of CRC codes. However, it does not describe table-driven implementation techniques.


第二十五章 已经找到但是还没有查看的参考文献
  
  Boudreau, Steen, "Cyclic Redundancy Checking by Program," AFIPS Proceedings, Vol. 39, 1971.
  Davies, Barber, "Computer Networks and Their Protocols," J. Wiley & Sons, 1979.
  Higginson, Kirstein, "On the Computation of Cyclic Redundancy Checks by Program," The Computer Journal (British), Vol. 16, No. 1, Feb 1973.
  McNamara, J. E., "Technical Aspects of Data Communication," 2nd Edition, Digital Press, Bedford, Massachusetts, 1982.
  Marton and Frambs, "A Cyclic Redundancy Checking (CRC) Algorithm," Honeywell Computer Journal, Vol. 5, No. 3, 1971.
  Nelson M., "File verification using CRC", Dr Dobbs Journal, May 1992, pp.64-67.
  Ramabadran T.V., Gaitonde S.S., "A tutorial on CRC computations", IEEE Micro, Aug 1988.
  Schwaderer W.D., "CRC Calculation", April 85 PC Tech Journal, pp.118-133.
  Ward R.K, Tabandeh M., "Error Correction and Detection, A Geometric Approach" The Computer Journal, Vol. 27, No. 3, 1984, pp.246-253.
  Wecker, S., "A Table-Lookup Algorithm for Software Computation of Cyclic Redundancy Check (CRC)," Digital Equipment Corporation memorandum, 1974.




请在引用本文时说明本文出处!这篇文章由filipg@repairfaq.org书写。最新的版本可以在
以下这个网站上面找到:
http://www.repairfaq.org/filipg/
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值