技术交流

好好学习,天天向上。

0%

第十一章——内核的数据类型

类型

驱动移植的许多问题都是数据类型导致的,同样的数据类型在不同的平台上可能有不同的位宽。内核将内存看做一个巨型数组看看待,但是内核中表示地址往往用的unsigned long,这可以防止一些错误的解指针。同时在当前支持linux的各个平台上,long的位宽和指针位宽总是相同的。内核提供可移植类型定义,下面这个头文件包含u8u16u32u64Linux专用数据类型。

1
#include <linux/types.h>

内存

对内核的页面大小不要有任何假定,不要认为内核页面一定是4KB的。具体可以通过PAGE_SIZE判断。如果是用户态,可以用getpagesize库来获得页面大小。对于需要用页面幂次分配内存的参数,可以用get_order获得正确的幂次。

1
2
3
#include <asm/page.h>
int order = get_order(16*1024);
buf = get_free_pages(GFP_KERNEL, order);

字节序

内核提供宏让内核代码判断当前平台是大端序还是小端序

1
2
3
4
5
6
7
8
9
#include <asm/byteorder.h>

#ifdef __LITTLE_ENDIAN
...
#endif

#ifdef __BIG_ENDIAN
...
#endif

有类似于u32 e32_to_le32(u32)这样的函数来保证将处理器类型转换为所需类型,完整列表见内核代码。

数据对齐

某些平台不能直接访问未对齐的数据,而是由异常处理机制处理,非常影响性能。内核提供接口辅助对非对齐数据的访问

1
2
3
#include <asm/unaligned.h>
get_unaligned(ptr);
put_unaligned(val, ptr);

同时,在需要精准的控制数据结构对齐的时候,可以手动填充占位符或者用编译器内建属性标签

1
2
3
struct {
...
} __attribute__ ((packed)) xxxx;

返回指针的函

返回指针的函同样可以报告具体错误编码,将错误码编码在指针中即可。为了帮助内核代码编写,内核提供了辅助函数

1
2
3
4
5
6
7
8
// 该函数在函数需要返回带有错误编码的指针时调用,error通常是负的错误编码,
void *ERR_PTR(long error);

// 函数调用者,可以利用该接口判断,下级函数返回的指针是不是含有错误的指针
long IS_ERR(const void *ptr)

// 该指针可以将指针中的错误编码提取出来
long PTR_ERR(const void *ptr);

链表

链表是内核经常需要用到的一种数据结构,所以内核实现了一套通用代码可以实现内核链表增删查改等。此外,内核这套链表都是循环的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

struct list_head todo_list; // 声明一个链表
INIT_LIST_HEAD(&todo_list); // 初始化一个链表
LIST_HEAD(todo_list); // 一步到位的声明并创建一个链表

list_add(struct list_head *new, struct list_head *head); // new节点将被插在head后面,因为循环关系,head不一定真的是链表表头
list_add_tail(struct list_head *new, struct list_head *head); // new节点将被插在head前面,因为循环关系,head不一定真的是链表表头
list_del(struct list_head *entry); // 删除节点
list_del_init(struct list_head *entry); // 如果打算从A链表拆下一个节点插入到B链表,应该用这个版本
list_move(struct list_head *entry, struct list_head *head); // 将节点移动到链表开头
list_move_tail(struct list_head *entry, struct list_head *head); // 将节点移动到链表结尾
list_empty(struct list_head *head); // 判断链表是否为空
list_splice(struct list_head *list, struct list_head *head); // 将list链表插入到head后面以合并两个链表
list_entry(struct list_head *ptr, type_of_struct, field_name); // 类似于container_of,映射到包含节点的数据结构

list_for_each(struct list_head *cursor, struct list_head *list) // 遍历链表辅助宏
list_for_each_prev(struct list_head *cursor, struct list_head *list) // 反向遍历辅助宏
// safe版本遍历,每次遍历时提前保存下一遍历点的值,可以在遍历过程中执行删除操作
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_entry调用的遍历
list_for_each_entry_safe(type *cursor, type *next, struct list_head *list, member); //

// 链表的手动遍历
void iteration_of_lsit(void)
{
struct list_head *ptr;
struct todo_struct *entry;
for (ptr = todo_list.next; ptr != &todo_list; ptr = ptr->next) {
entry = list_entry(ptr, struct todo_struct, list);
...
}
}
// 利用辅助宏的链表遍历
void iteration_of_lsit(struct todo_struct *new)
{
struct list_head *ptr;
struct todo_struct *entry;
list_for_each(ptr, &todo_list) {
entry = list_entry(ptr, struct todo_struct, list);
...
}
}