在linux内核中有一个list.h头文件。
它采用有头节点的双向循环链表的方式,很优雅的支持了诸多链表操作。
本文意在辅助理解list.h中最核心的部分——内核链表如何定义 & 如何访问下一个数据。
内核链表的使用 vs 双循环链表
- 使用双循环链表时结构体的构建:
指针指向最外层的结构体,访问数据时访问的是结构体内部的数据元素
struct Node{
//声明一个数据 可为结构体
struct Node *next; //指向下一个结点的指针
struct Node *prev; //指向前一个结点的指针
}
- 使用内核链表时,结构体的构建:
在内核结构体外面套一层用于存储数据,
指针指向内核结构体,访问数据时需先计算外部结构体的位置,在进行访问(详见下文)。
这是list.h 中定义的一个list_head结构体,暂且称之为内核结构体。
struct list_head {
struct list_head *next, *prev;
};
内核结构体中只有两个指针,那我们在使用时,数据该如何存储呢?
显然,不能直接改动list.h中的结构体,因此我们使用时需要在内核结构体外部套一层用来存储数据(暂且称之为外部结构体)。
在使用时在内核结构体外套一层,用来存储数据
struct Node{
int number; 想存储的数据,此处以整型为例
struct list_head list; list.h中定义的结构体
}
内核结构体访问数据
内核结构体访问数据时,只能找到要访问的内核结构体。
我们知道,结构体只能找到自己内部的元素。那如何找到套在外面的数据呢?
这就需要通过container_of**(ptr, type, member)
(list.h提供的宏定义)来计算外部结构体位置。
找到外部结构体位置后,我们就能访问它内部的数据元素啦~
"list.h"中定义的 计算外部结构体位置的宏
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
内核结构体为啥存在
内核结构体的访问显然比常规的结构体要复杂。
但由于内核无法预先得知需要存储数据的数据结构,为保证其可扩展性,只能采用内嵌的方式。