LINUX内核设计与实现之可移植性

来源:互联网 发布:剑三2016成女捏脸数据 编辑:程序博客网 时间:2024/05/18 00:52

LINUX系统是一个可移植性非常好的操作系统.一般而言,暴露在外的内核接口往往是与硬件体系结构无关的.例如:

调试程序(存放于kernel/sched.c文件中)就是与体系结构无关的.但是其中封装的一些功能函数,如处理器上下文切换和地址空间切换等,是依赖于相应的体系结构完成的.因此,context_switch()用于实现进程切换,而在它内部,会调用switch_to()switch_mm()分别完成处理器上下文和地址空间的切换.

与体系结构相关的代码存放在arch/architecture/include/asm-architecture/目录下.architectureLINUX支持的体系结构的简称,i386.

 

19.1 LINUX的可移植性

目前LINUX支持的体系结构已经达到了20多种.ARMX86MIPS.

 

19.2 字长和数据类型

:能够由机器一次就完成处理的数据.处理器通用寄存器(GPR’s)的大小和它的字长相同.一般来说,对于一个体系结构,它各部件的宽度,如内存总线,最少要和它的字长一样大.

LINUX无论支持哪种体系结构,都要遵循下面的规则:

.ANSI C标准规定,一个char的长度一定是8;

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

.short类型如果没有明文规定,它都是16位的;

.决不假定指针和long的长度,它是可变的,32位和64位中变化;

.不能假设sizeof(int)==sizeof(long);

.不要假定指针和int长度相等.

 

19.2.1 不透明类型

typedef声明一个类型,把它叫做不透明类型.如用来保存进程标识符的pid_t类型.处理不透明类型的原则是:

.不要假设该类型的长度;

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

.编程时要保证该类型实际存储空间和格式发生变化时代码不受影响.

 

19.2.2 指定数据类型

内核中一些数据虽然不需要用不透明的类型表示,但是它们被定义成了指定的数据类型.jiffy数目和在中断控制时用到的flags参数就是两个例子.它们都应该被存放在unsigned long类型中.

 

19.2.3 长度明确的类型

我们在实际编程中,需要在程序中使用长度明确的数据.像操作硬件设备,进行网络通信和操作二进制文件时,通常都必须满足它们明确的内部要求.比如说,一块声卡可能用的是32位寄存器,一个网络包有一个16位字段,一个可执行文件有8位的cookie.内核在<asm/typs.h>中定义了这些长度明确的类型,而该文件又被包含在文件<linux/types.h>.如下表:

这些长度明确的类型大部分通过typedef对标准C类型进行映射得到.下面是示意代码:

Typedef signed char s8;

Typedef unsigned char u8;

Typedef signed short s16;

Typedef unsigned short u16;

Typedef signed int s32;

Typedef unsigned int u32;

Typedef signed long s64;

Typeedf unsigned long u64;

而在32位机上,它们可能定义为:

Typedef signed char s8;

Typedef unsigned char u8;

Typedef signed short s16;

Typedef unsigned short u16;

Typedef signed int s32;

Typedef unsigned int u32;

Typedef signed long long s64;

Typedef unsigned long long u64;

上述的这些类型只能在内核使用,不可以在用户空间出现.用户空间的长度明确类型有两个下划线.如下:

__u32 à用户空间

U32 à内核空间

 

19.2.4 char型的符号问题

大部分体系结构上,char默认是带符号的,它可以自-128127之间取值.也有一些例外,ARM体系结构上,char就是不带符号的,它的取值范围是0~255.

 

19.3 数据对齐

保证数据对齐可以提高系统性能.

19.3.1 避免对齐引发的问题

一个数据类型长度较小,它本来是对齐的,如果用一个指针进行类型转换,并且转换后的类型长度较大,那么通过改指针进行数据访问时就会引发对齐问题.如下代码是错误的:

Char dog[10];

Char *p = &dog[1];

Unsigned long l = *(unsigned long *)p;

这个例子将一个指向char型的指针当作指向unsigned long 型来用,会引发对齐问题.因为此时会试图从一个并不能被4整除的内存地址上载入32位的unsigned long型数据.

 

19.3.2 非标准类型的对齐

前面提到了,对于标准数据类型来说,它的地址只要是其长度的整数倍对齐就可以了.而非标准的(复合的)C数据类型按照下列原则对齐:

.对于数组,只要按照基本数据类型进行对齐就可以了;

.对于联合,只要它包含的长度最大的数据类型能够对齐就可以了;

.对于结构体,只要它包含的长度最大的数据类型能够对齐就可以了.

 

19.3.3 结构体填补

为了保证结构体中每一个成员能够自然对齐,结构体要被填补.这点确保了当处理器访问结构中一个给定元素时,元素本身是对齐的.如下面的例子:

Struct animal_struct

{

Char dog; //1字节

Unsigned long cat; //4字节

Unsigned short pig; //2字节

Char fox; //1字节

}

由于该结构体不能准确满足各成员自然对齐,所以它在内存中可不是按原样放置的.编译器会在内存中创建一个类似下面给出的结构体:

Struct animal_struct

{

Char dog;

U8 __pad0[3];

Unsigned long cat ;

Unsigned short pig;

Char fox;

U8 __pad1;

}

因此,在内存中此结构体实际占用的空间大小为12字节.因此,需要我们编程时注意自然对齐问题,避免编译器填充,节省内存空间.对此结构体重新排序如下:

Struct animal_struct

{

Unsigned long cat;

Unsigned short pig;

Char dog;

Char fox;

}

这样结构体只有8字节了.

19.4 字节顺序

Big-EndianLittle-Endian的定义如下:
1) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
2) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
举一个例子,比如数字0x12 34 56 78在内存中的表示形式为:
1)大端模式:

低地址 -----------------> 高地址
0x12  |  0x34  |  0x56  |  0x78
2)小端模式:

低地址 ------------------> 高地址
0x78  |  0x56  |  0x34  |  0x12 

下面给出一个判断机器是高位优先还是低位优先字节顺序的代码:

Int x = 1;

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

Printf("Little-Endian\n");

Else

Printf("Big-Endian\n");

 

19.4.2 内核中的字节顺序

内核中常常会涉及到字节顺序的转换.常用宏命令如下:

19.5 时间

不要假定时钟中断发生的频率.也就是每秒产生的jiffies数目.因上,绝对不要用jiffiess直接去和1000这样的数值比较.计量时间的正确方法是乘以或除以HZ.:

HZ //1

(2*HZ) //2

(HZ/2) //半秒

 

19.6 页长度

不要假定页的长度.如假定页大小为4KB.当处理用页组织管理内存时,通过PAGE_SIZE来使用以字节数来表示的长度.PAGE_SHIFT这个值定义了从最右端屏蔽多少位能够得到该地址对应的页的页号.比如,在页长为4KBX86机上,PAGE_SIZE4096PAGE_SHIFT12.它们都定义于<asm/page.h>文件中.如下表所示:

 

19.7 处理器排序

在代码中,如果在对排序要求最弱的体系结构上,要保证指令执行顺序.那么就必须使用诸如rmb()wmb()等恰当的内存屏障来确保处理器以正确顺序提交装载和存储指令.

 

19.8 SMP、内核抢占、高端内存

在内核编程中涉及到SMP、内核抢占、高端内存时候,需要加上以下几条规范:

.假设你的代码会在SMP系统上运行,要正确选择和使用锁;

.假设你的代码会在支持内核抢占的情况下运行,要正确选择和使用锁和内核抢占语句;

.假设你的代码会运行在使用高端内存(非永久映射内存)的系统上,必要时使用kmap().