内核链表超详细讲解(含源码)

内核链表

内核链表:设计思想与核心优势

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 确保 ptrmember 类型一致。

  • 字节级计算:将指针转换为 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值