当前位置:   article > 正文

Linux内核链表理解与使用

内核链表

一. Linux内核链表简介

        Linux内核中需要经常用到双链表,该链表只有指针域,没有数据域。在很多的数据结构中都会嵌入struct list_head结构体变量,它可以使结构体加入到一个双向链表中。链表的初始化,增加,删除等操作的接口在linux-x.x.x/include/linux/list.h里面,内核链表在内核中使用的是如此广泛,所以需要深刻的理解。

二. Linux内核链表使用介绍

1. 基本数据结构

  1. struct list_head {
  2. struct list_head *next, *prev;
  3. };

        从结构体成员看,内核链表是一个双向链表,没有数据域,next指向下一个链表元素,prev指向上一个链表元素。

2. 链表的初始化

  1. #define LIST_HEAD_INIT(name) { &(name), &(name) }
  2. #define LIST_HEAD(name) \
  3. struct list_head name = LIST_HEAD_INIT(name)

        链表声明和初始化的通常用LIST_HEAD(name)处理。LIST_HEAD定义了一个变量struct list变量name,并且name的next和prev指针都指向name的地址。如下图所示

 3. 链表元素添加

  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. static inline void list_add(struct list_head *new, struct list_head *head)
  12. {
  13. __list_add(new, head, head->next);
  14. }
  15. # 向尾部添加 (在链表最后一个元素和链表头指针之间插入元素)
  16. static inline void list_add_tail(struct list_head *new, struct list_head *head)
  17. {
  18. __list_add(new, head->prev, head);
  19. }

        __list_add函数的作用是在prev和next指针指向的元素之间插入new元素。如下图所示。

        list_add函数向__list_add函数传入的prev和next参数分别是head和head->next,所以list_add函数是在链表头指针和链表第一个元素之间插入new元素。

        list_add_tail函数向__list_add函数传入的prev和next指针分别是head->prev和head,head->prev就是指向链表的最后一个元素,所以list_add_tail是在链表最后一个元素和链表头指针之间插入元素。

4. 链表元素删除

  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. static inline void __list_del_entry(struct list_head *entry)
  7. {
  8. __list_del(entry->prev, entry->next);
  9. }
  10. static inline void list_del(struct list_head *entry)
  11. {
  12. __list_del(entry->prev, entry->next);
  13. entry->next = LIST_POISON1;
  14. entry->prev = LIST_POISON2;
  15. }

        _list_del函数的作用是删除prev和next指针指向的元素之间的元素,如下图所示

         __list_del_entry函数向__list_del函数传入的的prev和next参数分别指向是要删除元素的前一个元素和后一个元素。

        list_del函数和__list_del_entry函数的区别是将删除元素的前向指针和后向指针都指向一个非法值,如元素从链表中删除后仍然反问,则会产生内存页错误。

5. 链表的遍历

  1. /**
  2. * list_for_each - iterate over a list
  3. * @pos: the &struct list_head to use as a loop cursor.
  4. * @head: the head for your list.
  5. */
  6. #define list_for_each(pos, head) \
  7. for (pos = (head)->next; pos != (head); pos = pos->next)
  8. /**
  9. * list_for_each_prev - iterate over a list backwards
  10. * @pos: the &struct list_head to use as a loop cursor.
  11. * @head: the head for your list.
  12. */
  13. #define list_for_each_prev(pos, head) \
  14. for (pos = (head)->prev; pos != (head); pos = pos->prev)
  15. /**
  16. * list_for_each_safe - iterate over a list safe against removal of list entry
  17. * @pos: the &struct list_head to use as a loop cursor.
  18. * @n: another &struct list_head to use as temporary storage
  19. * @head: the head for your list.
  20. */
  21. #define list_for_each_safe(pos, n, head) \
  22. for (pos = (head)->next, n = pos->next; pos != (head); \
  23. pos = n, n = pos->next)
  24. /**
  25. * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
  26. * @pos: the &struct list_head to use as a loop cursor.
  27. * @n: another &struct list_head to use as temporary storage
  28. * @head: the head for your list.
  29. */
  30. #define list_for_each_prev_safe(pos, n, head) \
  31. for (pos = (head)->prev, n = pos->prev; \
  32. pos != (head); \
  33. pos = n, n = pos->prev)
  34. /**
  35. * list_for_each_entry - iterate over list of given type
  36. * @pos: the type * to use as a loop cursor.
  37. * @head: the head for your list.
  38. * @member: the name of the list_struct within the struct.
  39. */
  40. #define list_for_each_entry(pos, head, member) \
  41. for (pos = list_first_entry(head, typeof(*pos), member); \
  42. &pos->member != (head); \
  43. pos = list_next_entry(pos, member))

        list_for_each函数是按照从前往后的顺序遍历链表,通过不断指向元素的next元素,直到元素的指针和链表头指针地址相同,则表示链表遍历完成。

        list_for_each_prev函数则是从链表的尾部元素向前遍历。

        list_for_each_safe函数引入了指针n,用于存储pos的下一个元素的地址。引入指针n可以方便在遍历链表的时候删除pos指向的元素,而不影响遍历。list_for_each无法做到这一点。

        list_for_each_prev_safe函数和list_for_each_safe函数的区别是从后往前遍历。

        list_for_each_entry函数是list_for_each和list_entry的结合,有pos,head,member三个参数,pos是一个中间变量,指向当前访问的链表元素,head指链表头,member指pos指向的结构体中链表成员变量的名称,示意图如下:

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)

        list_entry宏有三个参数ptr,type,member。ptr是指数据结构中struct list_head变量成员的地址,type是指数据结构的类型,member是指数据结构中struct list_head的变量名。list_entry宏的结果是ptr指向的type类型的数据结构的变量地址。

示意图如下:

        

代码示例

  1. struct task_struct {
  2. ...
  3. struct list_head tasks;
  4. ...
  5. };

        如果要获取某个struct task_struct类型的变量的地址,ptr指向该变量的tasks指针变量的地址,则可以这么写

struct task_struct *addr = list_entry(ptr, struct task_struct, tasks);

举例:list_for_each和list_entry配合使用

        list_for_each函数用于获取数据结构中struct list_head成员的地址,list_entry则是通过struct list_head成员的地址获取到数据结构的地址。

代码示例:

  1. list_for_each(pos, &list)
  2. {
  3. struct task_struct *addr = list_entry(pos, struct task_struct, tasks);
  4. }

三. 总结

代码示例:

  1. #include <linux/init.h>
  2. #include <linux/slab.h>
  3. #include <linux/module.h>
  4. #include <linux/kernel.h>
  5. #include <linux/list.h>
  6. #include <linux/rculist.h>
  7. struct stu_info
  8. {
  9. char* name;
  10. int age;
  11. void (*print)(struct stu_info *stu);
  12. struct list_head list;
  13. };
  14. void print_info(struct stu_info *stu)
  15. {
  16. printk("name = %s, age = %d\n", stu->name, stu->age);
  17. }
  18. struct stu_info students[8] = {
  19. {"Zhao", 20, print_info},
  20. {"Qian", 21, print_info},
  21. {"Sun", 22, print_info},
  22. {"Li", 23, print_info},
  23. };
  24. LIST_HEAD(stu);
  25. static int student_list_init(void)
  26. {
  27. struct list_head *pos;
  28. struct stu_info *s;
  29. list_add(&students[0].list, &stu);
  30. list_add(&students[1].list, &stu);
  31. list_add(&students[2].list, &stu);
  32. list_add(&students[3].list, &stu);
  33. list_for_each(pos, &stu)
  34. {
  35. s = list_entry(pos, struct stu_info, list);
  36. s->print(s);
  37. }
  38. return 0;
  39. }
  40. static void student_list_exit(void)
  41. {
  42. printk("student_list_exit\n");
  43. return;
  44. }
  45. module_init(student_list_init);
  46. module_exit(student_list_exit);
  47. MODULE_LICENSE("Dual BSD/GPL");

输出结果:

  1. [19764.110000] name = Li, age = 23
  2. [19764.120000] name = Sun, age = 22
  3. [19764.120000] name = Qian, age = 21
  4. [19764.130000] name = Zhao, age = 20

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/128081
推荐阅读
相关标签
  

闽ICP备14008679号

        
cppcmd=keepalive&