Byte and Bit Order Dissection(转载)

来源:互联网 发布:荷鲁斯之眼与全知之眼 编辑:程序博客网 时间:2024/06/10 12:45

Byte and Bit Order Dissection

September 2nd, 2003 by Kevin Kaichuan He in

Discussing the differences between big and little endianness, bit and byte order and what it all means.

Editors' Note: This article has been updated since its original posting.

Software and hardware engineers who have to deal with byte and bit order issues know the process is like walking a maze. Though we usually come out of it, we consume a handful of our brain cells each time. This article tries to summarize the various areas in which the business of byte and bit order plays a role, including CPU, buses, devices and networking protocols. We dive into the details and hope to provide a good reference on this topic. The article also tries to suggest some guidelines and rules of thumb developed from practice.

Byte Order: the Endianness

We probably are familiar with the word endianness. First introduced by Danny Cohen in 1980, it describes the method a computer system uses to represent multi-byte integers.

Two types of endianness exist, big endian and little endian. Big endian refers to the method that stores the most significant byte of an integer at the lowest byte address. Little endian is the opposite; it refers to the method of storing the most significant byte of an integer at the highest byte address.

Bit order usually follows the same endianness as the byte order for a given computer system. That is, in a big endian system the most significant bit is stored at the lowest bit address; in a little endian system, the least significant bit is stored at the lowest bit address.

Every effort is made to avoid bit swapping in software when designing a system, because bit swapping is both expensive and tedious. Later sections describe how hardware takes care of it.

Documentation Guideline

Just as most people write a number from left to right, the layout of a multi-byte integer should flow from left to right, that is, from the most significant to the least significant byte. This is the most clear way to write integers, as we can see in the following examples.

Here is how we would write the integer 0x0a0b0c0d for both big endian and little endian systems, according to the rule above:

Write Integer for Big Endian System

byte  addr       0         1       2        3bit  offset  01234567 01234567 01234567 01234567     binary  00001010 00001011 00001100 00001101        hex     0a       0b      0c        0d

Write Integer for Little Endian System

byte  addr      3         2       1        0bit  offset  76543210 76543210 76543210 76543210     binary  00001010 00001011 00001100 00001101        hex     0a       0b      0c        0d

In both cases above, we can read from left to right and the number is 0x0a0b0c0d.

If we do not follow the rule, we might write the number in the following way:

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

As you can see, it's hard to make out what number we're trying to represent.

Simplified Computer System Used in this Article

Without losing generality, a simplified view of the computer system discussed in this article is drawn below.

CPU, local bus and internal memory/cache all are considered to be CPU, because they usually share the same endianness. Discussion of bus endianness, however, covers only external bus. The CPU register width, memory word width and bus width are assumed to be 32 bits for this article.

Endianness of CPU

The CPU endianness is the byte and bit order in which it interprets multi-byte integers from on-chip registers, local bus, in-line cache, memory and so on.

Little endian CPUs include Intel and DEC. Big endian CPUs include Motorola 680x0, Sun Sparc and IBM (e.g., PowerPC). MIPs and ARM can be configured either way.

The CPU endianness affects the CPU's instruction set. Different GNU C toolchains for compiling the C code ought to be used for CPUs of different endianness. For example, mips-linux-gcc and mipsel-linux-gcc are used to compile MIPs code for big endian and little endian, respectively.

The CPU endianness also has an impact on software programs if we need to access part of a multi-byte integer. The following program illustrates that situation. If one accesses the whole 32-bit integer, the CPU endianness is invisible to software programs.

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" );
Endianness of Bus

The bus we refer to here is the external bus we showed in the figure above. We use PCI as an example below. The bus, as we know, is an intermediary component that interconnects CPUs, devices and various other components on the system. The endianness of bus is a standard for byte/bit order that bus protocol defines and with which other components comply.

Take an example of the PCI bus known as little endian. It implies the following: among the 32 address/data bus line AD [31:0], it expects a 32-bit device and connects its most significant data line to AD31 and least significant data line to AD0. A big endian bus protocol would be the opposite.

For a partial word device connected to bus, for example, an 8-bit device, little endian bus-like PCI specifies that the eight data lines of the device be connected to AD[7:0]. For a big endian bus protocol, it would be connected to AD[24:31].

In addition, for PCI bus the protocol requires each PCI device to implement a configuration space. This is a set of configuration registers that have the same byte order as the bus.

Just as all the devices need to follow bus's rules regarding byte/bit endianness, so does the CPU. If a CPU operates in an endianness different from the bus, the bus controller/bridge usually is the place where the conversion is performed.

An alert reader nows ask this question, "so what happens if the endianness of the device is different from the endianness of the bus?" In this case, we need to do some extra work for communication to occur, which is covered in the next section.

Endianness of Devices

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.

We assume CPU and bus share the same endianness in the following discussion. If the endianness of a device is the same as that of CPU/bus, then no conversion is needed.

In the case of different endianness between the device and the CPU/bus, we offer two solutions here from a hardware wiring point of view. We assume CPU/bus is little endian and the device is big endian in the following discussion.

Word Consistent Approach

In this approach, we swap the entire 32-bit word of the device data line. We represent the data line of device as D[0:31], where D(0) stores the most significant bit, and bus line as AD[31:0]. This approach suggests wiring D(i) to AD(31-i), where i = 0, ..., 31. Word Consistent means the semantic of the whole word is preserved.

To illustrate, the following code represents a 32-bit descriptor register in a big endian NIC card:

After applying the Word Consistent swap (wiring D[0:31] to AD[31:0]) , the result in the CPU/bus is:

Notice that it automatically is little endian for CPU/bus. No software byte or bit swapping is needed.

The above example is for those simple cases where data does not cross a 32-bit memory boundary. Now, let's take a look at a case where it does. In the following code, vlan[0:24] has a value of 0xabcdef and crosses a 32-bit memory boundary.

After the Word Consistent swap, the result is:

Do you see what happened? The vlan field has been broken into two noncontiguous memory spaces: bytes[1:0] and byte(7). It violates Kevin's Theory #1, and we are not able to define a nice C structure to access the in-contiguous vlan fields.

Therefore, the Word Consistent solution works only for data within word boundaries and does not work for data that may cross a word boundary. The second approach solves this problem for us.

Byte Consistent Approach

In this approach, we do not swap bytes, but we do swap the bits within each byte lane (bit at device bit-offset i goes to bus bit-offset (7-i), where i=0...7) in hardware wiring. Byte Consistent means the semantic of the byte is preserved.

After applying this method, the big endian NIC device value in above results in this CPU/bus value:

Now, the three bytes of the vlan field are in contiguous memory space, and the content of each byte reads correctly. But this result still looks messy in byte order. However, because we now occupy a contiguous memory space, let the software do a byte swap for this 5-byte data structure. We get the following result:

We see that software byte swapping needs to be performed as the second procedure in this approach. Byte swapping is affordable in software, unlike bit swapping.

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.

Now that everything is sorted out nicely, we can define the C structure as the following to access the descriptor in the NIC:

struct nic_tag_reg {        uint64_t vlan:24 __attribute__((packed));        uint64_t rx  :6  __attribute__((packed));        uint64_t tag :10 __attribute__((packed));};
Endianness of Network Protocols

The endianness of network protocols defines the order in which the bits and bytes of an integer field of a network protocol header are sent and received. We also introduce a term called wire address here. A lower wire address bit or byte always is transmitted and received in front of a higher wire address bit or byte.

In fact, for network endianness, it is a little different than what we have seen so far. Another factor is in the picture: the bit transmission/reception order on the physical wire. Lower layer protocols, such as Ethernet, have specifications for bit transmission/reception order, and sometimes it can be the reverse of the upper layer protocol endianness. We look at this situation in our examples.

The endianness of NIC devices usually follow the endianness of the network protocols they support, so it could be different from the endianness of the CPU on the system. Most network protocols are big endian; here we take Ethernet and IP as examples.

Endianness of Ethernet

Ethernet is big endian. This means the most significant byte of an integer field is placed at a lower wire byte address and transmitted/received in front of the least significant byte. For example, the protocol field with a value of 0x0806(ARP) in the Ethernet header has a wire layout like this:

wire byte offset:     0       1hex             :    08      06

Notice that the MAC address field of the Ethernet header is considered as a string of characters, in which case the byte order does not matter. For example, a MAC address 12:34:56:78:9a:bc has a layout on the wire like that shown below, and byte 12 is transmitted first.

Bit Transmission/Reception Order

The bit transmission/reception order specifies how the bits within a byte are transmitted/received on the wire. For Ethernet, the order is from the least significant bit (lower wire address offset) to the most significant bit (higher wire address offset). This apparently is little endian. The byte order remains the same as big endian, as described in early section. Therefore, here we see the situation where the byte order and the bit transmission/reception order are the reverse.

The following is an illustration of Ethernet bit transmission/reception order:

We see from this that the group (multicast) bit, the least significant bit of the first byte, appeared as the first bit on the wire. Ethernet and 802.3 hardware behave consistently with the bit transmission/reception order above.

In this case, where the protocol byte order and the bit transmission/reception order are different, the NIC must convert the bit transmission/reception order from/to the host(CPU) bit order. By doing so, the upper layers do not have to worry about bit order and need only to sort out the byte order. In fact, this is another form of the Byte Consistent approach, where byte semantics are preserved when data travels across different endian domains.

The bit transmission/reception order generally is invisible to the CPU and software, but is important to hardware considerations such as the serdes (serializer/deserializer) of PHY and the wiring of NIC device data lines to the bus.

Parsing Ethernet Header in Software

For either endianness, the Ethernet header can be parsed by software with the C structure below:

struct ethhdr{        unsigned char   h_dest[ETH_ALEN];               unsigned char   h_source[ETH_ALEN];             unsigned short  h_proto;                };

The h_dest and h_source fields are byte arrays, so no conversion is needed. The h_proto field here is an integer, therefore a ntohs() is needed before the host accesses this field, and htons() is needed before the host fills up this field.

Endianness of IP

IP's byte order also is big endian. The bit endianness of IP inherits that of the CPU, and the NIC takes care of converting it from/to the bit transmission/reception order on the wire.

For big endian hosts, IP header fields can be accessed directly. For little endian hosts, which are most PCs in the world (x86), byte swap needs to be be performed in software for the integer fields in the IP header.

Below is the structure of iphdr from the Linux kernel. We use ntohs() before reading integer fields and htons() before writing them. Essentially, these two functions do nothing for big endian hosts and perform byte swapping for little endian hosts.

struct iphdr {#if defined(__LITTLE_ENDIAN_BITFIELD)        __u8    ihl:4,                version:4;#elif defined (__BIG_ENDIAN_BITFIELD)        __u8    version:4,                ihl:4;#else#error  "Please fix <asm/byteorder.h>"#endif        __u8    tos;        __u16   tot_len;        __u16   id;        __u16   frag_off;        __u8    ttl;        __u8    protocol;        __u16   check;        __u32   saddr;        __u32   daddr;        /*The options start here. */};

Take a look at some interesting fields in the IP header:

version and ihl fields: According to IP standard, version is the most significant four bits of the first byte of an IP header. ihl is the least significant four bits of the first byte of the IP header.

There are two methods to access these fields. Method 1 directly extracts them from the data. If ver_ihl holds the first byte of the IP header, then (ver_ihl & 0x0f) gives the ihl field and (ver_ihl > > 4) gives the version field. This applies for hosts with either endianness.

Method 2 is to define the structure as above, then access these fields from the structure itself. In the above structure, if the host is little endian, then we define ihl before version; if the host is big endian, we define version before ihl. If we apply Kevin's Theory #2 here that an earlier defined field always occupies a lower memory address, we find that the above definition in C structure fits the IP standard pretty well.

saddr and daddr fields: these two fields can be treated as either byte or integer arrays. If they are treated as byte arrays, there is no need to do endianness conversion. If they are treated as integers, then conversions need to be performed as needed. Below is a function with integer interpretation:

/*  dot2ip - convert a dotted decimal string into an  *           IP address  */uint32_t dot2ip(char *pdot){  uint32_t i,my_ip;  my_ip=0;  for (i=0; i<IP_ALEN; ++i) {    my_ip = my_ip*256+atoi(pdot);    if ((pdot = (char *) index(pdot, '.')) == NULL)        break;                     ++pdot;    }    return my_ip;}

And here is the function with byte array interpretation:

uint32_t dot2ip2(char *pdot){  int i;  uint8_t ip[IP_ALEN];  for (i=0; i<IP_ALEN; ++i) {    ip[i] = atoi(pdot);    if ((pdot = (char *) index(pdot, '.')) == NULL)        break;               ++pdot;  }  return *((uint32_t *)ip);}
Summary

The topic of byte and bit endianness can go even further than what we discussed here. Hopefully this article has covered the main aspects of it. See you next time in the maze.

Kevin Kaichuan He is a senior system software engineer at Solustek Corp. He currently is working on board bring-up, embedded Linux and networking stacks projects. His previous work experience includes being a software engineer at Cisco Systems and a research assistant in Computer Science at Purdue University. In his spare time, he enjoys digital photography, PS2 games and movies.

email: hek_u5@yahoo.com

 
Byte and Bit Order Dissection
 
作者:Kevin He2003-09-02
原文地址:http://www.linuxjournal.com/article/6788
 
译者:Love. Katherine,2007-04-14
译文地址:http://blog.csdn.net/lovekatherine/archive/2007/04/14/1564731.aspx
 
转载时务必以超链接形式标明文章原始出处及作者、译者信息。

 
讨论大端与小端、比特序与节序的区别,以及它们的作用范围
 
编辑提示:本文自最初发表后已
                           
做过修改
 
 
那些不得不和比特序、字节序问题打交道的软件或硬件工程师,都很清楚这过程就像是走迷宫。尽管通常我们都能走出迷宫,但是每次都要牺牲数量可观的脑细胞。本文试图概括需要处理比特序和字节序问题的领域,包括CPU、总线、硬件设备以及网络协议。我们将深入问题的细节,并希望能在这个问题上提供有价值的参考。本文同时还试图提供一些从实践中总结出的指导和拇指法则。
 
 
大小端
 
我们对"endianness"这个名词估计都很熟悉了。它首先被Danny Cohen于1980引入,用来表述计算机系统表示多字节整数的方式。
 
endianness分为两种:大端和小端。(从字节序的角度来看)大端方式是将整数中最高位byte存放在最低地址中。而小端方式则相反,将整数中的最高位byte存放在最高地址中。
 
对于某个确定的计算机系统,比特序通常与字节序保持一致。换言之,在大端系统中,每个byte中最高位bit存放在内存最低位;在小端系统中,最低位bit存放在内存最低位。
 
在设计计算机系统时,应该尽一切可能避免通过软件方式执行bit换位,因为这样不仅会产生巨大开销,也是件令程序员感到乏味的工作。后文将介绍如何通过硬件方式处理这一问题。
 
书写规则
 
正如大部分人是按照从左至右的顺序书写数字,一个多字节整数的内存布局也应该遵循同样的方式,即从左至右为数值的最高位至最低位。正如我们在下面的例子中所看到的,这是书写整数最清晰的方式。
 
根据上述规则,我们按以下方式分别在大端和小端系统中值为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 addr      0         1       2        3
bit  offset 01234567 01234567 01234567 01234567
     binary 10110000 00110000 11010000 01010000
 
正如你所看到的,这种方式下想要看出我们要表达的整数是件困难的事情。
 
本文中使用的简化计算机系统
 
在不失一般性的前提下,在本文中使用下图所描述的简化计算机系统:
 
 
 
CPU、内部总线和内存/Cache这些部件由于通常拥有相同的endianness,可以作为一个整体用CPU来代表。而对于总线endianness讨论,只涉及外部总线。CPU寄存器宽度、内存字宽和总线宽度在本文中被设定为32bits。
 
CPUendianness
 
 
CPU的endianness是指它在寄存器、内部总线、Cahce和内存中表示多字节整数时所采取的字节序和比特序。
 
小端的CPU包括Intel和DEC。大端CPU包括Motorola 680x0, Sun Sparc and IBM (如PowerPC)。MIPs and ARM可以设定为任选其一。
 
 
 
CPU的endianness影响着CPU的指令集。对于使用不同endianness的CPU,应该使用不同的GNU工具包来编译代码。例如,mips-linux-gcc和mipsel-linux-gcc分别用来编译生成运行于大端和小端模式的MIPS之上的代码。
 
如果我们(程序员)需要访问多字节整数的一部分时,也必须考虑CPU的endianness。以下的程序展示了该种情形。注意,在访问32-bit整数的整体时,CPU的endianness对于软件(程序员)是不可见的。
 
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" );
 
 
总线的Endianness
 
此处我们所谈论的总线是在上图中显示的外部总线。下文以PCI总线为例。正如我们所知,总线是联接CPU、外设以及其它各种设备的媒介部件。总线的endianness是由总线协议定义的、所有联接到其上的部件都必须遵守的比特/字节序标准。
 
以类型为小端的PCI总线为例:对于PCI的32位地址/数据线AD[31:0],要求所有联接到PCI上的32-bit设备将其最高位数据线联接到AD31,最低位数据线联接到AD0。类型为大端的总线协议则有相反的要求。
 
对于一个数据宽度不满总线宽度的设备,例如一个8-bit设备,小端的总线如PCI规定设备的8根数据线应联接到AD[7:0],而对于大端的总线协议,则要求联接到AD[24:31]。
 
此外,对于PCI,总线协议要求每个PCI设备实现可配置空间——即一组与总线具有相同字节序的可配置寄存器。
 
正如所有的设备都需要遵守(外部)总线所规定的比特/字节序标准,CPU也一样。如果CPU与(外部)总线工作于不同的endianness模式,那么总线控制器/桥通常是完成转换的部件。
 
一个机敏的读者现在会提出这样的疑问:“既然如此,如果设备的endianness模式与总线的endianness模式不匹配,会怎样?“ 在这种情况下,必须执行额外的转换工作才能进行信息传递,这将在下一节谈到。
 
 
设备的Endianness
 
Kevin定理#1: 当一个多字节数据单元在两个具有相反endianness系统之间传输时,需要执行转换以维护数据单元的内存空间连续性。
 
我们在下面的讨论中假设CPU和总线具备相同的endianness。如果设备的endianness与CPU/bus相同,那么不需要执行转换。
 
在设备与CPU/bus的endianness不同的情形下,从硬件接线的角度,我们在此提供两种解决方式。以下的讨论假设CPU/bus类型为小端,而设备类型为大端。
 
字一致方案
 
在该解决方案中,我们对整个32-bit的设备数据线进行变换。我们用D[0:31]表示设备的数据线,其中D[0]存放最高位,而对于总线用AD[31:0]表示。该方案建议将D[i]联到AD[31-i],其中i=0,...,31。字一致意味着整个(32-bit)字的语义得到了维护。
 
下图显示的是一个类型为大端的NIC card中的32-bit描述符寄存器。
 
 
 
 
在执行字一致交换后,在CPU/bus上的结果数据为:
 
 
 
注意,转化的结果自动符合CPU/bus的字节序和比特序要求,而不需要通过软件(程序员)进行字节或比特的交换。
 
上述例子是针对数据并未超过32-bit内存边界的简单情形。现在我们看一个穿越边界的例子。在下面的例子中,vlan[0:24]的值为0xabcdef,并且穿越了32-bit内存边界。
 
 
在字一致转化后,结果为:
 
 
看到这里发生了什么?转换后的vlan被分割为两个非连续的内存空间:bytes[1:0]和byte[7]。这违背了Kevin定理#1,而且我们无法定义一个结构良好的C结构来访问内存空间非连续的vlan。
 
 
因此,字一致方案只适用于数据位于字边界之内的情形,对于存在边界穿越的数据并不适用。第二种方案可解决该问题。
 
字节一致方案
 
在该方案中,我们不执行字节间的变换,但是我们还是要对每个字节中的比特通过硬件绕线进行变换(设备中偏移量为i的比特转换为bus中偏移量为7-i的比特,i=0...7)。字节一致意味着字节的语义得到了维护。
 
在应该了该方案后,上图所示大端NIC设备中的值转换后的结果为:
 
 
现在,vlan的三个字节位于连续的内存空间,并且每个字节的内容可以被正确读出。但是转换后的记过在字节序角度看来依然很乱。然而,由于我们现在拥有一块连续的内存空间,可以交给软件来完成图中5字节数据交换的任务。最终结果为:
 
 
我们看到,在这种解决方案中软件执行的字节交换作为第二阶段。字节交换是由软件完成的,这不同于比特交换。
 
Kevin定理#:2 在C中一个包含位域的结构中,如果位域A在位域B之前定义,那么位域A所占据的内存空间永远低于B所占用的内存空间。
 
现在一切都已经分类的井井有条,我们可以定义如下的C结构来访问NIC中的描述符:
 
struct nic_tag_reg {
        uint64_t vlan:24 __attribute__((packed));
        uint64_t rx :6 __attribute__((packed));
        uint64_t tag :10 __attribute__((packed));
};
 
 
网络协议的Endianness
 
网络协议的endianness定义了网络协议头部中整数域发送和传输时所遵循的比特序和字节序。我们在此还要引入一个概念:绕线地址。一个低绕线地址比特或字节在发送和接受时永远位于高绕线地址比特或字节之前。
 
实际上,对于网络endianness,它于我们之前所看到的endianness有些许不同。对于网络endianness,还存在另外一个影响因素:物理连线上比特的发送和接受顺序。底层协议,例如以太网,对于比特的传输和接受顺序有特定规定,有时这个规定是与上层协议的endianness相反的。我们将在下面的例子中考虑这种情形。
 
NIC设备的endianness通常遵循它们所支持的网络协议所使用的endianness类型,因此可能与系统中CPU的endianness不同。多数网络协议是大端的。此处我们以以太网和IP为例。
 
 
 
以太网的endianness
 
以太网是大端的。这意味着一个整数域的最高字节存放于低绕线地址,并且在接受和发送时位于最低字节之前。例如,以太网头部值为0x0806(ARP)协议域有如下的绕线布局:
 
wire byte offset:               0       1
hex             :           08      06
 
注意,以太网头部中MAC地址被视为字符串,因此不受字节序的影响。例如,MAC地址12:34:56:78:9a:bc有如下的绕线布局,并且值为12的字节被首先传输。
 
比特传输/接收序
 
比特传输/接受序规定了一个字节内的所有bit在物理线路中传输的顺序。对于以太网,顺序是由最不重要bit(低绕线地址)至最重要bit(高绕线地址)。这显然属于小端的类型。字节序仍保持为大端,如前所叙。因此,我们看到在这种情况下,字节序和比特传输/接收序是相反的。
 
下图展示了以太网的比特传输/接收序:
 
 
我们看到,MAC地址第一个字节中的最不重要bit,即组(多播)位,作为第一个bit出现在物理线路上。以太网和802.3硬件按照上述字节发传输/接受顺序一致性的工作。
 
 
在协议字节序与比特传输/接收序不同的情形下:NIC必须在传输时完成由主机(CPU)至比特序到以太网比特比特传输序的转换,而在接受时完成由以太网比特接受序至主机(CPU)比特序的转换。这样,上层协议就不用担心比特序而只需保证字节序的正确。实际上,这是另一种形式的字节一致转换方案,它保证了数据通过不同endianness时字节级语义的完整性。
 
 
比特传输/接受序通常对于CPU和软件是不可见的,但是对于硬件而言是个重要的问题,例如物理层的串并转化,NIC的数据线与总线的联接。
 
基于软件的以太网头部语法分析
 
对于任何类型的endianness,以太网头部可以用下面的C结构来完成软件的语法分析:
 
struct ethhdr
{
        unsigned char   h_dest[ETH_ALEN];      
        unsigned char   h_source[ETH_ALEN];    
        unsigned short h_proto;               
};
 
 
h_dest和h_source域是字节数组,因此不需要转换。h_proto域是整数,因此在主机访问该域前需调用ntohs(),而在填充该域前需调用htons()。
 
IPendianness
 
IP的字节序也为大端。而IP的比特序从CPU处继承,并由NIC负责其与物理传输线路中的比特传输/发送序进行转化。
 
对于大端主机,IP头部中的域可以被直接访问。对于小端主机(多数为基于x86的PC)需要对IP头部中的整数域进行字节变换才能进行访问和填充。
 
下面是Linux Kernel中定义的iphdr结构。我们在读取整数前调用ntohs(),在填写整数前调用htons()。本质上,这两个函数在大端主机上不执行任何操作,而在小端主机上执行字节变换。
 
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
#else
#error "Please fix <asm/byteorder.h>"
#endif
        __u8    tos;
        __u16   tot_len;
        __u16   id;
        __u16   frag_off;
        __u8    ttl;
        __u8    protocol;
        __u16   check;
        __u32   saddr;
        __u32   daddr;
        /*The options start here. */
};
 
 
我们来查看IP头部中一些有意思的域。
 
 
version and ihl :根据IP标准,IP头部第一个字节中的最高4bit表示IP协议的版本。ihl表示第一个字节低4位bit。
 
有两种方法可以用来访问这些域。方法1直接从数据中进行提取。假设ver_ihl存放着IP头部的第一个字节,那么(ver_ihl &0x0f)可得到ihl域,而(ver_ihl>>4)可得到verion域。这种方法对于任何一种endianness类型都适用。
 
方法二是定义上述的结构,然后通过结构来访问这些域。在上述结构中,如果主机为小端,那么我们定义ihl在version之前;如果主机为大端,我们定义version在ihl之后。如果我们在此应用Kevin 定理#2—— 一个先定义的的域永远占据低地址空间,我们可以发现以上的C结构定义很好的符合了IP标准。
 
saddr and daddr fields:这两个域可以被视为整数或字节数组。如果视为字节数组的话,没有必要进行转化。如果被视为整数,那么则需要转化,以下是一个基于整数解释的函数
 
/* dot2ip - convert a dotted decimal string into an
 *           IP address
 */
uint32_t dot2ip(char *pdot)
{
 uint32_t i,my_ip;
 my_ip=0;
 for (i=0; i<IP_ALEN; ++i) {
    my_ip = my_ip*256+atoi(pdot);
    if ((pdot = (char *) index(pdot, '.')) == NULL)
        break;            
        ++pdot;
    }
    return my_ip;
}
 
 
下面则是基于字节数组的函数:
 
uint32_t dot2ip2(char *pdot)
{
 int i;
 uint8_t ip[IP_ALEN];
 for (i=0; i<IP_ALEN; ++i) {
    ip[i] = atoi(pdot);
       if ((pdot = (char *) index(pdot, '.')) == NULL)
        break;         
     ++pdot;
 }
 return *((uint32_t *)ip);
}
 
总结
 
本文所讨论的关于字节序和比特序的问题还可以进一步深入 。希望本文已经介绍了该问题的主要方面。迷宫里下次见吧。
 
Kevin Kaichuan He 是一名Solustek Corp的高级软件工程师。他目前的工作致力于borad bring-up、嵌入式Linux和网络协议栈工程。他之前曾是Cisco公司的软件工程师、Purdue大学计算机系的助教。业余时间,他喜欢数码摄像,PS2游戏和电影。
 
原创粉丝点击