当前位置:   article > 正文

linux内核链表的实现和使用和详解_linux引入内核链表头文件

linux引入内核链表头文件


首先,内核链表的头文件,在linux内核的 /include/linux 下的 List.h ,把List.h 复制出来,黏贴到 工程下,就可以直接用内核链表的一些宏和函数。

以下介绍内核链表的一些宏和函数,已经他的实现方式和使用方法。

(1)什么是内核链表:

如图:内核链表一般就是 在一个结构体 有一个结构体成员变量,该结构体成员变量只有 next 和 prev两个指针,分别指向下一个结点和上一个结构,就像一条绳子串起所有的结构体,这样做的好处,就是可以用内核链表来串起各个不同类型的结构体。

(2)内核链表的初始化:

  1. #include "kernel_list.h" //我把内核中的 /include/linux/List.h 改名为 kernel_list.h
  2. #include <stdio.h>
  3. struct Person
  4. {
  5. char name[20];
  6. int age;
  7. struct list_head mylist; //其中 list_head 在kernel_list.h 中有定义,这个结构体就只有两个结构体指针(next和prev)
  8. };
  9. int main()
  10. {
  11. struct Person *p;
  12. INIT_LIST_HEAD(&(p->mylist)); //主要是使用 INIT_LIST_HEAD()这个宏初始化那个链表。
  13. return 0;
  14. }

以下是宏INIT_LIST_HEAD()的实现方法,仅作了解就行,因为引入了 /include/linux/List.h 后,是可以直接使用 INIT_LIST_HEAD的

  1. #define INIT_LIST_HEAD(ptr) \ //ptr是指针
  2. do { \
  3. (ptr)->next = (ptr); (ptr)->prev = (ptr); \
  4. } while (0)

作用是把 指针指向的结构体 中的next和prev都指向自己。


(3)插入内核链表结点:

在结点head之后插入新节点。(这是实现原理,仅作了解就行,后面会有怎么用的代码)

  1. static inline void list_add(struct list_head *new, struct list_head *head) //在结点之后插入新结点
  2. {
  3. __list_add(new, head, head->next); //__list_add()这个函数,等等就讲
  4. }


在结点head之前插入新节点。(这是实现原理,仅作了解就行,后面会有怎么用的代码)

  1. static inline void list_add_tail(struct list_head *new, struct list_head *head)
  2. {
  3. __list_add(new, head->prev, head);
  4. }
访问顺序和新增插入顺序(如在head之前或之后插入)有相应的函数。

__list_add()的实现:
  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. }
struct list_head 在内核头文件 List.h中的实现:
  1. struct list_head {
  2. struct list_head *next, *prev;
  3. };

list_head()和list_head_tail()选一个使用就行,没必要两个都记住。

(4)遍历内核链表:
宏函数 list_entry()是重点,不过这个也了解一下就好,真正操作内核链表用不到。下面再解释这个宏。

两种遍历方法(对应两种插入结点的方法):

第一种:向前遍历:(下面有更好的方法)
  1. #define list_for_each(pos, head) \
  2. for (pos = (head)->next; pos != (head); \
  3. pos = pos->next)
  4. /**
第二种:向后遍历:(下面有更好的方法)
  1. #define list_for_each_prev(pos, head) \
  2. for (pos = (head)->prev; pos != (head); \
  3. pos = pos->prev)


注意:插入结点的方法要跟遍历方法匹对好,用head之后插入结点的方法的话,遍历方法就要选向后遍历。
           用head之前插入结点的方法的话,遍历方法就要宣向前遍历,这样才能 按插入顺序遍历所有结点。

上述的两种遍历方法,都是指向内核链表结构体的,这样设计起来不太简易,首先你的封装性就不够强,而下面介绍的方法则是 指向内核链表结构体 所在的结构体。
如:(推荐以下这种--->list_for_each_entry())

list_for_each_entry() 是                  向前遍历。
list_for_each_entry_reverse()是向后遍历。
list_for_each_entry() 和list_for_each_entry_reverse()选一个就好,当然也得匹配好插入结点是向前还是向后。
  1. struct student
  2. {
  3.  int num;
  4.  struct list_head mylist;
  5. };
  1. int show(struct student *head)
  2. {
  3. struct student *pos;
  4. list_for_each_entry(pos,&(head->mylist),mylist) //list_for_each_entry()是将要介绍的宏。
  5. { //用list_for_each_entry的话,pos可以写你定义的结构体类型(struct student)
  6. printf("pos->num is:%d\n",pos->num); // 而不是struct list_head ,而用list_for_each的话,pos必须是 list_head类型的
  7. }
  8. return 0;
  9. }

list_for_each_entry_reverse()的实现代码:(跟list_for_each_entry差不多)
  1. #define list_for_each_entry_reverse(pos, head, member) \
  2. for (pos = list_entry((head)->prev, typeof(*pos), member); \
  3. &pos->member != (head); \
  4. pos = list_entry(pos->member.prev, typeof(*pos), member))



list_for_each_entry()的实现:(实现原理仅作了解即可)(他只是一个 for 循环)

  1. #define list_for_each_entry(pos, head, member) \
  2. for (pos = list_entry((head)->next, typeof(*pos), member);&pos->member != (head); \
	pos = list_entry(pos->member.next, typeof(*pos), member))   //一个for循环
list_for_each_entry()的作用是 :
           list_for_each_entry()的三个参数分别是,pos(指向member的指针) , head(数据类型结构体头结点),member(数据类型结构体的一个成员变量)
           这个宏可以通过 通过 一个指针指向 一个结构体中 一个成员,来返回这个结构体的起始地址。
 
其中实现这个功能最重要的是一个宏 list_entry() ,我们来看看他的实现:
  1. #define list_entry(ptr, type, member) \
  2. ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
这片代码看起来有点难懂,我们来画个图看看:

首先  &((type*)0)->member:把0地址转换成(type*)类型,然后就可以映射出 member(因为member是结构体x的一个成员),0地址上的结构体是不存在的,只是虚拟出来
然后 &((type*)0)->member 是上图A部分的大小,因为同是 type型结构体,所以A部分大小 = B部分大小。然后再用  指向member 的指针pos 减去B部分 就等于 结构体x的
起始地址。



(5)删除结点:
直接上代码,并不难理解:
  1. static inline void list_del(struct list_head *entry)
  2. {
  3. __list_del(entry->prev, entry->next);
  4. entry->next = (void *) 0;
  5. entry->prev = (void *) 0;
  6. }
这个函数,包括上面的宏和函数,只是一个接口,后续的操作函数其实还需要自己设计,例如上面这个删除函数,其实还得free掉entry,否则会一直占着堆空间

宏__list_del()的实现原理:(这些都简单理解就好)
  1. static inline void __list_del(struct list_head *prev, struct list_head *next)
  2. {
  3. next->prev = prev;
  4. prev->next = next;
  5. }


最后,一片代码:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include "kernel_list.h"
  4. struct student
  5. {
  6. int num;
  7. struct list_head mylist;
  8. };
  9. // 内核链表的初始化
  10. struct student * list_init()
  11. {
  12. struct student *head=malloc(sizeof(struct student));
  13. INIT_LIST_HEAD(&(head->mylist));
  14. return head;
  15. }
  16. // 创建新的节点
  17. struct student * new_node(int data)
  18. {
  19. struct student *new=malloc(sizeof(struct student));
  20. new->num=data;
  21. INIT_LIST_HEAD(&(new->mylist));
  22. return new;
  23. }
  24. // 添加新的节点到内核链表中
  25. int kernellist_add(struct student *new,struct student *head)
  26. {
  27. list_add(&(new->mylist), &(head->mylist));
  28. return 0;
  29. }
  30. // 打印内核链表的数据
  31. int show(struct student *head)
  32. {
  33. struct student *pos;
  34. list_for_each_entry(pos,&(head->mylist),mylist)
  35. {
  36. printf("pos->num is:%d\n",pos->num);
  37. }
  38. return 0;
  39. }
  40. int main()
  41. {
  42. int n;
  43. int i;
  44. struct student *mynewnode;
  45. struct student *p=list_init();
  46. printf("please input num you want create node!\n");
  47. scanf("%d",&n);
  48. for(i=1; i<=n; i++)
  49. {
  50. mynewnode = new_node(i);
  51. kernellist_add(mynewnode,p);
  52. }
  53. show(p);
  54. return 0;
  55. }

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

闽ICP备14008679号