学习笔记——Linux内核链表

Linux内核链表(list.h)详细分析

一、内核链表核心数据结构

1. 双向循环链表(struct list_head)

复制代码
struct list_head {
    struct list_head *prev;
    struct list_head *next;
};
  • 特点:双向循环链表,头节点也指向自己

  • 使用方式:嵌入到自定义结构体中

2. 单向链表(struct hlist_head/hlist_node)

复制代码
struct hlist_head {
    struct hlist_node *first;
};

struct hlist_node {
    struct hlist_node *next, **pprev;
};
  • 特点:单向链表,适用于哈希表,节省空间

  • 缺点:无法O(1)访问尾节点

二、核心宏定义

1. container_of 宏

复制代码
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
    const typeof(((type *)0)->member) *__mptr = (ptr); \
    (type *)((char *)__mptr - offsetof(type, member)); \
})
  • 作用:通过成员指针找到包含它的结构体

  • 原理:通过成员偏移量计算结构体首地址

2. list_entry 宏

复制代码
#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)
  • 作用:简化container_of的使用

三、链表初始化

1. 静态初始化

复制代码
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)

使用

复制代码
LIST_HEAD(student_list);  // 静态定义并初始化

2. 动态初始化

复制代码
static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}

使用

复制代码
struct list_head head;
INIT_LIST_HEAD(&head);

四、链表基本操作

1. 插入节点

复制代码
static inline void __list_add(struct list_head *new,
                  struct list_head *prev,
                  struct list_head *next)
{
    next->prev = new;
    new->next = next;
    new->prev = prev;
    prev->next = new;
}

// 头插
static inline void list_add(struct list_head *new, 
                           struct list_head *head)
{
    __list_add(new, head, head->next);
}

// 尾插
static inline void list_add_tail(struct list_head *new,
                                struct list_head *head)
{
    __list_add(new, head->prev, head);
}

2. 删除节点

复制代码
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->next = LIST_POISON1;  // 设置毒药指针
    entry->prev = LIST_POISON2;
}

3. 判断链表是否为空

复制代码
static inline int list_empty(const struct list_head *head)
{
    return head->next == head;
}

五、遍历宏

1. 基本遍历(不安全)

复制代码
#define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

2. 安全遍历(支持删除)

复制代码
#define list_for_each_safe(pos, n, head) \
    for (pos = (head)->next, n = pos->next; \
         pos != (head); \
         pos = n, n = pos->next)

3. 遍历结构体(常用)

复制代码
#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))

4. 安全遍历结构体

复制代码
#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(n->member.next, typeof(*n), member))

六、哈希链表(hlist)操作

1. 初始化

复制代码
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
static inline void INIT_HLIST_HEAD(struct hlist_head *h)
{
    h->first = NULL;
}

2. 插入节点

复制代码
static inline void hlist_add_head(struct hlist_node *n,
                                 struct hlist_head *h)
{
    struct hlist_node *first = h->first;
    n->next = first;
    if (first)
        first->pprev = &n->next;
    h->first = n;
    n->pprev = &h->first;
}

3. 遍历宏

复制代码
#define hlist_for_each_entry(tpos, pos, head, member) \
    for (pos = (head)->first; \
         pos && ({ prefetch(pos->next); 1; }) && \
         ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; }); \
         pos = pos->next)

七、实际应用示例

1. 自定义结构体

复制代码
struct Student {
    int id;
    char name[50];
    struct list_head list;  // 嵌入链表节点
};

2. 完整操作流程

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"

LIST_HEAD(student_list);  // 全局链表头

// 添加学生
void add_student(int id, const char *name)
{
    struct Student *new_stu = malloc(sizeof(struct Student));
    new_stu->id = id;
    strcpy(new_stu->name, name);
    
    INIT_LIST_HEAD(&new_stu->list);
    list_add_tail(&new_stu->list, &student_list);
}

// 显示所有学生
void show_students()
{
    struct Student *pos;
    list_for_each_entry(pos, &student_list, list) {
        printf("ID: %d, Name: %s\n", pos->id, pos->name);
    }
}

// 删除学生(安全方式)
void delete_student(int id)
{
    struct Student *pos, *n;
    list_for_each_entry_safe(pos, n, &student_list, list) {
        if (pos->id == id) {
            list_del(&pos->list);
            free(pos);
            return;
        }
    }
}

// 更新学生信息
void update_student(int id, const char *new_name)
{
    struct Student *pos;
    list_for_each_entry(pos, &student_list, list) {
        if (pos->id == id) {
            strcpy(pos->name, new_name);
            return;
        }
    }
}

// 查找学生
struct Student *find_student(int id)
{
    struct Student *pos;
    list_for_each_entry(pos, &student_list, list) {
        if (pos->id == id) {
            return pos;
        }
    }
    return NULL;
}

3. 主函数示例

复制代码
int main()
{
    // 初始化链表
    INIT_LIST_HEAD(&student_list);
    
    // 添加学生
    add_student(1, "Alice");
    add_student(2, "Bob");
    add_student(3, "Charlie");
    
    // 显示
    show_students();
    
    // 更新
    update_student(2, "Bobby");
    
    // 查找
    struct Student *stu = find_student(2);
    if (stu) {
        printf("Found: %s\n", stu->name);
    }
    
    // 删除
    delete_student(1);
    
    // 再次显示
    show_students();
    
    return 0;
}

八、自实现内核链表(klist)

1. 核心宏定义

复制代码
// 计算成员偏移
#define offset(type, mem) ((size_t) &((type*)0)->mem)

// 通过成员指针找到结构体
#define containerof(p, type, mem) ({ \
    const typeof(((type*)0)->mem) *_mptr = (p); \
    (type*)((char*)_mptr - offset(type, mem)); \
})

// 遍历宏
#define klist_for_each(p, n, head, node) \
    for(p = containerof((head)->next, typeof(*p), node), \
        n = containerof(p->node.next, typeof(*p), node); \
        &p->node != (head); \
        p = n, n = containerof(n->node.next, typeof(*n), node))

2. 主要函数

复制代码
// 初始化
void klist_init(KLIST *l)
{
    l->next = l;
    l->prev = l;
}

// 添加节点(通用)
static void klist_add(KLIST *new, KLIST *prev, KLIST *next)
{
    new->prev = prev;
    new->next = next;
    prev->next = new;
    next->prev = new;
}

// 头插
void klist_add_head(KLIST *new, KLIST *head)
{
    klist_add(new, head, head->next);
}

// 尾插
void klist_add_tail(KLIST *new, KLIST *head)
{
    klist_add(new, head->prev, head);
}

// 删除
void klist_del(KLIST *node)
{
    node->prev->next = node->next;
    node->next->prev = node->prev;
    node->next = NULL;
    node->prev = NULL;
}

九、关键知识点总结

1. 内核链表特点

  • 嵌入方式:list_head嵌入到结构体中

  • 类型无关:操作与数据类型无关

  • 双向循环:头节点也参与循环

  • 高性能:使用内联函数减少函数调用开销

2. 容器宏原理

复制代码
// 假设结构体:
struct Person {
    int age;
    struct list_head list;
    char name[20];
};

// 计算list成员偏移:
// &((struct Person*)0)->list = 4(age占4字节)

// 通过list指针找到Person:
// Person* = list指针 - 4

3. 安全遍历的重要性

  • list_for_each_entry_safe:允许在遍历时删除节点

  • 保存下一个节点:防止访问已释放内存

  • 应用场景:需要删除匹配节点的遍历

4. 使用技巧

复制代码
// 1. 定义链表头
static LIST_HEAD(my_list);

// 2. 定义结构体
struct MyData {
    int value;
    struct list_head list;
};

// 3. 添加节点
struct MyData *data = kmalloc(sizeof(*data), GFP_KERNEL);
data->value = 100;
INIT_LIST_HEAD(&data->list);
list_add_tail(&data->list, &my_list);

// 4. 遍历节点
struct MyData *pos;
list_for_each_entry(pos, &my_list, list) {
    printk("Value: %d\n", pos->value);
}

// 5. 删除所有节点
struct MyData *pos, *n;
list_for_each_entry_safe(pos, n, &my_list, list) {
    list_del(&pos->list);
    kfree(pos);
}

十、常见问题解答

Q1:为什么使用双向循环链表?

  • 可以从任意节点开始遍历

  • 删除节点时不需要知道前驱节点

  • 头节点也参与循环,简化边界条件判断

Q2:hlist和list_head的区别?

  • list_head:双向循环,每个节点有两个指针

  • hlist:单向,头节点只有一个指针,节点有next和**pprev(二级指针),节省空间

Q3:为什么要有list_for_each_safe?

  • 普通遍历在删除当前节点后,next指针会失效

  • safe版本保存下一个节点,避免访问无效内存

Q4:LIST_POISON1/2的作用?

  • 标识已删除的节点

  • 帮助调试,访问已删除节点时会产生段错误

相关推荐
sheeta19982 小时前
LeetCode 每日一题笔记 日期:2025.12.14 题目:2147.分隔长廊的方案数
linux·笔记·leetcode
徐子元竟然被占了!!2 小时前
Linux-tar
linux
发疯幼稚鬼2 小时前
插入排序与冒泡排序
c语言·数据结构·算法·排序算法
艾莉丝努力练剑2 小时前
【Linux进程(一)】深入理解计算机系统核心:从冯·诺依曼体系结构到操作系统(OS)
java·linux·运维·服务器·git·编辑器·操作系统核心
宋军涛2 小时前
记一次服务器异常宕机导致的系统异常
运维·服务器
阿蒙Amon2 小时前
JavaScript学习笔记:8.日期和时间
javascript·笔记·学习
被制作时长两年半的个人练习生2 小时前
使用rvv优化rms_norm
linux·llama·risc-v
暗然而日章2 小时前
C++基础:Stanford CS106L学习笔记 10 函数模板(Function Templates)
c++·笔记·学习
swan4162 小时前
SCAU期末笔记 - 实时计算框架章末实验
笔记