字节序与大小端

endian_of_picture.jpg

概述

我们书写的规则一般是从左往右,如写一个数字一千二百三十四,写成1234.这是日常生活中约定俗称的表示方法,其背后的逻辑是最高有效位在最左边,1表示1千是最高有效位,其在最左;如果我们约定最低有效位在最左,那么同样表示一千二百三十四,我们需要写成4321了。
在计算机领域,特别是在存储和通信领域,一般以字节为单位存储或者传输数据,那么对于多字节数据的字节序(Byte Order)表示就有两种方法:

大端(big endian): 最高有效字节(MSB:Most Significant Byte)存储在最低地址(LBA: Lowest Byte Address)。
小端(little endian): 最高有效字节存储在最高地址(HBA:Highest Byte Address)。

同样对于一个字节的8个位的位序(Bit Order)也有两种表示方法:

大端(big endian): 最高有效位(msb:Most Significant Bit)存储在最低地址(LBA: Lowest Byte Address)。
小端(little endian): 最高有效位存储在最高地址(HBA:Highest Byte Address)。

对于给定一个计算机系统位序一般与字节序相同。

如果我们认为日常书写数字是从低地址开始写的,那么可以说,我们的书写规则是遵从大端规则的。

表示方法

endian_in_computer.gif

我们用大端和小端两种形式来表示同一个数0x0A0B0C0D.
大端系统:

byte addr 0 1 2 3
bit offset 01234567 01234567 01234567 01234567
binary 00001010 00001011 00001100 00001101
hex 0A 0B 0C 0D

小端系统:

byte addr 3 2 1 0
bit offset 76543210 76543210 76543210 76543210
binary 00001010 00001011 00001100 00001101
hex 0A 0B 0C 0D

以上两种表示方法,我们都可以一目了然看出0x0A0B0C0D。
如果我们保持byte address和bit offset不变,小端系统也可以表示成:

byte addr 0 1 2 3
bit offset 01234567 01234567 01234567 01234567
binary 10110000 00110000 11010000 01010000

这样不去计算,不能一眼看出表示的数是0x0A0B0C0D.

CPU的大小端

CPU的大小端一般指寄存器,总线,cache及内存等的字节序和位序。
小端CPU包括Intel(x86等)和DEC,小端格式也称为Intel格式
大端CPU包括Motorola,Sun Spark以及IBM(PowerPC),大端格式也称为Motorola格式
MIPS和ARM可以配置成任意一种。
CPU的大小端影响到CPU的指令集,不同的字节序CPU需要不一样的GNU工具链编译器。如,mipse-linux-gcc是支持大端mips架构的编译器,mipsl-linux-gcc是支持小端架构的编译器。
CPU的大小端会影响到软件访问多字节数据,虽然大多数时候对CPU大小端对软件是透明的。
如下代码,在不同字节序的CPU上运行会得到不同的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
union {
uint32_t my_int;
uint8_t my_bytes[4];
} endian_tester;
endian_tester et;
et.my_int = 0x0a0b0c0d;
if(et.my_bytes[0] == 0x0a)
{
printf( "I'm on a big-endian system\n" );
}
else
{
printf( "I'm on a little-endian system\n" );
}

总线的大小端

这里的总线不包括CPU内部的地址总线和数据总线等,专指外部总线,指器件之间的通讯总线。
对于并行总线(如PCI),如果是小端,则在32位的地址/数据总线AD[31:0],MSB连接到AD31,LSB连接到AD0。
对于串行总线,只存在协议上的大小端,不存在总线的大小端。

Device的大小端

Kevin’s Theory #1: When a multi-byte data unit travels across the boundary of two reverse endian systems, the conversion is made such that memory contiguousness to the unit is preserved.

Kevin’s Theory #2: In a C structure that contains bit fields, if field A is defined in front of field B, then field A always occupies a lower bit address than field B.

以NIC为例,vlan[0:24]有一个0xabcdef的值通过32位的存储边界(memory boundary),大端系统中:

byte addr 0 1 2 3 4 5 6 7
bit offset 01234567 01234567 01234567 01234567 01234567 01234567 01234567 01234567
binary 10101010 10 010111 10101011 11001101 11101111 00000000 00000000 00000000
hex aa 97 ab cd ef 00 00 00

tag[0:9]=bit[0:9]=0x2aa,rx[0:5]=bit[10:15]=0x17,vlan[0:15]=bit[16:23]=0xabcdef.
如果按照字长一致原则转换到小端系统中,字长位32bit:

byte addr 7 6 5 4 3 2 1 0
bit offset 76543210 76543210 76543210 76543210 76543210 76543210 76543210 76543210
binary 11101111 00000000 00000000 00000000 10101010 10 010111 10101011 11001101
hex ef 00 00 00 aa 97 ab cd

字长转换造成存储地址不连续问题。
按照字节一致原则转换到小端系统中:

byte addr 7 6 5 4 3 2 1 0
bit offset 76543210 76543210 76543210 76543210 76543210 76543210 76543210 76543210
binary 00000000 00000000 00000000 11101111 11001101 10101011 10010111 10 101010
hex 00 00 00 ef cd ab 97 aa

为得到与大端系统中一致的数据位,还需要软件再做一个字节交换(byte swap):

byte addr 7 6 5 4 3 2 1 0
bit offset 76543210 76543210 76543210 76543210 76543210 76543210 76543210 76543210
binary 00000000 00000000 00000000 10 101010 10010111 10101011 11001101 11101111
hex 00 00 00 aa 97 ab cd ef

根据Kevin’s Theory #2,我们可以定义C结构体来访问小端模式的NIC的标识符:

1
2
3
4
5
struct nic_tag_reg {
uint64_t vlan:24 __attribute__((packed));
uint64_t rx :6 __attribute__((packed));
uint64_t tag :10 __attribute__((packed));
};

大端模式下:

1
2
3
4
5
6
7
struct nic_tag_reg {
uint64_t tag :10 __attribute__((packed));
uint64_t rx :6 __attribute__((packed));
uint64_t vlan:24 __attribute__((packed));
};

协议的大小端

对于硬线上发送接收的位顺序,通常有规范来定义,不一定与上层的协议字节序一致,硬件会做相应的转换工作。
我们通常所说的网络字节序值网络传输协议的字节序,如IP协议数据按照大端字节序发送接收。
网络(network)字节序与本机(host)字节序不一致时,需要进行转换。如 htons/ntohs/ntohl/htonl等函数。

词源

据Jargon File记载,endian这个词来源于Jonathan Swift在1726年写的讽刺小说 “Gulliver’s Travels”(《格利佛游记》)。该小说在描述Gulliver畅游小人国时碰到了如下的一个场景。在小人国里的小人因为非常小(身高6英寸)所以总是碰到一些意想不到的问题。有一次因为对水煮蛋该从大的一端(Big-End)剥开还是小的一端(Little-End)剥开的争论而引发了一场战争,并形成了两支截然对立的队伍:支持从大的一端剥开的人Swift就称作Big-Endians,而支持从小的一端剥开的人就称作Little-Endians……(后缀ian表明的就是支持某种观点的人)。
1980年,Danny Cohen在其著名的论文”On Holy Wars and a Plea for Peace”中为了平息一场关于在消息中字节该以什么样的顺序进行传送的争论而引用了该词。该文中,Cohen非常形象贴切地把支持从一个消息序列的最高位开始传送的那伙人叫做Big-Endians,支持从最低位开始传送的相对应地叫做Little-Endians。此后Endian这个词便随着这篇论文而被广为采用。

Reference:

  1. Byte and Bit Order Dissection
  2. Endian_baiduBaike
  3. byte oreder……