将 Linux on x86 应用程序移植到 Linux on Power 的指南(2)
- UID
- 1066743
|
将 Linux on x86 应用程序移植到 Linux on Power 的指南(2)
处理字节顺序本节将介绍如何识别代码中依赖于字节顺序的区域,并将它们转换为正确的字节顺序格式的方法。
依赖于字节顺序的代码
数据引用上的非一致性是 C 语言的优势,这使得它在编程系统级软件(包括操作系统和设备驱动程序)中变得很流行。此优势包括类型转换、指针操作、union、位字段、结构和灵活的类型检查。但是,同样是这些特性,它们也可能是字节顺序可移植性问题的来源。作为示例,请考虑以下两个代码清单:
清单 1. 使用指针的非一致数据引用
1
2
3
4
5
6
7
8
9
10
| #include <stdio.h>
int main(void) {
int val;
unsigned char *ptr;
ptr = (char*) &val;
val = 0x89ABCDEF; /* four bytes constant */
printf("%X.%X.%X.%X\n", ptr[0], ptr[1], ptr[2], ptr[3]);
exit(0);
}
|
清单 2. 使用 union 的非一致数据引用
1
2
3
4
5
6
7
8
9
10
11
| #include <stdio.h>
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 处理器的系统上,结果为:
字节顺序问题表现为 val,它从最重要的字节开始逐个字节地读取数据。
如何确定系统的字节顺序
在 Linux 中,GNU_C 预处理器通常会自动提供一组常用的预定义宏,它们的名称以双下划线开始和结束。一组对确定使用的系统的字节顺序最有用的宏是 __BYTE_ORDER__、__ORDER_LITTLE_ENDIAN__ 和 __ORDER_BIG_ENDIAN__。
1
2
3
| /* Test for a big-endian machine */
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
/* Do big endian processing */
|
编写字节顺序中立的代码
如果程序模块在跨具有不同字节顺序的平台移植时能够保留其功能,那么它就是字节顺序中立的。换句话说,它的功能与运行它的平台的字节顺序无关。以下是一些编写字节顺序中立的代码的建议:
- 使用宏和指令
要让代码变得可移植,可以使用宏和条件编译指令,如清单 3 和清单 4 所示。 清单 3. 使用指令让字节顺序效果中立化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| #include <stdio.h>
#define BIG_ENDIAN 1
#define LITTLE_ENDIAN 0
#define BYTE_ORDER (( htonl(1)==1) ) // returns 1 or 0 depending on platform
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);
}
|
清单 4. 使用宏交换字节(对在运行时确定字节顺序很有用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| // Useful Endian Neutral Macros
#include <endian.h>
#if __BYTE_ORDER == __BIG_ENDIAN
// No translation needed for big endian system
#define sw2Bytes(val) val
#define sw4Bytes(val) val
#define sw8Bytes(val) val
#else
// Little Endian:Translate
// Swap 2 byte, 16 bit values:
#define sw2Bytes(val) \
( (((val) >> 8) & 0x00FF) | (((val) << 8) & 0xFF00) )
// Swap 4 byte, 32 bit values:
#define sw4Bytes(val) \
( (((val) >> 24) & 0x000000FF) | (((val) >> 8) & 0x0000FF00) | \
(((val) << 8) & 0x00FF0000) | (((val) << 24) & 0xFF000000) )
// Swap 8 byte, 64 bit values:
#define sw8Bytes(val) \
( (((val) >> 56) & 0x00000000000000FF) | (((val) >> 40) & 0x000000000000FF00) | \
(((val) >> 24) & 0x0000000000FF0000) | (((val) >> 8) & 0x00000000FF000000) | \
(((val) << 8) & 0x000000FF00000000) | (((val) << 24) & 0x0000FF0000000000) | \
(((val) << 40) & 0x00FF000000000000) | (((val) << 56) & 0xFF00000000000000) )
#endif
|
1
2
3
4
5
| int main(void) {
int a=0x11121314;
int b;
b = sw4Bytes(a); // b is 0x12 in LE and BE
}
|
- 使用编译时选项
实现此目的的另一种方式是在编译器命令行上将 BYTE_ORDER 的值定义为 -DBYTE_ORDER=BIG_ENDIAN。在一个具有不同字节顺序的新平台上编译时,这会消除编辑设备驱动程序或应用程序中的每个文件的需求。只需编译用于构建该驱动程序或应用程序的 makefile。 字节顺序:特定于 Linux
事实证明,Linux 内核提供了一组特定的系统宏,它们可以从低位优先到高位优先和从高位优先到低位优先执行 16、32 和 64 位交换。这些宏对从 Linux x86 移植到 Linux on Power 以及让您大代码对字节顺序中立都很方便,无需担忧会在代码中编写大量 #ifdef __LITTLE_ENDIAN 条件指令。
例如:
cpu_to_le16(u16); // converts CPU endianness 4 bytes to little-endian
le16_to_cpu(u16); // converts little-endian 4 bytes to CPU endianness
这两个宏将一个值从 CPU 使用的字节顺序转换为一种无符号、低位优先、32 位数字,或者执行反向转换。例如,在 Linux on Power 系统上,le16_to_cpu(u16) 将低位优先转换为高位优先,而 cpu_to_le16(u16) 将高位优先转换为低位优先。根据使用的宏和运行的是哪个系统,如果没有工作要做,那么它们将会返回原始值。
这些宏和其他许多宏位于:/usr/include/linux/byteorder/big_endian.h> 和 /usr/include/linux/byteorder/little_endian.h 中。
您可以查看这些头文件来找到您需要的宏。您还可以根据名称中的模式推断出它们的用途。
您可以使用预定义的 GNU_C 预处理器指令,轻松地包含正确的 include 文件,如下所示:
1
2
3
4
5
6
7
8
9
| #include <endian.h>
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#include <linux/byteorder/big_endian.h>
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#include <linux/byteorder/little_endian.h>
#else
// …user defined endian header
#endif
|
|
|
|
|
|
|