-
引子
Big/Little Endian是Host CPU如何去理解在内存中的数据,内存中的数据是没有Big/Little Endian之分的(内存仅仅作为存储介质),而Host CPU才有Big/Little Endian之分。
不同Endian的CPU,从内存读取数据的时候,按自己的Endian模式转换数据,放入CPU的寄存器reg中,假如寄存器是32bits,此时寄存器bit31存储数据的MSB高位,即寄存器bit[31-0]符合我们人类的阅读顺序(我们程序中定义的变量,是在CPU寄存器完成操作,因此在编程时对变量值的赋值或者读取是符合人类阅读顺序的)。数据从CPU写入到内存,同样需要类似的转换。 -
Big Endian和Little Endian
本章解释Big Endian和Little Endian的具体行为,从软件人员的角度理解是这样的: 大小端字节序指的是多字节类型的字节数据在内存中的存储顺序,字节内的各bit位置并不变化。
例如数据:0x12345678 (符合人类的阅读顺序,或者程序中常量的顺序,即寄存器bit31-0)
在大端模式下的存储情况为:
内存地址 | 0 | 1 | 2 | 3 |
---|---|---|---|---|
十六进制数据 | 0x12 | 0x34 | 0x56 | 0x78 |
二进制数据 | 0001 0010 | 0011 0100 | 0101 0110 | 0111 1000 |
在小端模式下的存储情况为:
内存地址 | 0 | 1 | 2 | 3 |
---|---|---|---|---|
十六进制数据 | 0x78 | 0x56 | 0x34 | 0x12 |
二进制数据 | 0111 1000 | 0101 0110 | 0011 0100 | 0001 0010 |
-
Networks Order
其实网络报文在线路上传播的时候并没有和内存打交道,但在接收或者发送的时候,需要存入内存,或者从内存中读取报文内容进行发送。不同的CPU Endian模式,可能会造成报文内容的差异,因此规定不管CPU是什么Endian模式,针对网络报文必须这样处理:报文的第一个字节被认为是报文的高字节MSB,放入内存的低地址上(报文是从第一个字节开始收发,内存是从低地址开始申请,因此这种定义是符合实际的)。这种跟CPU Endian模式无关,只针对网络报文的定义并称为网络字节序(Networks Order)。
可见,网络序的定义是和Big Endian的行为一致的,因此在Big Endian CPU上处理网络序并不需要特殊处理了。而在Little Endian CPU上的软件需要特殊处理(请注意是软件处理),比如报文的DMAC高4字节是0x12345678,在作为网络报文时,软件通过htonl函数转成网络序,在Little Endian CPU对应的内存中存放的格式同Big Endian CPU。硬件处理发送接收报文的模块(例如网卡,非CPU),按网络序发送到网络线路上或从网络线路上接收。 -
多字节如何转换Big/Little Endian
第二章描述了多字节数据在不同Big/Little Endian CPU的存储情况,普通的应用程序是不需要转换的,读写都是CPU自动完成。但是作为运行在CPU上的网卡驱动,需要去配置网卡硬件中的寄存器或者网卡内部Memory。然而,Host CPU和网卡的Endian模式可能是不同的,因此网卡驱动软件需转换数据的Big/Little Endian。
例如网迅的网卡是Little Endian的,若驱动运行在Big Endian的Host CPU上,驱动的转换代码就会生效。
a) 通过PCIE bar直接配置寄存器:
寄存器往往是32bits的,软件定义u32变量和其对齐。驱动通过wr32配置,在wr32函数内会进行Big/Little Endian转换:
#define wr32(reg, val) \
do { \
u8 *base = hw->base; \
writel((val), &base[(reg)]); \
} while (0)
static inline void writel(u32 value, volatile void __iomem *addr)
{
__io_bw();
__raw_writel((u32 __force)__cpu_to_le32(value), addr);
__io_aw();
}
b) 通过Admin queue配置网卡内部Memory:
Adminq中的data,对应的是网卡内部的Memory,可能是u32,u16,u8类型,软件要选择和硬件对齐的数据类型来定义。驱动在data buffer中已经组装成了Little Endian的格式,方便固件直接Copy到网卡内部Memory。Adminq Desc描述符本身也是Little Endian,驱动负责转换大小端。
网络报文内容相关的网卡硬件寄存器或者内部Memory,则要定义为Networks Order(Big Endian)。
5. 芯片硬件人员的大小端理解
咨询了网卡的硬件开发人员,Little Endian是从高bit到低bit取数据,Big Endian是从低bit到高bit取数据。例如读取16bit的一个数据(寄存器或者网卡内部Memory),Little Endian的网卡按bit[15:0]方式,而Big Endian是按bit[0:15]的方式。
以数据0x1234为例:
SRAM物理连线编号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Little Endian的位序 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 |
Big Endian的位序 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
二进制值 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 |
在Little Endian网卡硬件上,以bit[15:0]的方式取到的是0x3412。在Big Endian硬件上,以bit[0:15]的方式取到的是0x1234。
这里最主要的一点是,Little Endian和Big Endian硬件对SRAM存储介质的第一根物理连线认为是bit几是不同的,Little Endian硬件认为是bit7,而Bit Endian硬件认为是bit0。
硬件ASIC应该是通过总线来读写SRAM中的值,上述这点可能是总线的行为操作。
- 单字节为什么不需要转换
第5章节已经能知道为什么单字节不需要转换。
以数据0x12为例:
SRAM物理连线编号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
Little Endian的位序 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Big Endian的位序 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
二进制值 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
在Little Endian网卡硬件上,以bit[7:0]的方式取到的是0x12。在Big Endian硬件上,以bit[0:7]的方式取到的是0x12。
-
位域bit field如何转换Big/Little Endian
位域在大小端系统的差异是软件编程和编译器的行为了。主要是针对结构体定义的内存分配原则。在对Struct(结构体)中成员进行内存分配的时候,“按排列顺序分配,先分配排在前面的”:
1.大端(big endian)模式,从高位向地位分配
对字节,先分配高字节(内存低地址中),再分配低字节(内存高地址中)。
对位域,先分配高bit位,再分配低bit位。大端的高bit位序是bit0
2.小端(little endian)模式,从地位向高位分配
对字节,先分配低字节(内存低地址中),再分配高字节(内存高地址中)。
对位域,先分配低bit位,再分配高bit位。小端的低bit位序是bit0
若能够理解第5章节的硬件行为,这个原则其实是统一的。不管大小端,对字节:从内存地址开始;对位域,在对应的字节数内,从硬件的位序bit0开始。
7.1 例子
定义如下结构体:
typedef struct tagBigOrLittleData
{
UINT32 uiA:5; /对应数据赋值:00101/ 简单起见,对应的值刚好是位域长度
UINT32 uiB:9; /对应数据赋值:000001001/
UINT32 uiC:12;/对应数据赋值:000000001100/
UINT32 uiD:6; /对应数据赋值:000110/
}BigOrLittleData_S;
大端模式下,硬件以bit[0:31]方式读取为(0x28240306),分配从bit0(物理连线编号0)开始,为uiA分配bit[0:4], uiB分配bit[5:13],依次类推,如下:
小端模式下,硬件以bit[0:31]方式读取为(0x18030125),分配从bit0(物理连线编号7)开始,为uiA分配bit[4:0](物理编号为3-7),为uiB分配bit[13:5] (物理连线编号10-15,0,1,2),如下:
可见,在相同的结构体定义下,在大小端上的值不同,为此软件定义结构体位域需要区分大小端。重新定义如下(大端不变,小端重新定义了一下):
typedef struct tagBigOrLittleData
{
#ifdef BIG_ENDIAN
UINT32 uiA:5; /对应数据赋值:00101/
UINT32 uiB:9; /对应数据赋值:000001001/
UINT32 uiC:12;/对应数据赋值:000000001100/
UINT32 uiD:6; /对应数据赋值:000110/
#else
UINT32 uiD:6; /对应数据赋值:000110/
UINT32 uiC:12;/对应数据赋值:000000001100/
UINT32 uiB:9; /对应数据赋值:000001001/
UINT32 uiA:5; /对应数据赋值:00101/
#endif
}BigOrLittleData_S;
重新定义的小端模式,硬件以bit[0:31]方式读取为(0x28240306),与之前的大端模式值相同。
- 参考资料
https://blog.csdn.net/AlbertoNo1/article/details/50862364
https://blog.csdn.net/chen_xing_hai/article/details/126279425
END