代码
在/usr/include/uapi/linux/ip.h和/usr/include/uapi/linux/tcp.h中分别有如下代码定义了ip以及tcp头部
iphdr:
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;
__be16 tot_len;
__be16 id;
__be16 frag_off;
__u8 ttl;
__u8 protocol;
__sum16 check;
__be32 saddr;
__be32 daddr;
/*The options start here. */
};
tcphdr:
struct tcphdr {
__u16 source;
__u16 dest;
__u32 seq;
__u32 ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u16 res1:4,
doff:4,
fin:1,
syn:1,
rst:1,
psh:1,
ack:1,
urg:1,
ece:1,
cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:4,
res1:4,
cwr:1,
ece:1,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
fin:1;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
__u16 window;
__u16 check;
__u16 urg_ptr;
};
这其中:4等是C语言中的位域,表示取二进制中的低四位(在大端序中这个低四位是存储在高地址的)。
疑问
初读这些代码,我还并不理解为何有大小端的区别,"big endian"和"little endian"的区别是在按字节之间的存储顺序上。比如0x12345678
在"little endian"上表示为(假设基址为0x100):
0x100 0x78 (01111000)
0x101 0x56
0x102 0x34
0x103 0x12
在“big endian"上表示为:
0x100 0x12
0x101 0x34
0x102 0x56
0x103 0x78 (01111000)
显然对单个字节来说,其中的位存储还是相同的,所以我并不理解为何在Linux的代码中,要把它们一些字节的位域定义成相反的顺序。
解答
经过我搜集资料后终于找到了答案:
譬如ip头部ip_hdr,假设仅仅是直接对ihl或者version进行操作,确实无需分辨大小端字节序,Linux代码中那样写反而有些多此一举。
但使用者可能使用memcpy来直接对这开头的8位进行赋值操作,而这在大端序和小端序的机器上会产生不同的情况。
举个经典的例子,比如说下述代码:
u_int16_t t = 0x1;
u_int8_t x[2];
memcpy(x, t);
在小端序的机器上结果应该是
x[0] x[1]
10 … 00
而在大端序的机器上执行结果会变为:
x[0] x[1]
00 … 01
注:x[1]的地址都是比x[0]高的。
因此为了提高兼容性,使程序能够被小端序和大端序的机器共用。须要预先推断是大端序还是小端序。并调换ihr和version在内存中的位置。