转自:http://blog.chinaunix.net/uid-27037833-id-3237153.html

链表(循环双向链表)是Linux内核中最简单、最常用的一种数据结构。

       
       1、链表的定义
            struct list_head {
                struct list_head *next, *prev;
            }
           这个不含数据域的链表,可以嵌入到任何数据结构中,例如可按如下方式定义含有数据域的链表:
            struct my_list {
                void  * mydata;
                struct list_head  list;
            } ;
 
       2、链表的声明和初始化宏
            struct list_head 只定义了链表结点,并没有专门定义链表头.那么一个链表结点是如何建立起来的?
内核代码 list.h 中定义了两个宏:
            #defind  LIST_HEAD_INIT(name)    { &(name), &(name) }      //仅初始化
            #defind  LIST_HEAD(name)     struct list_head  name = LIST_HEAD_INIT(name)  //声明并初始化
           
            如果要声明并初始化链表头mylist_head,则直接调用:LIST_HEAD(mylist_head),之后,
mylist_head的next、prev指针都初始化为指向自己。这样,就有了一个带头结点的空链表。
 
             判断链表是否为空的函数:
             static inline int list_empty(const struct list_head  * head) {
                  return head->next  ==  head;
              }    //返回1表示链表为空,0表示不空
 
      3、在链表中增加一个结点
          (内核代码中,函数名前加两个下划线表示内部函数)
            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 ;
            }  
          
            list.h 中增加结点的两个函数为:
           (链表是循环的,可以将任何结点传递给head,调用这个内部函数以分别在链表头和尾增加结点)
            static inline void list_add(struct list_head *new, struct llist_head *head)
            {
                    __list_add(new, head, head -> next) ;
            }
            static inline void list_add_tail(struct list_head 8new, struct list_head *head)
            {
                     __list_add(new, head -> prev, head) ;
            }
            附:给head传递第一个结点,可以用来实现一个队列,传递最后一个结点,可以实现一个栈。
            static 加在函数前,表示这个函数是静态函数,其实际上是对作用域的限制,指该函数作用域仅局限
           于本文件。所以说,static 具有信息隐蔽的作用。而函数前加 inline 关键字的函数,叫内联函数,表
           示编译程序在调用这个函数时,立即将该函数展开。
            
    4、 遍历链表
           list.h 中定义了如下遍历链表的宏:
           #define   list_for_each(pos, head)    for(pos = (head)-> next ;  pos != (head) ;  pos = pos -> next)  
           这种遍历仅仅是找到一个个结点的当前位置,那如何通过pos获得起始结点的地址,从而可以引用结
点的域?list.h 中定义了 list_entry 宏:
           #define   list_entry( ptr, type, member )  \
              ( (type *) ( (char *) (ptr)  - (unsigned long) ( &( (type *)0 )  ->  member ) ) )
          分析:(unsigned long) ( &( (type *)0 )  ->  member ) 把 0 地址转化为 type 结构的指针,然后获取该
          结构中 member 域的指针,也就是获得了 member 在type 结构中的偏移量。其中  (char *) (ptr) 求
         出的是 ptr 的绝对地址,二者相减,于是得到 type 类型结构体的起始地址,即起始结点的地址。
 
   5、链表的应用
         一个用以创建、增加、删除和遍历一个双向链表的Linux内核模块

点击(此处)折叠或打开

  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/slab.h>
  4. #include <linux/list.h>
  5. MODULE_LICENCE("GPL");
  6. MODULE_AUTHOR("LUOTAIJIA");
  7. #define N 10
  8. struct numlist {
  9. int num;
  10. struct list_head list;
  11. };
  12. struct numlist numhead;
  13. static int __init doublelist_init(void)
  14. {
  15. //初始化头结点
  16. struct numlist * listnode; //每次申请链表结点时所用的指针
  17. struct list_head * pos;
  18. struct numlist * p;
  19. int i;
  20. printk("doublelist is starting...\n");
  21. INIT_LIST_HEAD(&numhead.list);
  22. /*
  23. * static inline void INIT_LIST_HEAD(struct list_head *list)
  24. * {
  25. * list->next = list;
  26. * list->prev = list;
  27. * }
  28. */
  29. //建立N个结点,依次加入到链表当中
  30. for (i=0; i<N; i++) {
  31. listnode = (struct numlist *)kmalloc(sizeof(struct numlist), GFP_KERNEL);
  32. //void *kmalloc(size_t size, int flages)
  33. //分配内存,size 要分配内存大小,flags 内存类型
  34. listnode->num = i+1;
  35. list_add_tail(&listnode->list, &numhead.list);
  36. printk("Node %d has added to the doublelist...\n", i+1);
  37. }
  38. //遍历链表
  39. i = 1;
  40. list_for_each(pos, &numhead.list) {
  41. p = list_entry(pos, struct numlist, list);
  42. printk("Node %d's data: %d\n", i, p->num);
  43. i++;
  44. }
  45. return 0;
  46. }
  47. static void __exit doublelist_exit(void)
  48. {
  49. struct list_head *pos, *n;
  50. struct numlist *p;
  51. int i;
  52. //依次删除N个结点
  53. i = 1;
  54. list_for_each_safe(pos, n, &numhead.list) {
  55. //为了安全删除结点而进行的遍历
  56. list_del(pos); //从链表中删除当前结点
  57. p = list_entry(pos, struct numlist, llist);
  58. //得到当前数据结点的首地址,即指针
  59. kfree(p); //释放该数据结点所占空间
  60. printk("Node %d has removed from the doublelist...\n", i++);
  61. }
  62. printk("doublelist is exiting...\n");
  63. }
  64. module_init(doublelist_init);
  65. module_exit(doublelist_exit);
 
参考资料:Linux操作系统原理与应用(第2版)    陈莉君、康华 编著

最新文章

  1. 一个Java程序员的实习总结(2)
  2. Android存储空间不足的解决办法
  3. css -- 题目汇总
  4. Centos6.5使用yum安装MariaDB
  5. Cracking the Coding Interview 第一章
  6. 图片验证码自动识别,使用tess4j进行验证码自动识别(java实现)
  7. Java开发环境的搭建及配置
  8. node实现一个WEBSOCKET服务器
  9. @Resource 与 @Service注解的区别
  10. [转载] Fiddler为所欲为第二篇 像OD一样调试 [二]
  11. JNI 开发基础篇:Android开发中os文件的探索
  12. cocoa组件化开发
  13. NYOJ 47:过河问题(思维)
  14. 1.5.4、CDH 搭建Hadoop在安装之前(定制安装解决方案---配置自定义Java主目录位置)
  15. java Concurrent包学习笔记(三):ReentrantLock
  16. 搭建SpringBoot、Jsp支持学习笔记
  17. 表达式树(Expression Tree)
  18. C# 与 Java Rsa加密与解密互通
  19. 怎样修复grub开机引导(grub rescue)
  20. 基于JavaSE阶段下的集合类汇总

热门文章

  1. c#对xml的操作
  2. 【EF】EF实现大批量数据库插入操作
  3. BZOJ4563:[HAOI2016]放棋子——题解
  4. BZOJ1297:[SCOI2009]迷路——题解
  5. 洛谷 P1311 选择客栈 解题报告
  6. String类的用法
  7. Jokewithpermutation (DFS)
  8. HDU1083 :Courses(二分图匹配)
  9. springboot初学
  10. ACM3018欧拉回路