可移植的操作系统尽可能少的涉及与机器相关的代码,为了支持不同的体系结构,界面和功能在定义时尽最大可能的有普遍性和抽象性。一个一致性非常高而本身有比较简单的操作系统在支持新的体系结构时,可能只需修改很少量的代码,但是可能会存在与体系结构相关的功能无法使用的现象。Linux在可移植性方面使用了折中的路线,差不多所有的接口和核心代码都是独立于硬件结构的C语言代码,但对于性能要求严格的部分,内核的特性会根据不同的硬件体系进行调整。
可移植性的另一个关键问题就是数据类型,本书对于系统结构在可移植性方面分析的较少,相对更多的注意力放在了这里。
对于支持的每一种体系结构,Linux都要将<asm/types.h>中的BITS_PER_LONG定义为Clong类型的长度,也就是系统字长,下面是linux的系统字长对照表。
Table 19.1 Supported Linux Architectures
Architecture Description Word Size
alpha Digital Alpha 64 bits
arm ARM and StrongARM 32 bits
avr AVR 32 bits
blackfin Blackfin 32 bits
cris CRIS 32 bits
frv FR-V 32 bits
h8300 H8/300 32 bits
ia64 IA-64 64 bits
m32r M32xxx 32 bits
m68k Motorola 68k 32 bits
m68knommu m68k without MMU 32bits
mips MIPS 32 and 64 bits
parisc HP PA-RISC 32 and 64bits
powerpc PowerPC 32 and 64 bits
s390 IBM S/390 32 and 64 bits
Sh Hitachi SH 32 bits
Sparc SPARC 32 and 64 bits
xtensa Xtensa 32 bits
牢记下面的规则:
-
ANSI C标准规定,一个char的长度一定是1个字节;
-
尽管没有规定int类型的长度是32bit,但是linux当前所有支持的体系结构中,它都是32位的;
-
short类型也类似,在当前所有支持的体系结构中,虽然没有明确规定,但都是16bit的;
-
绝对不应该假定指针和long的长度,在Linux当前支持的体系结构中,他们可以在32bit和64bit间变换;
-
由于不同的体系结构的long长度不同,不应该假设sizeof(int) = sizeof(long );
-
类似的,不要假设指针和int长度相等;
不透明类型隐藏着他的内部格式或结构,开发者利用typedef声明一个类型,把它叫做不透明类型。
处理不透明类型是的原则:
-
不要假设类型的长度。这些类型在某些系统中可能是32bit,而在其他系统中又可能是64bit,并且,内核开发者可以任意修改这些类型的大小;
-
不要将该类型转化回对应的C标准类型使用;
-
编程时要保证在该类型实际存储空间和格式发生变化时代码不受影响;
char型的符号问题
大部分体系结构上,默认char是带符号的,它可以从-128到127之间取值。但是也有例外,比如ARM结构,char就是不带符号的,他的取值范围是0-255.
因此,如果在自己的代码中使用了char型,要保证带符号和不带符号的情况下代码都没问题。如果能明确使用的哪一种,就直接声明它。
数据对齐
对齐是跟数据块在内存中的位置相关的话题,如果一个变量的内存地址正好是他的长度的整数倍,他就称作是自然对齐。
编译器会同过让所有的数据自然对齐来避免引发对齐问题。实际上,内核开发者在对齐上不用话费太多心思。
但了解一些也终归是好的嘛。
举个例子,将一个指向char型的指针当作指向unsignedlong型的指针用,会引起问题,因为此时会试图从一个并不能被4或8整除的内存地址上载入32或64位的unsignedlong型数据。
非标准C数据类型按照下面原则对齐:
-
对于数组,只要按照基本数据类型进行对齐就可以,随后的所有元素自然能够对齐;
-
对于联合体,只要它包含的长度的最大数据类型能够对齐就可以;
-
对于结构体,只要结构体每一个元素能够正确对齐就可以;
结构体的填补
为了保证每一个成员能够自然对齐,结构体要被填补。
例:
struct animal_struct {
char dog; /* 1 byte */
unsigned long cat; /* 4 bytes */
unsigned short pig; /* 2 bytes */
char fox; /* 1 byte */
};
在编译时为了满足各成员自然对齐,它在内存中不是按原样存放的。编译后:
struct animal_struct {
char dog; /*1 byte */
u8 __pad0[3]; /*3 byte */
unsigned long cat; /*4 byte */
unsigned short pig; /*2 byte */
char fox; /*1 byte */
u8 __pad1; /*1 byte */
};
note:在大部分32bit系统上,对于任何这样一个结构体,sizeof(animal_struct)返回都是12.
当然可以通过重新排列结构体来避免填充:
struct animal_struct {
unsigned long cat; /*4 byte */
unsigned short pig; /*3 byte */
char dog; /*1 byte */
char fox; /*1 byte */
};
但是不是任何时候都可以对结构体进行调整,如果结构体是某个标准的一部分(比如USB),或是它是现有代码,它的成员次序就已经定死。
注意,ANSIC明确规定不允许编译器改变结构体内成员对象的次序—他是有程序员决定的。
字节顺序
如果最高有效位所在的字节放在低字节位置上,其他字节依次放在高字节位置上,那么该字节顺序称作高位优先(big-endian)。如果最低有效位所在的字节放在高字节位置上,其他字节依次存放在低字节位置上,那么就称做低位优先(little-endian)。
举个例子,如何判断是大端还是小端:
int x = 1;
if (*(char *)&x == 1)
/* little endian */
else
/* big endian */
对于Linux支持的每一种体系结构,相应的内核都会根据机器使用的字节顺序在他的<asm/byteorder.h>中定义__BIG_ENDIAN或__LITTLE_ENDIAN中的一个。
这个头文件还从include/linux/byteorder/中包含了一组宏命令用于完成字节顺序之间的相互转换,命令包括:
u23 __cpu_to_be32(u32); /*convertcpu’s byte order to big-endian */
u32 __cpu_to_le32(u32); /*convertcpu’s byte order to little-endian */
u32 __be32_to_cpu(u32); /*convertbig-endian to cpu’s byte order */
u32 __le32_to_cpus(u32); /*convertlittle-endian to cpu’s byte order */
时间的计算
正确计量时间应该使用HZ,而不是jiffies。HZ定义在<asm/param.h>
最后一点就是关于页长度的问题,处理页管理内存时,绝对不要假设页的长度,不同体系结构的页长度见下表:
Table 19.4Architecture Page Size(s)
Architecture PAGE_SHIFT PAGE_SIZE
alpha 13 8KB
arm 12, 14, 15 4KB, 16KB, 32KB
avr 12 4KB
cris 13 8KB
blackfin 12 4KB
frv 14 16KB
h8300 12 4KB
12, 13, 14, 16 4KB, 8KB, 16KB,64KB
m32r 12 4KB
m68k 12, 13 4KB, 8KB
m68knommu 12 4KB
mips 12 4KB
mn10300 12 4KB
parisc 12 4KB
powerpc 12 4KB
s390 12 4KB
sh 12 4KB
sparc 12, 13 4KB, 8KB
um 12 4KB
x86 12 4KB
xtensa 12 4KB