目录
在我们进入更高级主题之前, 我们需要停下来快速关注一下可移植性问题. 现代版本的 Linux 内核是高度可移植的, 它正运行在很多不同体系上. 由于 Linux 内核的多平台特性, 打算做认真使用的驱动应当也是可移植的.
但是内核代码的一个核心问题是不但能够存取已知长度的数据项(例如, 文件系统数据结构或者设备单板上的寄存器), 而且可以使用不同处理器的能力(32-位 和 64-位 体系, 并且也可能是 16 位).
内核开发者在移植 x86 代码到新体系时遇到的几个问题与不正确的数据类型相关. 坚持严格的数据类型和使用 -Wall -Wstrict-prototypes 进行编译可能避免大部分的 bug.
内核数据使用的数据类型分为 3 个主要类型: 标准 C 类型例如 int, 明确大小的类型例如 u32, 以及用作特定内核对象的类型, 例如 pid_t. 我们将看到这 3 个类型种类应当什么时候以及应当如何使用. 本章的最后的节谈论一些其他的典型问题, 你在移植 x86 的驱动到其他平台时可能遇到的问题, 并且介绍近期内核头文件输出的链表的常用支持.
如果你遵照我们提供的指引, 你的驱动应当编译和运行在你无法测试的平台上.
尽管大部分程序员习惯自由使用标准类型, 如 int 和 long, 编写设备驱动需要一些小心来避免类型冲突和模糊的 bug.
这个问题是你不能使用标准类型, 当你需要"一个 2-字节 填充者"或者"一个东西来代表一个4-字节 字串", 因为正常的 C 数据类型在所有体系上不是相同大小. 为展示各种 C 类型的数据大小, datasize 程序已包含在例子文件 misc-progs 目录中, 由 O' Reilly's FTP 站点提供. 这是一个程序的样例运行, 在一个 i386 系统上(显示的最后 4 个类型在下一章介绍):
morgana% misc-progs/datasize arch Size: char short int long ptr long-long u8 u16 u32 u64 i686 1 2 4 4 4 8 1 2 4 8
这个程序可以用来显示长整型和指针在 64-位 平台上的不同大小, 如同在不同 Linux 计算机上运行程序所演示的:
arch Size: char short int long ptr long-long u8 u16 u32 u64 i386 1 2 4 4 4 8 1 2 4 8 alpha 1 2 4 8 8 8 1 2 4 8 armv4l 1 2 4 4 4 8 1 2 4 8 ia64 1 2 4 8 8 8 1 2 4 8 m68k 1 2 4 4 4 8 1 2 4 8 mips 1 2 4 4 4 8 1 2 4 8 ppc 1 2 4 4 4 8 1 2 4 8 sparc 1 2 4 4 4 8 1 2 4 8 sparc64 1 2 4 4 4 8 1 2 4 8 x86_64 1 2 4 8 8 8 1 2 4 8
注意有趣的是 SPARC 64 体系在一个 32-位 用户空间运行, 因此那里指针是 32 位宽, 尽管它们在内核空间是 64 位宽. 这可用加载 kdatasize 模块(在例子文件的 misc-modules 目录里)来验证. 这个模块在加载时使用 printk 来报告大小信息, 并且返回一个错误( 因此没有必要卸载它 ):
kernel: arch Size: char short int long ptr long-long u8 u16 u32 u64 kernel: sparc64 1 2 4 8 8 8 1 2 4 8
尽管在混合不同数据类型时你必须小心, 有时有很好的理由这样做. 一种情况是因为内存存取, 与内核相关时是特殊的. 概念上, 尽管地址是指针, 内存管理常常使用一个无符号的整数类型更好地完成; 内核对待物理内存如同一个大数组, 并且内存地址只是一个数组索引. 进一步地, 一个指针容易解引用; 当直接处理内存存取时, 你几乎从不想以这种方式解引用. 使用一个整数类型避免了这种解引用, 因此避免了 bug. 因此, 内核中通常的内存地址常常是 unsigned long, 利用了指针和长整型一直是相同大小的这个事实, 至少在 Linux 目前支持的所有平台上.
因为其所值的原因, C99 标准定义了 intptr_t 和 uintptr_t 类型给一个可以持有一个指针值的整型变量. 但是, 这些类型几乎没在 2.6 内核中使用.