一、前言

本文详细分析Linux 2.6.10内核的list实现,源码位置include/linux/list.h

二、List详细分析

1.毒药指针

用于检测未初始化或已释放的链表节点的非法使用

#define LIST_POISON1  ((void *) 0x00100100)
#define LIST_POISON2  ((void *) 0x00200200)

标记无效的链表指针

  • 当链表节点(struct list_head)被删除或未初始化时,内核会将其 nextprev 指针设置为 LIST_POISON1LIST_POISON2
  • 这些值是非法的指针值(指向内核空间中通常不会映射的地址),如果代码错误地解引用这些指针,会触发页面错误(Page Fault),导致内核崩溃(Oops)并打印调用栈,从而帮助定位问题

防止野指针

  • 如果一个链表节点被释放后,其指针未被清空,后续代码可能误用它。通过填充 LIST_POISON,可以尽早暴露这种错误

实际使用

static inline void list_del(struct list_head *entry)
{
        __list_del(entry->prev, entry->next);
        entry->next = LIST_POISON1;
        entry->prev = LIST_POISON2;
}

2.链表节点和初始化

struct list_head {
    struct list_head *next, *prev;
};

#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)

2.1.链表节点结构体 struct list_head

struct list_head {
    struct list_head *next, *prev;
};
  • 作用:定义了一个双向链表的节点结构。
  • 成员:
    • next:指向下一个节点的指针(类型为 struct list_head*)。
    • prev:指向前一个节点的指针(类型为 struct list_head*)。
  • 特点:
    • 侵入式设计:链表节点是独立的,不包含实际数据。数据结构需要嵌入 struct list_head 作为成员

LIST_HEAD_INIT(name)

#define LIST_HEAD_INIT(name) { &(name), &(name) }
  • 作用:初始化一个链表头节点,使其 nextprev 指针均指向自己(表示空链表)
  • 参数:
    • name:链表头节点的变量名(注意这里是变量名,而非字符串)
  • 展开结果:
    • LIST_HEAD_INIT(my_list) 会展开为 { &(my_list), &(my_list) }
  • 细节:
    • &(name):取变量 name 的地址(即 struct list_head* 类型)
    • 大括号 {}:表示结构体的初始化列表(C 语言语法)
    • 自引用nextprev 都指向自身,形成循环链表的初始状态

2.2.宏 LIST_HEAD(name)

#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)
  • 作用:定义并初始化一个链表头节点

  • 参数:

    • name:链表头节点的变量名
  • 展开结果:

    LIST_HEAD(my_list)
    

    会展开为:

    struct list_head my_list = { &(my_list), &(my_list) };
    

2.3.宏 INIT_LIST_HEAD(ptr)

#define INIT_LIST_HEAD(ptr) do { \
    (ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)
  • 作用:动态初始化一个链表头节点(通常用于已分配的内存或指针变量)

  • 参数:

    • ptr:指向 struct list_head 的指针(注意是指针,而非变量名)
  • 展开结果:

    INIT_LIST_HEAD(&my_list)
    

    会展开为:

    do {
        (&my_list)->next = (&my_list);
        (&my_list)->prev = (&my_list);
    } while (0);
    
  • 细节:

    • do { ... } while (0) 技巧:
      • 将多条语句包装为一个整体,避免宏展开时的语法问题(例如与 if 结合使用时)
      • 确保宏在代码中像一条普通语句(以分号结尾)
    • 指针解引用:
      • (ptr)->next:访问指针 ptr 指向的结构体的 next 成员
      • 这里 ptr 可以是任意合法的 struct list_head* 类型指针

2.4.关键设计思想

  • LIST_HEAD(name):用于静态变量(如全局变量或局部变量)

  • INIT_LIST_HEAD(ptr):用于动态分配的链表头,如 struct list_head *head = kmalloc(...); INIT_LIST_HEAD(head);

  • struct list_head 是通用的,实际数据结构通过嵌入该结构体来支持链表操作(例如 container_of 宏从节点获取宿主结构体,container_of 详细内容可参考博客 https://blog.csdn.net/weixin_51019352/article/details/151870691 container_of 宏是怎么实现的 一节)


3.链表操作函数

3.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);
}
3.1.1.__list_add 函数
static inline void __list_add(struct list_head *new,
                  struct list_head *prev,
                  struct list_head *next)
{
    next->prev = new;  // (1) 将 next 的前驱指向 new
    new->next = next;  // (2) 将 new 的后继指向 next
    new->prev = prev;  // (3) 将 new 的前驱指向 prev
    prev->next = new;  // (4) 将 prev 的后继指向 new
}

作用

prevnext 之间插入一个新节点 new,并维护双向链表的正确性

示意图(插入 newprevnext 之间)

before:
prev <-> next
 
after:
prev <-> new <-> next
3.1.2.list_add 函数
static inline void list_add(struct list_head *new, struct list_head *head)
{
    __list_add(new, head, head->next);  // 在 head 之后插入 new
}

作用

将新节点 new 插入到链表头 head 之后(即作为链表的第一个实际节点

  • new:待插入的新节点。
  • head:链表头节点(通常是 LIST_HEAD 定义的静态或动态初始化的节点)
__list_add(new, head, head->next)
  • prev = head(新节点的前驱是 head

  • next = head->next(新节点的后继是原 head 的后继节点)

  • 效果:新节点 new 成为 head 的直接后继

示意图(在 head 后插入 new

before:
head <-> node1 <-> node2
 
after:
head <-> new <-> node1 <-> node2
3.1.3. list_add_tail 函数
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    __list_add(new, head->prev, head);  // 在 head 之前插入 new
}

作用

将新节点 new 插入到链表头 head 之前(即作为链表的最后一个实际节点

__list_add(new, head->prev, head)
  • prev = head->prev(新节点的前驱是原链表的最后一个节点)

  • next = head(新节点的后继是 head

  • 效果:新节点 new 成为 head 的直接前驱

示意图(在 head 前插入 new

before:
head <-> node1 <-> node2
 
after:
head <-> node1 <-> node2 <-> new
3.1.4.示例代码

使用 list_add(头插法)

LIST_HEAD(my_list); // 初始化链表头
struct list_head new_node;
list_add(&new_node, &my_list); // 插入到链表头部

使用 list_add_tail(尾插法)

LIST_HEAD(my_list);
struct list_head new_node;
list_add_tail(&new_node, &my_list); // 插入到链表尾部

3.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.2.1. __list_del 函数
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
    next->prev = prev;  // 将后驱节点的 prev 指针指向前驱节点
    prev->next = next;  // 将前驱节点的 next 指针指向后驱节点
}
  • 参数:

    • prev:待删除节点的前驱节点(entry->prev)。
    • next:待删除节点的后驱节点(entry->next)。
  • 关键操作:

    • next->prev = prev:让后驱节点的 prev 跳过当前节点,直接指向前驱节点。

    • prev->next = next:让前驱节点的 next 跳过当前节点,直接指向后驱节点。

  • 结果
    链表逻辑上移除了 prevnext 之间的节点,但该节点的 nextprev 指针仍保留原值(可能成为野指针)

3.2.2. list_del 函数
static inline void list_del(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
    entry->next = LIST_POISON1;            // 污染被删除节点的 next 指针
    entry->prev = LIST_POISON2;            // 污染被删除节点的 prev 指针
}
  • 作用
    从链表中删除 entry 节点,并主动污染被删除节点的指针,以检测非法访问

  • 参数:

    • entry:待删除的链表节点(struct list_head 类型)
  • 关键操作:

    • __list_del(entry->prev, entry->next):调用底层函数,解除 entry 与前后节点的连接。

    • entry->next = LIST_POISON1:将 entry->next 赋值为一个特殊值(LIST_POISON1,通常是 0x00100100),用于标记节点已删除

    • entry->prev = LIST_POISON2:将 entry->prev 赋值为另一个特殊值(LIST_POISON2,通常是 0x00200200

  • 目的:

    • 安全性:污染指针后,若代码误操作已删除的节点,如解引用 next/prev),会触发明显的错误
    • 调试:通过检查 LIST_POISON1/2,可以快速定位链表使用后的非法访问

3.3.替换函数

static inline void list_replace_rcu(struct list_head *old, struct list_head *new){
    new->next = old->next;      // 第1行:让new指向old的下一个节点
    new->prev = old->prev;      // 第2行:让new指向old的前一个节点
    smp_wmb();                  // 第3行:内存屏障,避免前两步和后两步乱序执行
    new->next->prev = new;      // 第4行:更新后一个节点的prev指针指向new
    new->prev->next = new;      //第5行:更新前一个节点的next指针指向new
}

smp_wmb()(写内存屏障)确保:

  • 第1-2行的写入操作在第4-5行之前完成

  • 其他CPU看到新指针时,新节点的链接已经正确设置

3.4.移动函数

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);
}

3.5.其他操作函数

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

static inline int list_empty_careful(const struct list_head *head)
{
        struct list_head *next = head->next;
        return (next == head) && (next == head->prev);
}

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)
{
        if (!list_empty(list))
                __list_splice(list, head);
}
  • list_empty:检查链表是否为空

  • list_empty_careful:检查headprevnext是否一致,确保在并发删除时判断是真的链表为空

    • // list_del() 的典型实现(两步操作):
      static inline void list_del(struct list_head *entry)
      {
          entry->next->prev = entry->prev;  // 步骤1:更新后一个节点的prev
          entry->prev->next = entry->next;  // 步骤2:更新前一个节点的next
          // 在两个步骤之间,链表处于不一致状态
      }
      
    • 假设有链表:head ↔ B ↔ C,正在删除B:

      • 步骤1完成后head.next = B, 但C.prev指向head(已更新)
      • 此时检查head->next != head->prev,函数返回false
    • 并发添加并不能检测到,因为节点添加过程中并不会马上修改原先的指针指向

  • list_splice:将list链表插入到head链表的开头位置


4.相关宏定义

4.1.list_entry()宏

#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

从链表节点指针获取包含它的结构体指针

container_of宏详细介绍可参考博客 https://blog.csdn.net/weixin_51019352/article/details/151870691 container_of 宏是怎么实现的 一节

4.2.基本遍历

#define list_for_each(pos, head) \
        for (pos = (head)->next, prefetch(pos->next); pos != (head); \
                pos = pos->next, prefetch(pos->next))
  • pos = (head)->next:将迭代指针pos指向链表的第一个实际节点

  • prefetch(pos->next):预取下一个节点的数据到CPU缓存,不同CPU架构有自己的预取指令

  • 检查当前节点是否为链表头(链表头作为遍历结束标志)

  • pos = pos->next:移动到下一个节点

  • prefetch(pos->next):继续预取下个节点的数据

  • 使用举例

  • struct list_head *pos;
    list_for_each(pos, &head) {
        // 循环体处理每个节点
        printk("Current node: %p\n", pos);
    }
    

4.3.安全遍历

#define list_for_each_safe(pos, n, head) \
    for (pos = (head)->next, n = pos->next; pos != (head); \
        pos = n, n = pos->next)
  • 安全删除:保存下一个节点的指针,允许在遍历时删除当前节点

4.4.高级遍历

#define list_for_each_entry(pos, head, member)                \
    for (pos = list_entry((head)->next, typeof(*pos), member),    \
             prefetch(pos->member.next);            \
         &pos->member != (head);                     \
         pos = list_entry(pos->member.next, typeof(*pos), member),    \
             prefetch(pos->member.next))

用于遍历链表并直接获取包含链表的结构体指针

  • pos:指向包含链表节点的结构体的指针(遍历变量)

  • head:链表头指针

  • member:链表节点在结构体中的成员名

  • list_entry((head)->next, typeof(*pos), member)

    • (head)->next:获取链表的第一个节点
    • typeof(*pos):获取pos指向类型的类型信息,即包含链表节点的结构体的类型
    • member:链表节点在结构体的成员名
    • 作用:通过链表节点指针计算出包含它的结构体的起始地址
  • 使用举例

  • struct my_data {
        int value;
        char name[20];
        struct list_head list;  // 链表节点
    };
    
    // 基础版本,my_list是链表头结点
    struct list_head *pos;
    list_for_each(pos, &my_list) {
        struct my_data *entry = list_entry(pos, struct my_data, list);
        printk("Value: %d\n", entry->value);
    }
    
    // 高级版本
    struct my_data *entry;
    list_for_each_entry(entry, &my_list, list) {
        printk("Value: %d\n", entry->value);
    }
    

5.RCU 相关操作

5.1.RCU插入函数

static inline void __list_add_rcu(struct list_head * new,
                struct list_head * prev, struct list_head * next)
{
        new->next = next;
        new->prev = prev;
        smp_wmb();
        next->prev = new;
        prev->next = new;
}

static inline void list_add_rcu(struct list_head *new, struct list_head *head)
{
        __list_add_rcu(new, head, head->next);
}

static inline void list_add_tail_rcu(struct list_head *new,
                                        struct list_head *head)
{
        __list_add_rcu(new, head->prev, head);
}

这里和正常插入函数的代码类似,唯一不同是这里用到了smp_wmb()smp_wmb()的具体实现可参考博客 https://blog.csdn.net/weixin_51019352/article/details/151968561 smp_wmb()的实现 一节,RCU相关知识可参考博客 https://blog.csdn.net/weixin_51019352/article/details/152009510

        smp_wmb();
  • 这是实现允许多线程并发安全访问的链表数据结构的关键

  • smp_wmb(): 写内存屏障 (Write Memory Barrier)

  • 作用:确保屏障之前的所有写操作(new->next = next;new->prev = prev;)先于屏障之后的所有写操作(next->prev = new;prev->next = new;

  • 为什么需要它?

    • 编译器和CPU会对指令进行重排以优化性能。如果没有屏障,编译器或CPU可能会先执行 next->prev = new;prev->next = new;

    • 如果 prev->next = new; 先于 new->next = next; 执行,那么会发生什么?在某个极短的瞬间,prev->next 指向了一个 next 指针还未正确初始化的新节点 new。如果恰巧在此时,另一个CPU核正在遍历链表,它通过 prev->next 读到了 new,然后去访问 new->next,就会读到一个内核不存在的地址,导致崩溃或错误

  • smp_wmb() 彻底杜绝了这种错误的发生顺序。它保证了新节点自身必须首先变得“完整”(指针全部初始化),然后才能被链入主链表从而变得“可见”

5.2.RCU删除函数

static inline void list_del_rcu(struct list_head *entry)
{
        __list_del(entry->prev, entry->next);
        entry->prev = LIST_POISON2;
}

从 RCU 保护的双向链表中删除 entry 节点,但不立即清理 next 指针

为什么保留 next 指针?

  • RCU 遍历的需求
    RCU 允许读者(遍历链表的线程)在无锁情况下访问链表。如果删除节点时立即清理所有指针,读者可能访问到被污染的指针

5.3.RCU遍历

#define list_for_each_rcu(pos, head) \
        for (pos = (head)->next, prefetch(pos->next); pos != (head); \
                pos = rcu_dereference(pos->next), prefetch(pos->next))

三、哈希链表详细分析

1.哈希链表结构

struct hlist_head {
        struct hlist_node *first;
};

struct hlist_node {
        struct hlist_node *next, **pprev;
};
  • hlist_head是链表头,常用于哈希表的桶数组,比正常链表节点节省内存

  • 怎么节省的内存?

    • // 普通双向链表头(两个指针)
      struct list_head {
          struct list_head *next, *prev;  // 16字节(64位系统)
      };
      
      // 哈希链表头(一个指针)  
      struct hlist_head {
          struct hlist_node *first;       // 8字节(64位系统)
      };
      
      
  • 为什么节省内存?

    • 哈希表的每个桶通常有一个链表头,哈希桶数一般有很多,因此链表头数量也有很多,但每个链表的平均长度短,所以节省链表头内存很有意义
  • hlist_nodepprev 的作用,指向前一个节点的next指针的地址

  • 为什么使用pprev,不使用prev

    • 统一的删除操作,无需判断是否为头节点

      hlist 的一个关键特征是:哈希表数组(struct hlist_head *)中存放的是 struct hlist_node *(即第一个节点的指针)。对于第一个节点来说,它的“前一个指针”是存储在哈希表数组里的

      • 如果使用 prev:在删除第一个节点时,你需要特殊处理,因为 node->prev 指向的是一个 struct hlist_head 结构,而不是 struct hlist_node 结构。你需要判断 node->prev 是另一个节点还是哈希桶的头,这会使代码变得复杂
      • 如果使用 pprev:无论要删除的节点是第一个节点(*pprev 是哈希表数组里的一个桶)还是中间节点(*pprev 是前一个节点的 next 指针),删除操作 *(node->pprev) = node->next; 完全一样,无需任何判断。因为 pprev 存储的就是“指向当前节点”的那个指针的地址。这个设计非常巧妙地将两种情况的逻辑统一了
    • 节省内存(这是最主要的原因)

      hlist 通常用于实现哈希表的拉链法。哈希表有一个很大的数组(struct hlist_head table[TABLE_SIZE]

      • 使用 prevstruct hlist_head 也需要包含一个 prev 指针来保证链表完整性,或者第一个节点的 prev 指针需要指向一个虚拟节点。无论哪种方式,每个哈希桶都相当于多了一个指针的开销。对于一个有 10000 个桶的哈希表,这就浪费了 10000 * 8字节 = 80KB 的内存(在 64 位系统上)
      • 使用 pprevstruct hlist_head 只需要一个单指针(first)。struct hlist_nodepprev 指向的是前一个节点的 next 指针(对于第一个节点,就是 hlist_headfirst 指针)。哈希表数组本身没有额外的内存开销。在拥有大量哈希桶的哈希表中,这种节省是非常可观的
  • 常用于需要高频插入/删除且内存敏感的场景(如哈希表、网络邻居表等)

2.哈希链表节点初始化

#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
#define INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL)

3.哈希链表删除操作

static inline void __hlist_del(struct hlist_node *n)
{
        struct hlist_node *next = n->next;
        struct hlist_node **pprev = n->pprev;
        *pprev = next;
        if (next)
                next->pprev = pprev;
}

static inline void hlist_del(struct hlist_node *n)
{
        __hlist_del(n);
        n->next = LIST_POISON1;
        n->pprev = LIST_POISON2;
}

从哈希链表(hlist)中删除一个节点n

  • *pprev = next

    • pprev 是指向前驱节点的 next 指针的指针(或指向哈希桶的头指针)
    • 通过解引用 pprev,直接修改前驱节点的 next 指针,使其跳过当前节点 n,指向 n->next
    • 如果 n 是头节点pprev 指向哈希桶的头指针,此时 *pprev = next 会直接修改桶的头指针。
  • next->pprev = pprev

    • 如果 n 不是最后一个节点(即 next != NULL),更新 nextpprev 指针,使其指向 n 的前驱指针(即 pprev
    • 这样,next 节点仍然可以通过 pprev 找到链表的前驱关系
  • 不修改 n 本身的指针

    • 删除后,n->nextn->pprev 仍然保持原值,但链表结构已正确更新
    • 这使得 n 可以安全地重新插入到另一个链表中

4.哈希链表添加操作

static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
    struct hlist_node *first = h->first; // 获取当前链表的第一个节点
    n->next = first;                     // 新节点的 next 指向原第一个节点
    if (first)                           // 如果链表非空
        first->pprev = &n->next;         // 原第一个节点的 pprev 指向新节点的 next指针
    h->first = n;                        // 更新链表头指针,指向新节点n
    n->pprev = &h->first;                // 新节点的 pprev 指向链表头的 first 指针
}
  • first 指向当前链表的第一个节点(如果链表为空,则 first = NULL
  • 新节点 nnext 指向原第一个节点 first(即 n 成为新的头节点)
  • 如果 first 非空(即链表之前有节点),则修改 first->pprev,使其指向新节点的 next 指针(即 &n->next
  • 链表头的 first 现在指向新节点 n(即 n 成为链表的第一个节点)
  • 新节点 npprev 指向 h->first(即链表头的 first 指针的地址)

5.哈希链表遍历

#define hlist_entry(ptr, type, member) container_of(ptr,type,member)

#define hlist_for_each(pos, head) \
        for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \
             pos = pos->next)

#define hlist_for_each_safe(pos, n, head) \
        for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
             pos = n)
  • 初始化 (pos = (head)->first)

    • pos 指向链表的第一个节点(head->first
  • 循环条件 (pos && ({ prefetch(pos->next); 1; }))

    • pos:检查当前节点是否非空(即链表是否遍历完毕)

    • ({ prefetch(pos->next); 1; })

  • prefetch(pos->next):预取 pos->next

    • 1:确保整个条件表达式的结果为 true(因为 prefetch 无返回值,而 && 需要一个布尔值)
  • ({ ... }):GCC 扩展语法,允许在表达式中执行多条语句,并将最后一条语句的执行结果返回

  • 迭代 (pos = pos->next)

    • pos 移动到下一个节点(pos->next

6.哈希链表RCU相关操作

6.1.删除操作

static inline void hlist_del_rcu(struct hlist_node *n)
{
        __hlist_del(n);
        n->pprev = LIST_POISON2;
}

5.2.RCU删除函数 解释

6.2.添加操作

static inline void hlist_add_head_rcu(struct hlist_node *n,
                                        struct hlist_head *h)
{
        struct hlist_node *first = h->first;
        n->next = first;
        n->pprev = &h->first;
        smp_wmb();
        if (first)
                first->pprev = &n->next;
        h->first = n;
}

这里将 n->pprev = &h->first;提取到前面执行,是为了确保在添加节点的过程中保证不让RCU读侧读到无效数据

可参考 5.1.RCU插入函数4.哈希链表添加操作 的解释

6.3.遍历操作

#define hlist_for_each_rcu(pos, head) \
        for ((pos) = (head)->first; pos && ({ prefetch((pos)->next); 1; }); \
                (pos) = rcu_dereference((pos)->next))

可参考 5.哈希链表遍历 的解释

四、设计哲学总结

1.侵入式设计(Intrusive Design)

  • 链表节点嵌入到数据结构中

2. 安全性设计

  • 毒药指针:检测use-after-free错误
  • 安全遍历_safe 版本允许在遍历时删除
  • 内存屏障:保证多核并发安全性

3. 多种链表类型

  • 普通双向链表struct list_head
  • 哈希链表struct hlist_head/hlist_node
  • RCU版本:无锁读取支持

4.完整的API生态系统

// 初始化
LIST_HEAD(), INIT_LIST_HEAD()

// 添加
list_add(), list_add_tail()

// 删除  
list_del(), list_del_init()

// 遍历
list_for_each(), list_for_each_entry()

// 移动
list_move(), list_move_tail()

// 判空
list_empty(), list_empty_careful()

// 合并
list_splice(), list_splice_init()

5.重要使用注意事项

  • 初始化必须:使用前必须初始化链表头

  • 并发保护:多核访问时需要额外的锁机制

  • 类型安全:正确使用 list_entrycontainer_of

  • 内存序:RCU操作需要正确使用内存屏障

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐