【原创】《Linux设备驱动程序》学习之循序渐进 --- 内核数据类型
第十一章 --- 内核数据类型
内核数据使用的数据类型分为 3 个主要类型: 标准 C 类型例如 int, 明确大小的类型例如 u32, 以及用作特定内核对象的类型, 例如 pid_t. 我们将看到这 3 个类型种类应当什么时候以及应当如何使用.
标准 C 类型的使用
尽管大部分程序员习惯自由使用标准类型, 如 int 和 long, 编写设备驱动需要一些小心来避免类型冲突和模糊的 bug.
内核中通常的内存地址常常是 unsigned long, 利用了指针和长整型一直是相同大小的这个事实, 至少在 Linux 目前支持的所有平台上.
为数据项分配确定的空间大小
重要的是记住这些类型是 Linux 特定的, 并且使用它们妨碍了移植软件到其他的 Unix 口味上. 使用近期编译器的系统支持 C99-标准 类型, 例如 uint8_t 和 uint32_t; 如果考虑到移植性, 使用这些类型比 Linux-特定的变体要好.
你可能也注意到有时内核使用传统的类型, 例如 unsigned int, 给那些维数与体系无关的项. 这是为后向兼容而做的.
接口特定的类型
内核中一些通常使用的数据类型有它们自己的 typedef 语句, 因此阻止了任何移植性问题. 例如, 一个进程标识符 ( pid ) 常常是 pid_t 而不是 int. 使用 pid_t 屏蔽了任何在实际数据类型上的不同. 我们使用接口特定的表达式来指一个类型, 由一个库定义的, 以便于提供一个接口给一个特定的数据结构.
无论何时你需要打印某个接口特定的数据, 最好的方法是转换它的值为最大的可能类型(常常是 long 或者 unsigned long ) 并且接着打印它通过对应的格式. 这种调整不会产生错误或者警告, 因为格式匹配类型, 并且你不会丢失数据位, 因为这个转换或者是一个空操作或者是数据项向更大数据类型的扩展.
其他有关移植性的问题
一个通常的规则是怀疑显式的常量值. 常常通过使用预处理宏, 代码已被参数化. 这一节列出了最重要的可移植性问题.
时间间隔
无论何时你使用jiffies来计算时间间隔, 应该使用 HZ ( 每秒的定时器中断数 ) 来标定你的时间. 例如, 检查一个半秒的超时, 用 HZ/2 和逝去时间比较. 更普遍地, msec 毫秒对应地jiffies一直是 msec*HZ/1000.
页大小
宏在 <asm/page.h> 中定义; 如果它们需要这个信息,用户空间程序可以使用 getpagesize 库函数,.
已经由内核开发者写好并且称为 get_order:
#include <asm/page.h>
int order = get_order(16*1024);
buf = get_free_pages(GFP_KERNEL, order);
记住, get_order 的参数必须是 2 的幂.
字节序
包含文件 <asm/byteorder.h> 定义了或者 __BIG_ENDIAN 或者 __LITTLE_ENDIAN, 依赖处理器的字节序. 当处理字节序问题时, 你可能编码一堆 #ifdef __LITTTLE_ENDIAN 条件语句, 但是有一个更好的方法. Linux 内核定义了一套宏定义来处理之间的转换, 在处理器字节序和你需要以特定字节序存储和加载的数据之间. 例如:
u32 cpu_to_le32 (u32);
u32 le32_to_cpu (u32);
数据对齐
如果你需要存取不对齐的数据, 你应当使用下列宏:
#include <asm/unaligned.h>
get_unaligned(ptr);
put_unaligned(val, ptr);
这些宏是无类型的, 并且用在每个数据项, 不管它是 1 个, 2 个, 4 个, 或者 8 个字节长. 它们在任何内核版本中定义.
为强制自然对齐在阻止编译器以不希望的方式安排成员量的时候, 你应当使用填充者成员来避免在数据结构中留下空洞.
链表
当使用链表接口时, 你应当一直记住列表函数不做加锁. 如果你的驱动可能试图对同一个列表并发操作, 你有责任实现一个加锁方案. 可选项( 破坏的列表结构, 数据丢失, 内核崩溃) 肯定是难以诊断的.
快速参考
#include <linux/types.h>typedef u8;
typedef u16;
typedef u32;
typedef u64;
保证是 8-位, 16-位, 32-位 和64-位 无符号整型值的类型. 对等的有符号类型也存在. 在用户空间, 你可用 __u8, __u16, 等等来引用这些类型.
#include <asm/page.h>
PAGE_SIZE
PAGE_SHIFT
给当前体系定义每页的字节数, 以及页偏移的位数( 对于 4 KB 页是 12, 8 KB 是 13 )的符号.
#include <asm/byteorder.h>
__LITTLE_ENDIAN
__BIG_ENDIAN
这 2 个符号只有一个定义, 依赖体系.
#include <asm/byteorder.h>
u32 __cpu_to_le32 (u32);
u32 __le32_to_cpu (u32);
在已知字节序和处理器字节序之间转换的函数. 有超过 60 个这样的函数: 在 include/linux/byteorder/ 中的各种文件有完整的列表和它们以何种方式定义.
#include <asm/unaligned.h>
get_unaligned(ptr);
put_unaligned(val, ptr);
一些体系需要使用这些宏保护不对齐的数据存取. 这些宏定义扩展成通常的指针解引用, 为那些允许你存取不对齐数据的体系.
#include <linux/err.h>
void *ERR_PTR(long error);
long PTR_ERR(const void *ptr);
long IS_ERR(const void *ptr);
允许错误码由返回指针值的函数返回.
#include <linux/list.h>
list_add(struct list_head *new, struct list_head *head);
list_add_tail(struct list_head *new, struct list_head *head);
list_del(struct list_head *entry);
list_del_init(struct list_head *entry);
list_empty(struct list_head *head);
list_entry(entry, type, member);
list_move(struct list_head *entry, struct list_head *head);
list_move_tail(struct list_head *entry, struct list_head *head);
list_splice(struct list_head *list, struct list_head *head);
操作环形, 双向链表的函数.
list_for_each(struct list_head *cursor, struct list_head *list)
list_for_each_prev(struct list_head *cursor, struct list_head *list)
list_for_each_safe(struct list_head *cursor, struct list_head *next, struct list_head *list)
list_for_each_entry(type *cursor, struct list_head *list, member)
list_for_each_entry_safe(type *cursor, type *next struct list_head *list, member)
方便的宏定义, 用在遍历链表上.