Data Types in the Kernel [LDD3 11]
为什么要单独讲kernel的数据结构?
kernel是一个支持很多平台很多架构的OS,不同的架构上,很多的数据结构都是不同的,如何保证kernel能兼容多个架构,这就得益于kernel良好的数据结构设计。
kernel中各种各样的数据结构,主要三类:
1, int等标准的C类型
2, 表明大小或者长度的类型,如u32等
3, kernel自己的结构体,如pid_t等
后面会讲应该如何使用这样的数据结构。
11.1. Use of Standard C Types
尽管developer可以尽情使用像int,long这类标准的数据类型,但是要注意类型冲突或者潜在的bug。这类数据类型,在不同的架构上占用的memory是不一样的:
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
可以看出long类型和指针占用的内存是一样大的,所以在kernel中大量使用unsigned long来表示地址。之所以不直接使用指针,是为了防止随意通过指针访问memory,因为kernel推荐使用各种各样的interface来访问。
11.2. Assigning an Explicit Size to Data Items
有些场景下,需要明确指定变量的size,比如:
#include <asm/types.h>
u8; /* unsigned byte (8 bits) */
u16; /* unsigned word (16 bits) */
u32; /* unsigned 32-bit value */
u64; /* unsigned 64-bit value */
如果user space需要使用这样的类型,需要使用带两个下划线的版本,如__u8,__u32等。
11.3. Interface-Specific Types
有些接口返回的数据类型,不是标准的C数据类型,而是kernel通过typedef自己定义的数据结构,比如进程ID pid_t。使用这种自定义的类型,好处在于可以隐藏架构不同导致的数据差异,对driver developer而言,无论什么架构,使用的数据类型都是pid_t,kernel则隐藏了每个架构的实现。
然而后面kernel的code使用interface specific 数据类型的越来越少了,许多code直接使用相应的数据类型,而不是类似于pid_t的数据类型。因为这种数据类型有其弊端,比如size_t可能对应unsigned int,或者unsigned long,那么你打印的时候按照什么数据类型打印。通常情况下,把它按照最大的数据类型转换,再打印就可以,比如size_t转换为unsigned long来打印。
11.4. Other Portability Issues
code中可能存在一些预定义的常量,导致存在某些移植性的问题,比如一些预定义的宏。
11.4.1. Time Intervals
计算时间间隔,不要假设当前的HZ是1000,就用1000直接计算,而应该使用HZ来计算。比如要计算0.5s,应该是HZ/2,而不是直接使用jiffies 500.
11.4.2. Page Size
虽然x86或者其他的架构上PAGE_SIZE是4KB,但是不能直接使用4KB,而应该使用PAGE_SIZE和PAGE_SHIFT。这里有一个例子,如果你需要分16KB的memory,如何分配呢?
#include <asm/page.h>
int order = get_order(16*1024);
buf = get_free_pages(GFP_KERNEL, order);
应该使用get_order来计算order,再使用order调用get_free_pages来分page。
11.4.3. Byte Order
不同架构上可能使用不同的字节序,但是device driver在实现时,尽量不对字节序做任何假设,也不应该依赖于字节序。但是在某些特定的情况下,可能需要依赖于字节序。
在需要依赖于字节序的地方,可以通过宏来check:__BIG_ENDIAN和__LITTLE_ENDIAN。但是如果这样的宏太多,会比较麻烦,所以kernel提供了这样的函数直接来实现:
u32 cpu_to_le32 (u32);
u32 le32_to_cpu (u32);
使用上面的两个宏,会把数据转换为小尾端的u32出来,无论你是大尾端还是小尾端。
11.4.4. Data Alignment
为了提高performance,编译器可能会对数据结构进行padding,而不同的架构上,可能对其的结构并不一样。为了减少这种问题,driver可以自己对数据结构进行padding。比如kernel的一个例子:
struct {u16 id;u64 lun;u16 reserved1;u32 reserved2;
} _ _attribute_ _ ((packed)) scsi;
11.4.5. Pointers and Error Values
很多interface如果fail了,会直接返回NULL;但是有时候caller需要直到发生了哪种错误,这就需要interface返回一些error code,而不是NULL,caller通过一些方法检查这些error code,就能知道发生了什么事。
//返回error ptr (通过error生成)
void *ERR_PTR(long error);//判断是否返回了error ptr
long IS_ERR(const void *ptr);//如果需要从error ptr解析error code
long PTR_ERR(const void *ptr);
11.5. Linked Lists
kernel实现了一套自己的双向链表,通过这个双向链表可以让device driver很方便的实现自己的双向链表结构,在使用的时候,如果需要互斥的访问链表资源,那driver就得自己做保护。
#include <linux/list.h>
struct list_head {struct list_head *next, *prev;
};struct todo_struct {struct list_head list;int priority; /* driver specific *//* ... add other driver-specific fields */
};
需要使用双向链表的数据结构来说,只需要把struct list_head包含进自己的数据结构即可。
上图就是双向链表的组织形式。使用之前要初始化:
//运行时初始化
struct list_head todo_list;
INIT_LIST_HEAD(&todo_list);//编译时初始化
LIST_HEAD(todo_list);//把new添加到head后面,head不一定是链表头,可以是链表中的任何节点
list_add(struct list_head *new, struct list_head *head);//把new添加到head前面,也就是链表的尾巴。相当于把双向链表作为FIFO使用
list_add_tail(struct list_head *new, struct list_head *head);//将entry从双向链表中删除,如果还会放入别的list,就使用list_del_init。
list_del(struct list_head *entry);
list_del_init(struct list_head *entry);//把entry从当前链表移动到head链表头部,如果放入尾部,用list_move_tail。
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这个链表,插入到head后面
list_splice(struct list_head *list, struct list_head *head);//通过ptr获取包含struct list_head的大的数据结构地址
list_entry(struct list_head *ptr, type_of_struct, field_name);
//sample code:
struct todo_struct *todo_ptr =list_entry(listptr, struct todo_struct, list);
通常需要loop这个双向链表,kernel已经提供了函数,这里有个例子:
void todo_add_entry(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);if (entry->priority < new->priority) {list_add_tail(&new->list, ptr);return;}}list_add_tail(&new->list, &todo_struct)
}
除了list_for_each,kernel还提供了别的loop用的函数:
//普通的loop,需要注意loop的过程中对链表的修改
list_for_each(struct list_head *cursor, struct list_head *list)//逆向loop
list_for_each_prev(struct list_head *cursor, struct list_head *list)//如果loop的过程中,需要从链表中删除node,就使用这个函数
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)
Data Types in the Kernel [LDD3 11]相关推荐
- mysql timestamp 插入null报错_读MySQL 5.7文档11.2 Date and Time Data Types总结
作者:魏新平,知数堂第5期MySQL实战班学员,第10期MySQL优化班学员,现任职助教. 读MySQL5.7文档11.2 Date and Time Data Types MySQL的时间类型分为D ...
- OpenCL Data Types (数据类型)
OpenCL Data Types (数据类型) 1. Built-in Scalar Data Types - 内置标量数据类型 The following table describes the ...
- date类型_Chapter 01. 常见数据类型概述 Overview of Common Data Types
课程:DataCamp_Skill Track_SQL fundamentals[笔记] Chapter 01. 常见数据类型概述 Overview of Common Data Types 了解常见 ...
- 【12c】扩展数据类型(Extended Data Types)-- MAX_STRING_SIZE
[12c]扩展数据类型(Extended Data Types)-- MAX_STRING_SIZE [12c]扩展数据类型(Extended Data Types)-- MAX_STRING_SIZ ...
- Redis Essentials 读书笔记 - 第二章: Advanced Data Types (Earning a Black Belt)
Chapter 2. Advanced Data Types (Earning a Black Belt) 本章介绍数据类型:Set, Sorted Set, Bitmap, HyperLogLog. ...
- 成功解决ValueError: DataFrame.dtypes for data must be int, float or bool.Did not expect the data types
成功解决ValueError: DataFrame.dtypes for data must be int, float or bool. Did not expect the data types ...
- Supported Data Types(flink支持的数据类型)
文章目录 1.Tuples and Case Classes 2.POJOs 3.Primitive Types 4.General Class Types 5.Values 6.Hadoop Wri ...
- golang中的variable和data types
https://golangbyexample.com/variables-in-golang-complete-guide/#What_is_variable 问题 go里面的string为什么是不 ...
- keil问题:报 ***WARNING L25: DATA TYPES DIFFERENT 警告,是什么问题?
在A.c文件中定义了一个函数fun(),在外面其他X.c文件中使用到了,但是在A.h文件中没有声明fun(),会出现 "***WARNING L25: DATA TYPES DIFFEREN ...
最新文章
- Python开发【第十篇】:CSS (二)
- document 文挡对象解析
- android时间显示中文版,系统运行时间显示工具(Vov System Uptime)
- 解决Visual Studio (VS) 插件下载缓慢
- Arts 第十九周(7/22 ~ 7/28)
- .NET Worker Service 作为 Windows 服务运行及优雅退出改进
- 【OpenCV 例程200篇】46. 直方图均衡化
- redis同步到磁盘
- 如何解决VC 应用程序无法启动,因为应用程序的并行配置不正确 sxstrace.exe问题...
- 三阶魔方大中小魔公式_三阶魔方花样大汇总 ,带公式带图
- 2022.3.19-2022.3.27每周刷题
- 求有10个整型元素的数组中最大元素及其下标。
- java手机解锁_Android手机屏幕敲击解锁功能代码
- 种植牙术后的注意事项
- 专访Cisco 梁永健:网络会议撬动SaaS市场
- fseek函数的用法(用于设定指针位置)
- Flink实操 : DataSource操作
- 真正厉害的人,都在延迟满足
- windows10中创建Direct3D11设备出现0x887a002d错误的解决方案
- 10. 从0学ARM-基于Exynos4412的pwm详解