CRC实例
假设要传输的原始数据为1101011011B,发送方和接收方在通信前约定好的除数为10011B。由于除数10011B是五位数(5bit),那么假设余数(即CRC码)为四位数(4bit)。因为现在余数未知,所以在进行模二除法运算前先将余数设为0000B,即待发送的数据为11010110110000B。下面开始进行模二除法运算来确定余数(即CRC码),更多计算细节,请查询模二除法:
可见余数(即CRC码)为1110B,因此发送方实际发送的是11010110111110B。
总结规律
将每次的计算结果定位res,使用的多项式称为p,正常的多项式是POLY=10011,异常为0,则:
每次迭代,根据res的首位决定p,若res的首位是1,则p=POLY;若res的首位是0,则p=0, 或者跳过此次迭代,上面的例子中就是碰到0后直接跳到后面的非零位。每次计算出结果后左移一位,即丢弃最高位。
如上图所示,直接的CRC-32校验代码如下:
/******************************************************************************
* Name: CRC-32/MPEG-2 x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1
* Poly: 0x4C11DB7
* Init: 0xFFFFFFF
* Refin: False
* Refout: False
* Xorout: 0x0000000
* Note:
*****************************************************************************/
uint32_t crc32_mpeg_2(uint8_t *data, uint16_t length)
{
uint8_t i;
uint32_t crc = 0xffffffff; // Initial value
while(length--)
{
crc ^= (uint32_t)(*data++) << 24;
{
if ( crc & 0x80000000 )
crc = (crc << 1) ^ 0x04C11DB7;
else
crc <<= 1;
}
}
return crc;
}
CRC参数模型
Width:CRC码的位宽,CRC32即为32位位宽。
Poly:省略最高位的多项式,因为更利于编程实现,即移位之后不需要考虑最高位;0x4C11DB7实际为0x104C11DB7,为32+1==33位。
Init:CRC码初始值,因为原始数据可能以不同位数的0开头,所以需要设置初始值来区分;
Refin是输入逆向标志位,如果为真,表示输入数据需要先进行逆向(即按位倒序),再进行后续运算;
Refout是最后输出逆向标志位,如果为真,表示CRC码需要先进行逆向(即按位倒序),再输出;
Xorout是最后输出异或值,是输出CRC码前与其进行异或运算的数;
Check是以UTF-8字符串"123456789"(作为8位字符数组)当做输入原始数据计算出的CRC码;
Residue是以无错误码字当做输入原始数据计算出的、不进行输出异或运算的CRC码,其具体含义有待进一步考究;
Name是算法名称。
举例
原始数据:0x34,使用CRC-8/MAXIN参数模型:
POLY = 0x31 = 0011 0001(最高位1已经省略)
INIT = 0x00
XOROUT = 0x00
REFIN = TRUE
REFOUT = TRUE
实际计算过程:
-
原始数据 = 0x34 = 0011 0100,多项式 = 0x31 = 1 0011 0001
-
INIT = 00,原始数据高8位和初始值进行异或运算保持不变。
-
REFIN为TRUE,需要先对原始数据进行翻转:0011 0100 > 0010 1100
-
原始数据左移8位,即后面补8个0:0010 1100 0000 0000
-
把处理之后的数据和多项式进行模2除法,求得余数:
原始数据:0010 1100 0000 0000 = 10 1100 0000 0000
多项式:1 0011 0001
模2除法取余数低8位:1111 1011 -
因为REFOUT为TRUE,对结果进行翻转得到最终的CRC-8值:1101 1111 = 0xDF
-
XOROUT进行异或,1111 1011 xor 0000 0000 = 1111 1011
-
数据+CRC:0011 0100 1101 1111 = 34DF,相当于原始数据左移8位+余数。
在实际计算的过程中,每次异或的最高位都会丢弃,因此在实际的代码中将poly最高位省略,相应的将crc寄存器左移一位。
得到如下的计算代码: crc = (crc << 1) ^ 0x04C11DB7
代码优化
对于如下的代码我们举个例子,对0xA3进行crc32_mpeg_2的计算
for (i = 0; i < 8; ++i)
{
if ( crc & 0x80000000 )
crc = (crc << 1) ^ 0x04C11DB7;
else
crc <<= 1;
}
从上述4次计算过程可以发现,影响最终结果的只有0xA3中的A与POLY(0x04C11DB7),我们可以将该计算过程进行整合:
(0xA3 << 4)(POLY<<3)(POLY<<1)
因为POLY是固定的,异或运行有结合律,我们可以根据不同的A值,将最终0xA3需要异或的数据X提前计算出来,加速运算的过程。
同样我们也可以一次处理8bit的数据,因此我们可以得到查表法的代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define POLY 0x04C11DB7
void get_table(uint32_t *table)
{
uint32_t res;
for(int i=0;i<256;i++)
{
res = (i<<24);
for (int j = 0; j < 8; ++j)
{
if((res&(1<<31)) != 0)
{
res = (res<<1)^POLY;
}
else
{
res = (res<<1);
}
}
table[i] = res;
}
}
uint32_t crc32_mpeg_2_table(uint8_t *data,uint32_t len, uint32_t *table)
{
uint8_t i;
uint32_t crc = 0xffffffff; // Initial value
uint8_t index;
while(len--)
{
index = (crc>>24)^(*data++)&0xff;
crc = (crc<<8)^table[index];
}
return crc;
}
int main()
{
uint8_t data[10] = {0x01,0x02,0x03,0x04};
uint32_t table[256];
get_table(table);
uint32_t ret = crc32_mpeg_2_table(data,4,table);
printf("res = %x\n",ret);
}
逆运算
对于一些CRC算法,在实际运算时需要考虑原始数据的逆向与输出结果的逆向,为了实际的代码,考虑将整个运算过程逆向,例如CRC32算法的参数模型为:
- Name: CRC-32 x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1
- Poly: 0x4C11DB7
- Init: 0xFFFFFFFF
- Refin: True
- Refout: True
- Xorout: 0xFFFFFFFF
- Alias: CRC_32/ADCCP
- Use: WinRAR,ect.
在实际运算时需要现将原始数据翻转,最后还需要将结果翻转,如图所示:
那我们在实际计算时,可以将CRC的原始值,POLY均进行翻转,计算时移位的方向也进行翻转,可以得到直接计算公式如下:
/******************************************************************************
* Name: CRC-32 x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1
* Poly: 0x4C11DB7
* Init: 0xFFFFFFFF
* Refin: True
* Refout: True
* Xorout: 0xFFFFFFFF
* Alias: CRC_32/ADCCP
* Use: WinRAR,ect.
*****************************************************************************/
uint32_t crc32(uint8_t *data, uint16_t length)
{
uint8_t i;
uint32_t crc = 0xffffffff; // Initial value
while(length--)
{
crc ^= *data++;
{
if ( crc & 1 )
crc = (crc >> 1) ^ 0xEDB88320;
else
crc >>= 1;
}
}
return crc^0xFFFFFFFF; // ~crc
}
同样可以得到逆向的查表法的代码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define POLY 0xEDB88320
void get_table(uint32_t *table)
{
uint32_t res;
for(int i=0;i<256;i++)
{
res = (i);
for (int j = 0; j < 8; ++j)
{
if((res&1) != 0)
{
res = (res>>1)^POLY;
}
else
{
res = (res>>1);
}
}
table[i] = res;
}
}
uint32_t crc32_table(uint8_t *data,uint32_t len, uint32_t *table)
{
uint8_t i;
uint32_t crc = 0xffffffff; // Initial value
uint8_t index;
while(len--)
{
index = (crc^(*data++))&0xff;
crc = (crc>>8)^table[index];
}
return ~crc;
}
int main()
{
uint8_t data[10] = {0x01,0x02,0x03,0x04};
uint32_t table[256];
get_table(table);
uint32_t ret = crc32_table(data,4,table);
printf("res = %x\n",ret);
}
硬件加速
ARMV8是支持CRC32的加速的,具体运行如下指令,查看是否包含crc32属性。
cat /proc/cpuinfo
使能硬件加速
typedef uint32_t u32;
typedef int64_t s64;
typedef uint8_t u8;
typedef uint64_t u64;
typedef uint16_t u16;
/*
*
* ARM hardware accelerated crc32 instruction
*
*/
#define CRC32X(crc, value) __asm__("crc32x %w[c], %w[c], %x[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32W(crc, value) __asm__("crc32w %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32H(crc, value) __asm__("crc32h %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32B(crc, value) __asm__("crc32b %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CX(crc, value) __asm__("crc32cx %w[c], %w[c], %x[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CW(crc, value) __asm__("crc32cw %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CH(crc, value) __asm__("crc32ch %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CB(crc, value) __asm__("crc32cb %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
static u32 crc32_arm64_le_hw(const u8 *p, unsigned int len)
{
uint32_t crc = 0xffffffff;
s64 length = len;
while ((length -= sizeof(u64)) >= 0) {
CRC32X(crc, *((uint64_t *)p));
p += sizeof(u64);
}
/* The following is more efficient than the straight loop */
if (length & sizeof(u32)) {
CRC32W(crc, *((uint32_t *)p));
p += sizeof(u32);
}
if (length & sizeof(u16)) {
CRC32H(crc, *((uint16_t *)p));
p += sizeof(u16);
}
if (length & sizeof(u8))
CRC32B(crc, *p);
return ~crc;
}
static u32 crc32c_arm64_le_hw(u32 crc, const u8 *p, unsigned int len)
{
s64 length = len;
while ((length -= sizeof(u64)) >= 0) {
CRC32CX(crc, *((uint64_t *)p));
p += sizeof(u64);
}
/* The following is more efficient than the straight loop */
if (length & sizeof(u32)) {
CRC32CW(crc, *((uint32_t *)p));
p += sizeof(u32);
}
if (length & sizeof(u16)) {
CRC32CH(crc, *((uint16_t *)p));
p += sizeof(u16);
}
if (length & sizeof(u8))
CRC32CB(crc, *p);
return ~crc;
}
说明:
x 代表 double :__crc32cd: 一次完成8个Byte的CRC32C计算,对应crc32cx指令。
w 代表 word :__crc32cw:一次完成4个Byte的CRC32C计算,对应crc32cw指令。
h 代表 half: __crc32ch:一次完成2个Byte的CRC32C计算,对应crc32ch指令。
b 代表 byte:__crc32cb: 一次完成1个Byte的CRC32C计算,对应crc32cb指令。
Poly 取值不一样
CRC32 : 0x04C11DB7; its reversed form 0xEDB88320
CRC32C :0x1EDC6F41; its reversed form 0x82F63B78
编译指令:
aarch64-linux-gnu-gcc -O2 -mcpu=generic+crc crc32_all.c -o crc32_all
or
aarch64-linux-gnu-gcc -O2 -march=armv8-a+crc crc32_all.c -o crc32_all
参考文献
https://blog.csdn.net/u012028275/article/details/112342542
https://blog.csdn.net/qq_25814297/article/details/109402108
https://blog.csdn.net/weixin_44256803/article/details/105805628
https://www.cnblogs.com/mic-chen/p/14944988.html
https://www.zangcq.com/2020/05/17/arm-crc32-%E6%8C%87%E4%BB%A4%E5%8A%A0%E9%80%9F/
常用的crc参数模型对应代码:crc-lib-c-master.zip
包含硬件加速的crc32的代码:crc32_all.c