移植性问题包含的内容很广泛,本节要介绍的是代码在不同体系结构下的移植问题。Linux内核是高度可移植的,若希望在不同平台下开发的应用程序或设备驱动程序也能很好地兼容,这就要求用户在开发的过程中要充分考虑与移植相关的内容。本文将讨论数据类型、数据对齐,以及与字节顺序相关的移植性问题。

1 字长和数据类型

不同的体系结构具有不同的字长,表1列出了一些常见体系结构的字长。

表1 表示例

体 系 结 构

字     长

alpha

64

ia64

64

mips64

64

powerpc64

64

sparc64

64

x86_64

64

um

32/64

s390

32/64

arm

32

体 系 结 构

字     长

h8300

32

i386

32

m32r

32

m68r

32

mips

32

powerpc

32

v850

32

因此在不同体系结构下C语言数据类型的大小不是相同的,用户必须安排特定大小的数据项,才能更好地进行移植。

在编写程序代码之前,有必要了解各种常用数据类型的长度,比如说char、short、int和long的长度。

● char类型的长度被定义为8字节。

● short类型的长度被定义为至少2字节。因此在有些计算机上,对于某些编译器,short类型的长度可能就是4个字节,甚至更长。

● int类型是一个整数的“自然”大小,其长度至少为2字节,并且至少要和short类型一样长。在16位计算机上,int类型的长度可能为2字节;在32位计算机上,可能为4字节;当64位计算机流行起来后,int类型的长度可能会达到8字节。例如,早期的Motorala 68000是一种16/32位的混合型计算机,依赖于不同的命令行选项,一个68000编译程序能产生两个字节长或4字节长的int类型。

● long类型至少和int类型一样长(因此,它也至少和short类型一样长)。long类型的长度至少为4字节。32位计算机上的编译程序可能会使short、int和long类型的长度都为4字节。

如果需要一个4字节长的整型变量,不能想当然地以为int或long类型能满足要求,而要用sizeof()来检测int和 long的长度;再根据检测的结果,使用typedef把一种固有的类型定义为用户所需要长度的类型,并在其前后加上相应的#ifdef指令。

#ifdef four_Byte_long

typedef long int4;

#endif

Linux内核,在/usr/src/linux/include/asm/types.h文件中也定义了一些长度确定的数据类型。

typedef unsigned short umode_t;

typedef __signed__ char __s8;           //带符号字节

typedef unsigned char __u8;            //无符号字节

typedef __signed__ short __s16;             //带符号16位整型

typedef unsigned short __u16;           //无符号16位整型

typedef __signed__ int __s32;           //带符号32位整型

typedef unsigned int __u32;                 //无符号32位整型

#if defined(__GNUC__) && !defined(__STRICT_ANSI__)

typedef __signed__ long long __s64;         //带符号64位整型

typedef unsigned long long __u64;       //无符号64位整型

#endif

2 数据对齐

编写可移植代码而值得考虑的一个问题是如何存取不对齐的数据。例如,如何读取存储在一个不是 4 字节倍数的地址的4字节值。i386用户常常存取不对齐数据项,必须注意,并不是所有的体系结构都允许。很多现代的体系会在发生上述事件时产生一个异常,每次程序试图进行不对齐数据传送时,数据传输由异常处理来处理,带来很大的性能牺牲。如果用户需要存取不对齐的数据,应当使用下列宏:

#include <asm/unaligned.h>

get_unaligned(ptr);

put_unaligned(val, ptr);

这些宏是无类型的,并且用在每个数据项,不管它是1字节、2字节、4字节或者8字节。

进行不对齐的数据操作严重影响系统的性能,虽然在现代的系统里采用异常处理机制,但不是通用的方法,比如在sparc或者MIPS上发生不对齐数据操作时就发生总线错误。

不对齐虽然能节省内存空间,但是不适合移植性编程,为了编写的程序可以跨平台移植,必须使用数据项对齐。

在数据对齐的处理上,编译器的作用也需要注意,同样的数据结构可能在不同的平台上进行不同的编译。编译器可能根据各平台不同的规则来安排结构的成员对齐。因此数据对齐不仅依赖处理器架构,也依赖于编译器的具体操作。

下面来分析一个数据对齐的例子。

结构体定义如下:

struct A

{

int x;

char y;

short z;

};

struct B

{

char y;

int x;

short z;

};

硬件平台:32bit,x86,GCC编译器。

先熟悉硬件平台上各数据类型的数据长度。

char:1B

short:2B

int:4B

long:4B

float:4B

double:8B

对结构体A、B分别使用sizeof()函数求长度,结果是:

● sizeof( struct A)得到的值是8;

● sizeof( struct A)得到的值是12。

从表面上看,结构体A和结构体B应该具有相同的长度。之所以发生上面的变化是因为编译器的作用,编译器默认对数据成员在空间上进行了对齐。用户也可以更改编译器的默认设置。

使用指定对齐值来修改#pragma pack (value)的指定对齐值value。

例如:

#pragma pack( 2 )

//指定下列数据按照两个字节对齐

struct C

{

char y;

int x;

short z;

};

//取消指定对齐,恢复默认方式对齐

#pragma pack()

此时sizeof( struct C )的值为8。

选择#pragma pack (value)中的value值为1。

#pragma pack( 1 )

//指定下列数据按照两个字节对齐

struct E

{

char y;

int x;

short z;

};

//取消指定对齐,恢复默认方式对齐

#pragma pack()

此时sizeof( struct E )的值为7。

3 字节顺序

在将应用程序从一种架构类型迁移至另一种架构类型的过程中,经常会遇到字节排列顺序(endianness)问题。字节排列顺序是数据元素及其单个字节在内存中存储和表示时的顺序。有两类字节排列顺序:大端(big-endian)和小端(little-endian)。

对于 big-endian 处理器,如 POWER、PowerPC 和 SPARC,在将字节放到内存中时,是从最低位地址开始的,首先放入最重要的字节。另一方面,对于 little-endian 处理器,如 Intel 和 Alpha 处理器,首先放入的是最不重要的字节。像ARM处理器既有大端模式也有小端模式,在使用的时候要先确定模式。如图1所示说明了32位在大端和小端模式下的字节顺序。

                                      

Big-endian   

Little_endian

图1   大端、小端中的字节顺序

如何获得一个平台的大端和小端信息,下面给出了使用指针方法的C描述:

int x = 1;

if ( *(char *) & x == 1)

printf( " little-endian \n" );

else

printf( " big-endian \n" );

出现字节顺序问题的原因是不一致的数据引用。它经常表现为数据元素转换使用联合数据结构或使用和操作位域导致数据类型不匹配。因此在进行操作的时候,要了解平台的字节顺序属性。

4 嵌入式Linux中代码移植实例

本节将通过一个基于移植编写的程序来复习移植性的问题。

数据对齐操作,依赖硬件平台。下面这个例子就和硬件平台有关系。

ssize_t ReadData( int fd, char * buf, size_t size)

{

int n;

int datalen;

n = readn(fd, buf, sizeof( int ) ); //读取数据

if( n <= 0) return n;

datalen = ntohl ( *((int *) buf )); //show error

if( datalen > size) return -2;

n = readn( fd, buf, datalen);

if( n > 0)

{

    *(buf + n) = ‘ \0 ’;

}

return n;

}

在INTEL Xeon芯片的fedora 7运行正常。移植到Linux ARM开发板上运行,在show error处报告错误。根据报告的错误发现是总线错误,把错误定位于数据对齐方面,对代码进行排查。修改后的代码如下:

ssize_t ReadData( int fd, char * buf, size_t size)

{

int n;

int datalen, tmp;

n = readn(fd, buf, sizeof( int ) ); //读取数据

if( n <= 0) return n;

memcpy( &tmp, buf, sizeof( int ));

datalen = ntohl( tmp);

if( datalen > size) return -2;

n = readn( fd, buf, datalen);

if( n > 0)

{

   *(buf + n) = ' \0 ';

}

return n;

}

再次进行编译。通过这个例子发现,在进行跨平台编写程序的时候,要十分细致。并对各种可能出现的问题进行排查。才能编写出适用平台移植的代码。

Logo

更多推荐