目录
内核链表
内核链表:设计思想与核心优势
Linux 内核链表是一种高度抽象、无数据域的双向循环链表,它通过将链表节点嵌入到宿主结构体中,实现了对任意数据类型的统一管理。这种设计在内核中被广泛应用于进程管理、设备驱动、文件系统等模块,其核心优势在于解耦数据结构与算法,让开发者可以专注于业务逻辑。
1. 内核链表的核心结构
(1) 链表节点定义
struct list_head {
struct list_head *prev;
struct list_head *next;
};
-
无数据域:仅包含指向前驱和后继节点的指针,不存储实际数据。
-
嵌入宿主结构体:通过将
list_head
嵌入到其他结构体中,实现对不同数据类型的链表化管理。
(2) 宿主结构体示例
struct Task {
int pid;
char name[16];
struct list_head list; // 嵌入的链表节点
};
2. 核心设计思想:通过偏移量定位宿主结构体
(1) offsetof 宏:计算成员偏移量
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER)
- 将地址
0
强制转换为TYPE*
,访问MEMBER
成员,获取其地址(即偏移量)。
(2) container_of 宏:通过成员地址反推宿主结构体地址
#define container_of(ptr,type,member) ({\
const typeof(((type*)0)->member)* _mptr = (ptr);\
(type*)((char*)_mptr - offsetof(type,member));})
-
类型安全:通过
typeof
确保ptr
与member
类型一致。 -
字节级计算:将指针转换为
char*
进行字节运算,确保偏移量计算的准确性。
(3) 链表遍历示例
#define list_for_each(pos, head)\
for (pos = (head)->next; pos != (head);\
pos = pos->next)
3. 内核链表的核心优势
(1) 与数据类型解耦
-
链表操作不依赖于具体数据类型,同一套链表 API 可处理任意结构体。
-
对比传统链表(需为每种数据类型单独实现链表操作),内核链表显著减少代码冗余。
(2) 零成本抽象
-
链表节点无需额外内存存储数据指针,直接嵌入宿主结构体,节省内存。
-
所有操作通过宏展开实现,无函数调用开销,性能接近手工编写的链表代码。
(3) 统一接口,简化维护
-
内核开发者只需掌握一套链表 API,即可在各种场景下使用链表。
-
当链表实现需要优化时,只需修改底层代码,所有上层模块自动受益。
(4) 安全性与健壮性
-
list_for_each_safe
等安全宏确保在遍历过程中删除节点不会导致崩溃。 -
类型检查机制(如
container_of
中的typeof
)减少了因类型不匹配导致的错误。
4. 内核中的典型应用场景
(1) 进程管理
(2) 设备驱动
(3) 文件系统
5. 与传统链表的对比
特性 | 内核链表 | 传统链表 |
---|---|---|
数据域 | 无(仅指针) | 包含数据或指向数据的指针 |
通用性 | 高(一套 API 处理所有类型) | 低(需为每种数据类型单独实现) |
内存效率 | 高(节点嵌入宿主结构体) | 低(需额外内存存储数据指针) |
代码复杂度 | 低(依赖宏和统一接口) | 高(重复实现链表操作) |
类型安全性 | 高(通过 container_of 类型检查) | 低(手动管理类型转换) |
6. 设计哲学:内核编程的 “组合优于继承”
内核链表体现了 Linux 内核的重要设计哲学:通过组合而非继承实现复用。它不要求数据结构继承特定基类,而是通过嵌入通用链表节点,将数据结构 “接入” 到链表系统中。这种方式比面向对象的继承更灵活,避免了继承带来的强耦合问题。
7. 总结:为什么内核选择这种链表?
-
性能敏感:内核场景对内存和 CPU 效率要求极高,零成本抽象符合内核设计原则。
-
通用性需求:内核需要管理各种类型的数据,统一的链表 API 降低了开发和维护成本。
-
代码一致性:所有模块使用相同的链表接口,提高了代码可读性和可维护性。
理解内核链表的设计思想,不仅能帮助你更好地理解 Linux 内核源码,也能为你的系统编程提供宝贵的架构经验。
8. 内核链表源码详细注解
#ifndef DLIST_H
#define DLIST_H
//计算偏移地址
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER)
//计算大结构体的首地址
//第一行:设置临时变量_mptr,确保ptr与member类型一致
//第二行:强转(char*) 按字节寻址
#define container_of(ptr,type,member) ({\
const typeof(((type*)0)->member)* _mptr = (ptr);\
(type*)((char*)_mptr - offsetof(type,member));})
struct list_head
{
struct list_head* prev;
struct list_head* next;
};
//初始化链表头结点
#define LIST_HEAD_INIT(name) {&(name),&(name)}
//传变量
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
//传指针
#define INIT_LIST_HEAD(ptr) \
do{ \
(ptr)->next = (ptr);\
(ptr)->prev = (ptr);\
}while(0)
//往两个节点之间插入一个新的节点
static inline void __list_add(struct list_head* pNew,
struct list_head* prev,
struct list_head* next)
{
next->prev = pNew;
pNew->next = next; //先连接尾结点
pNew->prev = prev;
prev->next = pNew; //再连接头节点, 貌似顺序不能随便修改,不然在多进程时会出现一些问题导致链表断裂
}
//头插
static inline void list_add(struct list_head* pNew, struct list_head* head)
{
__list_add(pNew, head, head->next);
}
//尾插, 循环双链表,所以能直接用head->prev访问尾节点
static inline void list_add_tail(struct list_head* pNew, struct list_head* head)
{
__list_add(pNew, head->prev, head);
}
//删除节点,给定一个节点的prev指针和next指针,删除该节点
static inline void __list_del(struct list_head* prev, struct list_head* next)
{
next->prev = prev;
prev->next = next;
}
//删除节点
static inline void list_del(struct list_head* entry)
{
__list_del(entry->prev, entry->next);
//指针指空,避免使用野指针
entry->prev = (void*)0;
entry->next = (void*)0;
}
//删除该节点并且重新初始化该节点
static inline void list_del_init(struct list_head* entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);
}
//删除该节点并且把该节点插入到另一个链表的头部
static inline void list_move(struct list_head* list, struct list_head* head)
{
__list_del(list->prev, list->next);
list_add(list,head);
}
//删除该节点并且把该节点插入到另一个链表的尾部
static inline void list_move_tail(struct list_head* list, struct list_head* head)
{
__list_del(list->prev, list->next);
list_add_tail(list,head);
}
//判断链表是否为空
static inline int list_empty(struct list_head* head)
{
return head->next == head;
}
//合并两个链表 有点像头插
static inline void __list_splice(struct list_head* list, struct list_head* head)
{
struct list_head* first = list->next; //首
struct list_head* last = list->prev; //尾
struct list_head* at = head->next; //头节点的下一个位置
//首和头结点相连
first->prev = head;
head->next = first;
//尾和头结点下一个位置想连
last->next = at;
at->prev = last;
}
//合并两个链表
static inline void list_splice(struct list_head* list, struct list_head* head)
{
//判断list链表是否为空
if (!list_empty(list))
__list_splice(list, head);
}
//合并两个链表,并重新初始化已清空的链表
static inline void list_splice_init(struct list_head* list, struct list_head* head)
{
if (!list_empty(list)) {
__list_splice(list,head);
INIT_LIST_HEAD(list);
}
}
//求出大结构体首地址
#define list_entry(ptr, type , member) \
((type*((char*)(ptr) - (unsigned long)(&((type*)0)->member)))
//下面遍历链表方式有点像迭代器,或者说就是迭代器的遍历方式
//循环向后遍历链表函数
#define list_for_each(pos, head)\
for (pos = (head)->next; pos != (head);\
pos = pos->next)
//循环向前遍历链表函数
#define list_for_each_prev(pos, head)\
for (pos = (head)->prev; pos != (head);\
pos = pos->prev)
//安全遍历链表并且允许删除当前节点
//关键点在于提前存储了下一个节点的地址
#define list_for_each_safe(pos, n, head)\
for (pos = (head)->next, n = pos->next; pos != (head);\
pos = n, n = pos->next)
//遍历大结构体
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member);\
&pos->member != (head);\
pos = list_entry(pos->member.next, typeof(*pos),member))
//安全遍历大结构体并且允许删除当前节点
//关键带你在于提前存储了下一个节点的地址
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member),\
n = list_entry(pos->member.next, typeof(*pos), member);\
&pos->member != (head);\
pos = n, n = list_entry(pos->member.next, typeof(*pos), member))
//list_entry 和 container_of 的 区别
//list_entry仅适用于内核链表
//container_of是一个“成员转结构体”的通用工具,
//list_entry可以依赖于container_of
#endif //DLIST_H