【数据结构】链表(3):Linux 的内核链表

  金牌会员 | 2025-1-3 11:45:03 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 858|帖子 858|积分 2574

Linux 内核链表是一种高效、灵活的双向循环链表实现,广泛用于内核开辟中(如进程调理、设备驱动、文件体系等)。它基于 C 语言的宏和结构体嵌套实现,并提供了一组通用的接口函数,使得链表操纵高效且安全。
内核链表

  1. vim /usr/src/linux-headers-5.4.0-150-generic/include/linux/list.h
  2. vim /usr/src/linux-headers-5.4.0-150-generic/include/linux/types.h
  3. 本质:就是双向循环链表
  4. struct list_head{
  5.         struct list_head *next,*prev;
  6. };
复制代码

这是Linux 内核链表的核心设计,由一个大结构体嵌套着一个小的结构体。拆解:

  • 核心数据结构:list_head
Linux 内核链表的基础是一个通用结构体 list_head,用于表现节点之间的链接关系。
  1. struct list_head {
  2.     struct list_head *next; // 指向下一个节点
  3.     struct list_head *prev; // 指向前一个节点
  4. };
  5. // 每个链表节点都有一个 list_head 类型的成员,用于链接前后节点。
  6. // 通过这种方式实现了双向循环链表。
复制代码

  • 链表头
链表的头节点也是一个 list_head 结构体,但它不存储数据,仅用于标志链表的起点和止境。


  • 假如链表为空,head->next 和 head->prev 都指向自己。
1> 内核链表的初始化操纵

  1. #define LIST_HEAD_INIT(name) { &(name), &(name) }
  2. #define LIST_HEAD(name) \
  3.         struct list_head name = LIST_HEAD_INIT(name)
  4. 1>第一种初始化
  5.     struct list_head name = LIST_HEAD_INIT(name)
  6.         
  7.      struct list_head name = { &(name), &(name) }
  8.      /*自己指向自己*/
  9.          struct list_head *next = &(name);
  10.      struct list_head *prev = &(name);
  11. 2>第二种初始化
  12. static inline void INIT_LIST_HEAD(struct list_head *list)
  13. {
  14.         list->next = list;
  15.         list->prev = list;
  16. }
  17. /*inline :内联函数,可以写在头文件,当调用时,直接将函数中的内容拷贝过去
  18.   简单特性,如果你的函数中有复杂的运算和循环结构,inline修饰的函数会退化成普通函数
  19. */   
  20. WRITE_ONCE : 本质与volatile一致
  21. 1>声明这个变量很重要,不要把它当成普通变量处理,做出错误的优化
  22. 2>保证cpu每次都从内存中读取数据,而不是直接向寄存器取值
复制代码
2> 内核链表的插入操纵

  1. static inline void __list_add(struct list_head *new,
  2.                               struct list_head *prev,
  3.                               struct list_head *next)
  4. {
  5.         next->prev = new;
  6.         new->next = next;
  7.         new->prev = prev;
  8.         prev->next = new;
  9. }
  10. 解说:
  11.     汤姆(prev)  杰瑞(new)  白猫(next)
  12.     new会插在prev和next之间
  13.    
  14. static inline void list_add(struct list_head *new, struct list_head *head)
  15. {
  16.         __list_add(new, head, head->next);
  17. }
  18. 解说: new会插在head和head->next之间,实现头插
  19.    
  20. static inline void list_add_tail(struct list_head *new, struct list_head *head)
  21. {
  22.         __list_add(new, head->prev, head);
  23. }
  24. 解说:new会插在head->prev和head之间,实现尾插
复制代码
3> 内核链表的删除操纵

  1. static inline void __list_del(struct list_head * prev, struct list_head * next)
  2. {
  3.         next->prev = prev;
  4.         prev->next = next;
  5. }
  6. 解说:使prev和next连接起来
  7.    
  8. static inline void __list_del_entry(struct list_head *entry)
  9. {
  10.         __list_del(entry->prev, entry->next);
  11. }
  12. 解说:entry->prev和entry->next连接起来了
  13.     entry剪切出去了
  14.    
  15.    
  16. static inline void list_del(struct list_head *entry)
  17. {
  18.         __list_del(entry->prev, entry->next);
  19.         entry->next = LIST_POISON1;
  20.         entry->prev = LIST_POISON2;
  21. }
  22. #define LIST_POISON1  ((void *) 0x00100100 + 0)
  23. #define LIST_POISON2  ((void *) 0x00200200 + 0)
  24. NULL ===》 0x00000000 ~ 0x08048000
  25. 解说:将entry剪切出去,为了安全使两个指针域指向NULL
  26.    
  27. static inline void list_del_init(struct list_head *entry)
  28. {
  29.         __list_del_entry(entry);
  30.         INIT_LIST_HEAD(entry);
  31. }
  32. 解说:将entry剪切出去,为了安全使两个指针域指向自己
复制代码
4> 内核链表的更换操纵

  1. static inline void list_replace(struct list_head *old,
  2.                                 struct list_head *new)
  3. {
  4.         new->next = old->next;
  5.         new->next->prev = new;
  6.         new->prev = old->prev;
  7.         new->prev->next = new;
  8. }
  9. 解说:new结点取代old结点
  10.    
  11. static inline void list_replace_init(struct list_head *old,
  12.                                         struct list_head *new)
  13. {
  14.         list_replace(old, new);
  15.         INIT_LIST_HEAD(old); //初始化,自己指向自己
  16. }
  17. 解说:new结点取代old结点,让old结点的prev和next指向自己
复制代码
5> 内核链表的移动操纵

  1. static inline void list_move(struct list_head *list, struct list_head *head)
  2. {
  3.         __list_del_entry(list);
  4.         list_add(list, head);
  5. }
  6. 解说:将list剪切出来
  7.      进行头插
  8.    
  9.    
  10. static inline void list_move_tail(struct list_head *list,
  11.                                   struct list_head *head)
  12. {
  13.         __list_del_entry(list);
  14.         list_add_tail(list, head);
  15. }
  16. 解说:将list剪切出来
  17.      进行尾插
复制代码
6> 内核链表的遍历操纵

  1. /**
  2. * list_entry - get the struct for this entry
  3. * @ptr:        the &struct list_head pointer.
  4. * @type:        the type of the struct this is embedded in.
  5. * @member:        the name of the list_struct within the struct.
  6. */
  7. #define list_entry(ptr, type, member) \
  8.         container_of(ptr, type, member)
  9. 分析:
  10.     功能:通过小结构体的地址得到大结构体的地址
  11.     ptr   : 小结构体的地址
  12.     type  : 大结构体的类型
  13.     member: 大结构体中小结构体的名字
  14.         
  15. container_of(ptr, type, member)
  16.       
  17. #define container_of(ptr, type, member) ({                        \
  18.         const typeof( ((type *)0)->member ) *__mptr = (ptr);        \
  19.         (type *)( (char *)__mptr - offsetof(type,member) );})           
  20. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
  21.         
  22. ({                        \
  23.         const typeof( ((type *)0)->member ) *__mptr = (ptr);        \
  24.         (type *)( (char *)__mptr - offsetof(type,member) );})         
  25. container_of做以下两个操作:
  26.       操作一://(保存小结构体的地址)
  27.         1> ((type *)0)->member  ==》list(指向大结构体的小结构体成员)  
  28.         2>typeof()  --> 取类型
  29.                 例子: typeof(10)  --> int
  30.         3>typeof( ((type *)0)->member )  ==> typeof( list )
  31.                 ===>struct list_head
  32.                
  33.                   const struct list_head *__mptr = (ptr);      
  34.        操作二://(求出大结构体的地址)
  35.                 (type *)( (char *)__mptr - offsetof(type,member) );
  36.         #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
  37.                 ( (char *)__mptr - ((size_t) &((TYPE *)0)->MEMBER );
  38.          
  39.                 (char *)__mptr : 对小结构体的地址进行char *转 ,让其地址偏移量为1字节
  40.         ((size_t) &((TYPE *)0)->MEMBER ://计算小结构体到大结构体的偏移量
  41.                  (size_t) == int
  42.             &((TYPE *)0)->MEMBER :
  43.                  (TYPE *)0 :指向大结构体的地址0x00000000
  44.                  ((TYPE *)0)->MEMBER : list
  45.                           &list  //偏移量的体现
复制代码

  1. #define list_for_each(pos, head) \
  2.         for (pos = (head)->next; pos != (head); pos = pos->next)
  3. 解说:
  4.    pos:用于循环的中间变量,类似与for循环中i(小结构指针)
  5.    head :头结点的小结构指针类型
  6.    正序遍历
  7.    
  8.    
  9. #define list_for_each_prev(pos, head) \
  10.         for (pos = (head)->prev; pos != (head); pos = pos->prev)
  11. 解说:
  12.    pos:用于循环的中间变量,类似与for循环中i(小结构指针)
  13.    head :头结点的小结构指针类型
  14.    逆序遍历
  15.    
  16.    
  17. #define list_for_each_entry(pos, head, member)                                \
  18.         for (pos = list_entry((head)->next, typeof(*pos), member);        \
  19.              &pos->member != (head);         \
  20.              pos = list_entry(pos->member.next, typeof(*pos), member))
  21. 解说:
  22.     pos:用于循环的中间变量,类似于for的i(大结构指针)
  23.     head:头结点的小结构体指针类型
  24.     member:大结构体中小结构体的名字
  25.     正序遍历
  26.    
  27.    
  28. #define list_for_each_entry_reverse(pos, head, member)                        \
  29.         for (pos = list_entry((head)->prev, typeof(*pos), member);        \
  30.              &pos->member != (head);         \
  31.              pos = list_entry(pos->member.prev, typeof(*pos), member))
  32. 解说:
  33.     pos:用于循环的中间变量,类似于for的i(大结构指针)
  34.     head:头结点的小结构体指针类型
  35.     member:大结构体中小结构体的名字
  36.     逆序遍历   
复制代码
7> 内核链表的删除函数

  1. void link_del(link_t *p,datatype data)
  2. {
  3.     link_t *pos = NULL; //类似for循环的i
  4.     link_t *node = NULL;
  5.     //1>遍历链表
  6.     /*#define list_for_each_entry(pos, head, member)                                \
  7.         for (pos = list_entry((head)->next, typeof(*pos), member);        \
  8.              &pos->member != (head);         \
  9.              pos = list_entry(pos->member.next, typeof(*pos), member))*/
  10.     list_for_each_entry(pos, &p->list, list)
  11.     {
  12.         //数据对比
  13.         if(pos->data == data)
  14.         {
  15.             //中间变量
  16.             node = pos;
  17.             //超秀操作  获得要删除结点前一个结点的地址
  18.             pos = list_entry(pos->list.prev, typeof(*pos), list);
  19.             //删除与释放
  20.             list_del_init(&node->list);
  21.             free(node);
  22.         }   
  23.     }
  24. }
复制代码

8> 内核链表的更换操纵

  1. void link_replace(link_t *p,datatype old,datatype new)
  2. {
  3.     link_t *pos = NULL; //类似for循环的i
  4.     link_t *new_node = NULL; //类似for循环的i
  5.     //1>遍历
  6.     list_for_each_entry(pos, &p->list, list)
  7.     {
  8.         //数据对比
  9.         if(pos->data == old)
  10.         {
  11.             //新创建一个结点
  12.             new_node = create_node(new);
  13.             if(NULL == new_node)
  14.             {
  15.                 perror("create_node");
  16.                 return;
  17.             }
  18.             //替换结点
  19.             list_replace_init(&pos->list,&new_node->list);
  20.             //释放旧结点
  21.             free(pos);
  22.             //将pos指向新的替换结点的地址
  23.             pos = new_node;
  24.         }   
  25.     }
  26. }
复制代码
Linux 内核链表的使用方法


  • 界说链表和节点:
界说链表头:
  1. LIST_HEAD(my_list);
复制代码
界说节点结构体:
  1. struct my_struct {
  2.     int data;
  3.     struct list_head list; // 嵌入链表节点
  4. };
复制代码

  • 添加节点:
  1. #include <stdio.h>#include <stdlib.h>#include "list.h" // 假设链表宏界说和函数在list.h中struct my_struct {    int data;    struct list_head list;};int main() {    LIST_HEAD(my_list);
  2. // 初始化链表头    // 创建并添加节点    for (int i = 0; i < 5; i++) {        struct my_struct *new_node = (struct my_struct *)malloc(sizeof(struct my_struct));        new_node->data = i;        list_add_tail(&new_node->list, &my_list); // 添加到链表尾部    }    // 遍历链表    struct list_head *pos;    struct my_struct *entry;    list_for_each(pos, &my_list) {        entry = list_entry(pos, struct my_struct, list);        printf("Data: %d\n", entry->data);    }    return 0;}
复制代码

  • 删除节点:
  1. // 删除所有节点
  2. struct list_head *pos, *n;
  3. list_for_each_safe(pos, n, &my_list) {
  4.     struct my_struct *entry = list_entry(pos, struct my_struct, list);
  5.     list_del(pos); // 从链表中删除
  6.     free(entry);   // 释放内存
  7. }
复制代码

  • 适配更多场景:
通过 list_for_each_entry 宏直接遍历包含链表节点的结构体:
  1. #define list_for_each_entry(pos, head, member) \
  2.     for (pos = list_entry((head)->next, typeof(*pos), member); \
  3.          &pos->member != (head); \
  4.          pos = list_entry(pos->member.next, typeof(*pos), member))
复制代码
Linux 内核链表的长处

内核链表具有通用性:list_head 和相关宏界说可以适配任何结构体,无需额外界说链表节点结构。高效性:插入、删除操纵时间复杂度为 O(1)。灵活性:支持双向、循环、动态扩展等多种操纵。内存安全:提供了防止悬空指针和错误访问的机制。
我们以一个模拟的任务调理体系为例,演示如何利用Linux 内核链表管理多个任务。这个例子演示了链表更为直观的实际应用场景:
任务调理场景形貌

假设我们须要管理多个任务,每个任务有以下信息:

  • 任务 ID:唯一标识任务。
  • 任务优先级:用于决定任务的执行顺序(高优先级任务优先执行)。
  • 任务状态:表现任务是运行中、等候中照旧完成。
通过内核链表,我们可以实现如下功能:

  • 动态插入新任务。
  • 根据条件删除任务(例如任务完成)。
  • 按优先级遍历任务列表。
  • 模拟任务的调理行为。
1> 数据结构界说

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. // 内核链表头文件(假设 `list.h` 中定义了 Linux 链表的相关宏和函数)
  5. #include "list.h"
  6. // 定义任务结构体
  7. struct task {
  8.     int task_id;               // 任务 ID
  9.     int priority;              // 任务优先级
  10.     char status[20];           // 任务状态(running, waiting, completed)
  11.     struct list_head list;     // 链表节点,用于将任务加入链表
  12. };
复制代码
2> 初始化链表

  1. // 定义链表头,用于管理任务列表
  2. LIST_HEAD(task_list);
复制代码
3> 添加任务

我们将任务按优先级插入链表中,确保链表始终按优先级从高到低分列。
  1. // 添加任务到链表,按优先级插入
  2. void add_task(int task_id, int priority, const char *status) {
  3.     // 创建新任务节点
  4.     struct task *new_task = (struct task *)malloc(sizeof(struct task));
  5.     new_task->task_id = task_id;
  6.     new_task->priority = priority;
  7.     strcpy(new_task->status, status);
  8.     // 遍历链表,找到插入位置
  9.     struct list_head *pos;
  10.     struct task *entry;
  11.     list_for_each(pos, &task_list) {
  12.         entry = list_entry(pos, struct task, list);
  13.         if (priority > entry->priority) {
  14.             // 在优先级较低的任务之前插入
  15.             list_add_tail(&new_task->list, pos->prev);
  16.             return;
  17.         }
  18.     }
  19.     // 如果链表为空或优先级最低,插入到链表末尾
  20.     list_add_tail(&new_task->list, &task_list);
  21. }
复制代码
4> 删除任务

我们可以根据任务 ID 或任务状态删除任务,例如删除全部状态为 “completed” 的任务。
  1. // 根据任务 ID 删除任务
  2. void delete_task_by_id(int task_id) {
  3.     struct list_head *pos, *n;
  4.     struct task *entry;
  5.     // 遍历链表找到目标任务
  6.     list_for_each_safe(pos, n, &task_list) {
  7.         entry = list_entry(pos, struct task, list);
  8.         if (entry->task_id == task_id) {
  9.             list_del(pos); // 从链表中删除节点
  10.             free(entry);   // 释放内存
  11.             printf("Task %d deleted.\n", task_id);
  12.             return;
  13.         }
  14.     }
  15.     printf("Task %d not found.\n", task_id);
  16. }
  17. // 删除所有完成的任务
  18. void delete_completed_tasks() {
  19.     struct list_head *pos, *n;
  20.     struct task *entry;
  21.     list_for_each_safe(pos, n, &task_list) {
  22.         entry = list_entry(pos, struct task, list);
  23.         if (strcmp(entry->status, "completed") == 0) {
  24.             list_del(pos);
  25.             free(entry);
  26.             printf("Completed task deleted.\n");
  27.         }
  28.     }
  29. }
复制代码
5> 遍历任务

我们可以按链表顺序(即优先级顺序)遍历全部任务,并打印任务信息。
  1. // 打印任务列表
  2. void print_tasks() {
  3.     struct list_head *pos;
  4.     struct task *entry;
  5.     printf("Task List:\n");
  6.     printf("ID    Priority    Status\n");
  7.     printf("=============================\n");
  8.     list_for_each(pos, &task_list) {
  9.         entry = list_entry(pos, struct task, list);
  10.         printf("%-5d %-10d %-10s\n", entry->task_id, entry->priority, entry->status);
  11.     }
  12.     printf("\n");
  13. }
复制代码
6> 模拟任务调理

我们可以通过修改任务状态来模拟任务的调理行为。
  1. // 模拟任务调度
  2. void simulate_task_execution() {
  3.     struct list_head *pos;
  4.     struct task *entry;
  5.     list_for_each(pos, &task_list) {
  6.         entry = list_entry(pos, struct task, list);
  7.         if (strcmp(entry->status, "waiting") == 0) {
  8.             // 将第一个等待中的任务标记为运行中
  9.             strcpy(entry->status, "running");
  10.             printf("Task %d is now running.\n", entry->task_id);
  11.             break;
  12.         }
  13.     }
  14. }
复制代码
7> 主函数(main.c)

  1. int main() {
  2.     // 初始化任务列表
  3.     add_task(1, 10, "waiting");
  4.     add_task(2, 20, "waiting");
  5.     add_task(3, 15, "waiting");
  6.     add_task(4, 5, "waiting");
  7.     print_tasks(); // 打印初始任务列表
  8.     // 模拟任务调度
  9.     printf("Simulating task execution:\n");
  10.     simulate_task_execution();
  11.     print_tasks();
  12.     // 删除完成的任务(目前没有完成任务)
  13.     delete_completed_tasks();
  14.     // 删除任务 ID 为 3 的任务
  15.     printf("Deleting task with ID 3:\n");
  16.     delete_task_by_id(3);
  17.     print_tasks();
  18.     // 添加新任务
  19.     printf("Adding new task:\n");
  20.     add_task(5, 25, "waiting");
  21.     print_tasks();
  22.     return 0;
  23. }
复制代码
运行效果示例

  1. Task List:
  2. ID    Priority    Status
  3. =============================
  4. 2     20         waiting   
  5. 3     15         waiting   
  6. 1     10         waiting   
  7. 4     5          waiting   
  8. Simulating task execution:
  9. Task 2 is now running.
  10. Task List:
  11. ID    Priority    Status
  12. =============================
  13. 2     20         running   
  14. 3     15         waiting   
  15. 1     10         waiting   
  16. 4     5          waiting   
  17. Deleting task with ID 3:
  18. Task 3 deleted.
  19. Task List:
  20. ID    Priority    Status
  21. =============================
  22. 2     20         running   
  23. 1     10         waiting   
  24. 4     5          waiting   
  25. Adding new task:
  26. Task List:
  27. ID    Priority    Status
  28. =============================
  29. 5     25         waiting   
  30. 2     20         running   
  31. 1     10         waiting   
  32. 4     5          waiting
复制代码
代码拆解分析:


  • 动态优先级插入:任务根据优先级动态插入到链表的适当位置,确保链表始终按优先级从高到低排序。
  • 链表操纵灵活性

    • 使用 list_add_tail() 和 list_del() 等操纵,轻松实现插入、删除等功能。
    • list_for_each_safe() 确保在遍历过程中安全删除节点。

  • 模拟调理:通过修改任务状态(如将 waiting 改为 running),模拟任务的执行。
  • 代码扩展性:可以轻松扩展其他功能,如暂停任务、重新调解优先级等。
这里先为各人介绍了Linux中内核链表的完备写法,然后用例子展示了 Linux 内核链表在动态任务管理中的应用,在实际开辟中,Linux 内核链表广泛用于管理类似的动态数据结构,例如网络数据包队列、设备驱动哀求队列等场景。内核链表具有很多利益:

  • 动态优先级插入和排序。
  • 高效的插入、删除、遍历操纵。
  • 扩展性强,得当复杂任务调理场景。
综上。Linux 内核链表利用 C 语言的灵活性和宏界说实现了简便、高效的双向循环链表。它的核心设计特点包罗:

  • 基于 list_head 实现通用双向循环链表。
  • 提供了一组操纵宏和辅助函数,简化链表操纵。
  • 通过结构体嵌套和 container_of 实现灵活的节点管理。
把握 Linux 内核链表的原理和使用方法,不但有助于理解内核开辟,还能在其他 C 语言项目中鉴戒实现高效的数据结构。
综上。希望该内容能对你有帮助,感谢!
以上。仅供学习与分享交换,请勿用于商业用途!转载需提前说明。
我是一个非常热爱技术的程序员,希望这篇文章可以或许对您有帮助,也希望认识更多热爱程序开辟的小同伴。
感谢!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表