HTML code本文介绍POWER和Intel x86平台间的字节序差异,以及在程序移植中,如何注意,评估字序问题
字节序(Endianness)
字节序是一个比较有意思的现象,简单说就是处理器按照什么存储格式处理、存储数据。
处理器架构的不同,对字节序的处理相应的存在差异,本文介绍POWER处理器和x86处理
器两种不同架构的处理器的字节处理方式。
字节序问题,是程序开发人员经常遇到的,如在程序移植中,驱动程序开发中,不同架
构处理器平台的数据迁移,等等。
字节序有两种,一种是big endian,中文通常译为大印地安序或者大尾,另一种为little
endian,译为小印地安序或者小尾。本文中,统一用其英文名称,不再翻译为中文。
Big endian 和 little endian
字节序反映了一个数据元素及其组成字节在内存中是如何存储和定位的。在一个多位数
中,高位的数字称之为更有效位或更高位(more significant),同样的,在一个多字节
的数中,某一字节的值越大,其字位就越有效,或更高,例如数字0x89ABCDEF,有四个字
节组成,分别为0x89, 0xAB, 0xCD, 0xEF, 相应的算术值为0x89000000,0xAB0000, 0xCD00
和0xEF,很明显,0x89的值最大,称之为数字0x89ABCDEF的最高位,而0xEF为最低位。数字在
内存中的存放,从低位起,有两种存放方式,分别为:
1) 先存放最低位(little endian)
2) 先存放最高位(big endian)
下图表明不同的处理器是如何在内存中,按照big endian和little endian存放32位十六进制
数的,如0x89ABCDEF:
图一. Big endian和little endian存放十六进制数的格式
0x89是最高位字节,0xEF为最低位字节,在big endian字序的系统中,最高位字节存放在内存
的最低位址,在little endian字序的系统中,最低位字节存放在内存的最低位址。对应用程序
而言,写一个字到内存中,然后再从同一个地址读取,字节序并不是太大的问题。但是,如果存
储的字在写的同时,某一个应用读取同一地址的字,应用读取到的值可能会不同,取决于处理器
采用的字节序是big endian还是little endian。
在POWER和Intel x86两种不同的处理器架构中,POWER处理器平台系统使用big endian字节序
存储数据,而x86处理器的系统使用little endian存储数据。从x86平台移植到POWER平台的应
用,很重要的一件事就是定位于字序相关的代码,并转换位big endian格式。
字节序处理
这部分内容介绍如何定位字序相关的代码,并介绍采用何种方式转换字序格式。使用的编程语
言为C,灵活、强大、高效的数据处理能力是C编程语言的一大特点,在许多系统级的应用软件
编写中,C编程语言是最优先的选择,如操作系统和设备驱动程序的开发。这一强大的功能,
同样包含构造新的类型,指针的使用、联合、结构和数据位处理等,这正是处理字节序最需要
的,举例如下:
示例一:使用指针处理数据
#include
#include
int main(void) {
int val;
unsigned char *ptr;
ptr = (char*) &val; /* 指针ptr指向val的内存地址*/
val = 0x89ABCDEF; /* 四字节的常量 */
printf("%X.%X.%X.%X\n", ptr[0], ptr[1], ptr[2], ptr[3]);
exit(0);
}
示例二:使用联合处理数据
#include
#include
union {
int val;
unsigned char c[sizeof(int)];
}u;
int main(void) {
u.val = 0x89ABCDEF; /* four bytes constant */
printf("%X.%X.%X.%X\n", u.c[0], u.c[1], u.c[2], u.c[3]);
exit(0);
}
分别在x86和POWER平台编译、链接并运行以上程序,得到如下结果:
l x86平台
EF.CD.AB.89
l POWER平台
89.AB.CD.EF
以上的测试用例,反映了比较典型的自序问题。从系统的层次来看,POWER处理器系统只支持
big endian格式的字节序,但也应当知道,在微通道和PCI侧面,却使用持little endian格
式的字节序,POWER系统中,在I/O总线和系统总线间I/O控制器起到桥梁的作用,负责格式的
转换。I/O控制器将数据流看作是字节流,单子节数据直接传输,对多字节数据则兑换字节的
高低位后再传输。
IO相关的应用,如TCP/IP协议的处理,同样会有字节序的处理,TCP/IP协议指定其数据格式
为big endian,基于x86的应用首先转换TCP/IP的数据为little endian格式。实际上,POSIX
提供相应的操作函数,这些函数是htonl(), ntohl(), htons()和ntohs()。
如何编写字节序无关的代码
如果应用在不同字节序的平台间仍可以保持其功能的一致性,可以认为应用是字节序无关的。
换句话说,应用的功能和运行平台的字节序无关,怎样实现这些应用代码呢?给出如下的建议:
1) 使用宏和条件编译
应用代码的编写中,充分使用宏和条件编译,可以明显提高应用的可以执行。
示例一:使用条件编译界定字节序
#include
#define BIG_ENDIAN 0
#define LITTLE_ENDIAN 1
#define BYTE_ORDER BIG_ENDIAN
union {
int val;
unsigned char c[sizeof(int)];
}u;
int main(void) {
u.val = 0x89ABCDEF;
#if (BYTE_ORDER == BIG_ENDIAN)
printf("%X.%X.%X.%X\n", u.c[0], u.c[1], u.c[2], u.c[3]);
#else /*! BYTE_ORDER == BIG_ENDIAN*/
printf("%X.%X.%X.%X\n", u.c[3], u.c[2], u.c[1], u.c[0]);
#endif /*BYTE_ORDER == BIG_ENDIAN*/
exit(0);
}
示例二:使用宏提取四字节的整形数的16-23位字节
#define INTB16TO23(a) ((a>>16) & 0xff)
int main(void) {
int a=0x11121314;
int b;
b = INTB16TO23(a); // b 的值为 0x12
}
2) 使用编译选项
使用编译器提供的相应的编译选项,如BYTE_ORDER,可以更方便的解决字节序的问题,而不用
修改程序代码,只需要修改makefile即可,已使用big endian字节序为例,增加-DBYTE_ORDER=BIG_ENDIAN,
重新在新的平台编译链接应用即可。
3) 测试内存的分布
假定1为little endian字节序,0为big endian字节序。在以下的示例中,首先测试多字节
整型数的第一个字节,如果为1,则按照little endian的格式处理数据,如为0,则按照big endian
的格式处理数据。采用这种方式的缺点是每一次的数据操作到需要检测,对应用而言,增加
了额外的操作,相应的会造成部分性能的损失。
示例:运行中判断字节序的类型
#include
#include
const int endian = 1;
#define is_bigendian() ( (*(char*)&endian) == 0 )
union {
int val;
unsigned char c[sizeof(int)];
}u;
int main(void) {
u.val = 0x89ABCDEF;
if (is_bigendian()) {
printf("%X.%X.%X.%X\n", u.c[0], u.c[1], u.c[2], u.c[3]);
}
else {
printf("%X.%X.%X.%X\n", u.c[3], u.c[2], u.c[1], u.c[0]);
}
exit(0);
}