其他分享
首页 > 其他分享> > 第十九章 可移植性

第十九章 可移植性

作者:互联网

1.简介      Linux是一个可移植性非常好的操作系统,它广泛支持了许多不同体系结构的计箅机。可移植性是指代码从一种体系结构移植到另外一种不同的体系结构上的方便程度。我们都知道Linux是可移植的,因为它巳经能够在各种不同的体系结构上运行了。      Linux在可移植性这个方面走的是中间路线。差不多所有的接口和核心代码都是独立于硬件体系结构的C语言代码。但是,在对性能要求很严格的部分,内核的特性会根据不同的硬件体系进行调整。举例来说,需要快速执行和底层的代码都是与硬件押关并且是用汇编语言写成的。这种实现方式使Limix在保持可移植性的同时兼顾对性能的优化。当可移植性妨碍性能发挥的时候,往往性能会被优先考虑。除此之外,代码就一定要保证可移植性。       调度程序就是一个好例子。调度程序的主体程序存放在kenid/sched.c文件中,用C语言编写的, 与体系结构无关。可是,调度程序需要进行的一些工作,比如说切换处理器上下文和切换地址空间等,却不得不依靠相应的体系结构完成。于是,内核用C语言编写了函数context—switch()用于实现进程切换,而在它的内部,会调用switch_to()和switch_mm()分别完成处理器上下文和地址空间的切换。        与体系结构相关的代码都存放在arch/architecture/和include/asm-architecture/目录中, architecture是Linux支持的体系结构的简称。



2.linux的可移植性     当前的2.6内核把这个数字进一步提高到了20,有不含MMU的Motorola 68k、 M32xxx、 H8/300, IBMPOWR、 v850、 x86-64,甚至还提供了用户模式(Usermode) Linux——个在Linux虚拟机上运行的内核版本。对64位S390的支持和32位S390的支持放在了一起,移去了重复之处。

       值得注意的是,每一种体系结构本身就可以支持不同的芯片和机型。像被支的ARM和PowerPC等体系结构,它们就可以支持很多不同的芯片和机型。所以说,尽管Linux移植到了20中基本体系结构上,但实际上可以运行它的机器的数目要大得多。



3.字长和数据类型

     能够由机器一次就完成处理的数据被称为字。这和我们在文档中用字符(8位)和页(许多字, 通常是4K或8K)来计量数据是相似的。

       处理器通用寄存器(GPR's)的大小和它的字长是相同的。一般来说,对于一个的体系结来说,它各个部件的宽度——比如说内存总线——最少要和它的字长一样大。而一般来说,地址空间的大小也等于字长,至少Linux支持的体系结构中都是这样的。此外,C语言定义的long类型总对等于机器的字长,而int类型有时会比字长小。

      对于支持的每一种体系结构,Linux都要将<asm/types.h>中的BITS_PER_LONG定义为C long 类型的长度,也就是系统的字长。

       牢记下述准则:

         • ANSI C标准规定,一个char的长度一定是8位。

         尽管没有规定int类型的长度是32位,但在Linux当前所有支持的体系结构中,它都是32的。

         •short类型也类似,在当前所有支持的体系结构中,虽然没有明文规定,但是它都是16位的

         •决不应该假定指针和long的长度,在Linux当前支持的体系结构中,它们就可以在32位和64位中变化。

        •由于不同的体系结构long的长度不同,决不应该假设sizeof( int) == sizeof( long )。

        类似地,也不要假设指针和int长度相等。


  3.1.不透明类型

      不透明数据类型隐藏了它们内部格式或结构。幵发者们利用typedef^明一个类型,把它叫做不透明类型,希望其他人别去把它重新转化回对应的那个标准C类型。通常开发者们在定义一套特别的接口时才会用到它们。

      另 外一个不透明数据类型的例子是atomic_t。在第9章"内核同步方法"中介绍过,它放置的是一个可以进行原子操作的整型值。尽管这种类型就是一个int,但利用不透明类型可以帮助确保这些数据只在特殊的有关原子操作的函数中才会被使用。

      内核还用到了其他一些不透明类型,包括dev_t、 gid—t和uid_t等等。处理不透明类型时的原则是:

          •不要假设该类型的长度。

          •不要将该类型转化回其对应的C标准类型使用。

          •编程时要保证在该类型实际存储空间和格式发生变化时代码不受影响。
  3.2.指定数据类型      内核中还有一些数据虽然无需用不透明的类型表示,但它们被定义成了指定的数据类型。jiffy数目和在中断控制时用到的flags参数就是两个例子,它们都应该被存放在unsigned long类型中。        当存放和处理这些特别的数据时,一定要搞清楚它们对应的类型后再使用。把它们存放在其他如unsigned im等类型中是一种常见错误。在32位机上这没什么问题,可是64位机上就会捅娄子了。
  3.3.长度明确的类型     作为一个程序员,你往往需要在程序中使用长度明确的数据。内核在<asm/typs.h>中定义了这些长度明确的类型,而该文件又被包含在文件<linux/types.h> 中。                  这些类型只能在内核中使用,不可以在用户空间中出现(比如,在头文件中的某个用户可见结构中出现)。这个限制是为了保护命名空间-不过内核对应这些不可见变量同时也定义了对应的用户可见的变量类型,这些类型与上面类型所不同的是增加了两个下画线前缀。比如,无符号32位整型对应的用户空间可见类型就__u32
  3.4.char型符号问题      大部分体系结构上,char默认是带符号的,它可以自-128到127之间取值。而也有一些例外, 比如ARM体系结构上,char就是不带符号的,它的取值范围是0〜255。        举例来说,在默认char不带符号,下面的代码实际会把255而不是-1賦予i:              char i = -1;


4.数据对齐     对齐是跟数据块在内存中的位置相关的话题。如果一个变量的内存地址正好是它长度的整数
倍,它就被称作是自然对齐的。
       一些体系结构对对齐的要求非常严格。通常像基于RISC(包括ARM)的系统,载入未对齐的数据会导致处理器陷入(一种可处理的错误)。还有一些系统可以访问没有对齐的数据,只不过性能会下降。编写可移植性髙的代码要避免对齐问题,保证所有的类型都能够自然对齐。
  4.1.避免对齐引发的问题       编译器通常会通过让所有的数据自然对齐来避免引发对齐问题。       一个数据类型长度较小,它本来是对齐的,如果你用一个指针进行类型转换,并且转换后的类型长度较大,那么通过改指针进行数据访问时就会引发对齐问题。        
         这个例子将一个指向char型的指针当作指向unsigned long型的指针来用,这会引起问题,因为此时会试图从一个并不能被4整除的内存地址上载入32位的unsigned long型数据。
  4.2.非标准类型对齐      对于标准数据类型来说,它的地址只要是其长度的整数倍就对齐了。而非标准的(复合的)C数据类型按照下列原则对齐-         对于数组,只要按照基本数据类型进行对齐就可了 (其实随后的所有元素自然能够对齐)。         •对于联合,只要它包含的长度最大的数据类型能够对齐就可以了。          •对于结构体,只要它包含的长度最大的数据类型能够对齐就可以了。
  4.3.结构体填补     为了保证结构体中每一个成员都能够自然对齐,结构体要被填补。               由于该结构不能准确地满足各个成员自然对齐,所以它在内存中可不是按照原样存放的。编译器会在内存中创建一个类似下面给出的结构体:                第一个填充物占用了3个字节的空间,保证cat可以按照4字节对齐。这也自动使其他小的对象都被对齐了,因为它们长度都比cat要小。第二个也是最后的填充是为了填补struct本身的大小。额外的这个填补使结构体的长度能够被4整除。      注意,ANSI C明确规定不允许编译器改变结构体内成员对象的次序——它总是由你,程序员来决定。


5.字节顺序     字节顺序是指在一个字中各个字节的顺序。如果最高有效位所在的字节放在最高字节位置上,其他字节依次放在低字节位置上,那么该字节顺序称作髙位优先(big-endian),如果最低有效位所在的字节放在最高字节位置上,其他字节依次放在低字节位置上,那么就称作低位优先(little-endian)       编写内核代码时不应该假设字节顺序是给定的哪一种(当然,如果你编写的是与体系结构相关的那部分代码就另当别论了)。ARM和x86都是低字节序。       让我们考察一下存放在一个四字节的整型中的二进制数,它的十进制对应值是1027:           00000000 00000000 00000100 00000011                      注意使用高位优先的体系结构把最高字节位存放在最小的内存地址上的。

        5.1.内核中的字节顺序     对于Linux支持的每一种体系结构,相应的内核都会根据机器使用的字节顺序在它的<asm/byteorder.h>中定义__BIG_ENDIAN和___LITTLE_ENDIAN中的一个。     这个头文件还从include/linux/byteonler/中包含了一组宏命令用于完成字节顺序之间的相互转换。最常用的宏命令有:        u32 _cpu_to_be32(u32);      /*把cpu字节顺序转换为髙位优先字节顺序*/        u32 _cpu_to_le32(u32);      /*把cpu字节顺序转换为低位优先字节顺序*/ -        u32 _be32_to_cpu(u32);      /*把髙位优先字节顺序转换为cpu字节顺序*/        u32 _le32_to_cpus(u32);     /*把低位优先字节顺序转换为cpu字节顺序*/     这些转换能够把一种字节顺序变为另一种字节顺序。如果两种字节顺序本来就相同(比如,希望从本地字节顺序转化为髙位优先字节顺序,而处理器本身使用的就是高位优先字节顺序),那么宏就什么都不做。否则它们就进行转换。


6.时间     绝对不要假定时钟中断发生的频率,也就是每秒产生的jiffies数目。相反,应该使用Hz来正确计量时间。这一点至关重要,因为不但不同的体系结构之间定时中断的频率不同,即使是在同一种体系机构上,两个不同版本的内核之间这种频率也不尽相同。      绝对不要用jiffies直接去和1000这样的数值去比较,认为这样做大体上不会出问题是要不得的。计量时间的正确方法是乘以或除以Hz。比如:          


7.页长度     当处理用页管理的内存时,绝对不要假设页的长度。尽管x86机器上使用的页确实是4KB,但是其他不同的体系结构使用的页长度可能不同。实际上有些体系结构还同时支持多种不同长度的页。       PAGE_SHIFT这个值定义了从最右端屏蔽多少位能够得到该地址对应的页的页号。          


8.处理器排序      回忆第9章,其中讨论过体系结构对指令序列的排序问题。有些处理器严格限制指令排序,代码指定的所有装载或存储指令都不能被重新排序;而另外一些体系结构对排序要求则很弱,可以自行排序指令序列。         在代码中,如果在对排序要求最弱的体系结构上,要保证指令执行顺序。那么就必须使用诸如mib()和wmb()等恰当的内存屏障来确保处理器以正确顺序提交装载和存储指令。

9.SMP,内核抢占,高端内存      它们代表的其实都是可配置的重要选项,而你的代码应该充分考虑到对它们的支持。就是说,只有在编程时就针对SMP/内核抢占/高端内存进行了考虑,你的代码才会无论内核怎样配置,都能身处安全之中。再在前面那些保证可移植性的规范下加上这几条:        •假设你的代码会在SMP系统上运行,要正确地选择和使用锁。           •假设你的代码会在支持内核抢占的情况下运行,要正确地选择和使用锁和内核抢占语句。        •假设你的代码会运行在使用高端内存(非永久映射内存)的系统上,必要时使用kmap()

 

 









标签:顺序,字节,可移植性,第十九章,内核,类型,对齐,体系结构
来源: https://www.cnblogs.com/wen123456/p/14009600.html