本文转载自:http://blog.csdn.net/coding__madman/article/details/51325646

链表简介:

链表是一种常用的数据结构,它通过指针将一系列数据节点连接成一条数据链。相对于数组,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以高效地在链表中的任意位置实时插入或者删除数据。链表的开销主要是访问的顺序性和组织链的空间损失。

内核链表的好主要体现为两点,1是可扩展性,2是封装。可扩展性肯定是必须的,内核一直都是在发展中的,所以代码都不能写成死代码,要方便修改和追加。将链表常见的操作都进行封装,使用者只关注接口,不需关注实现。分析内核中的链表我们
可以做些什么呢?我觉得可以将其复用到用户态编程中,以后在用户态下编程就不需要写一些关于链表的代码了,直接将内核中list.h中的代码拷贝过来用。也可以整理出my_list.h,在以后的用户态编程中直接将其包含到C文件中。

1. 链表对比

传统链表和内核链表

传统链表:一般指的是单向链表

struct List

{

struct list *next;//链表结点指针域

};

内核链表:双向循环链表 设计初衷是设计出一个通用统一的双向链表!

struct list_head

{

struct list_head    *head, *prev;

};

list_head结构包含两个指向list_head结构体的指针

prev和next,由此可见,内核的链表具备双链表功能,实际上,通常它都组织成双向循环链表

2. 内核链表使用

1. INIT_LIST_HEAD:创建链表

2. list_add:在链表头插入节点

3. list_add_tail:在链表尾插入节点

4. list_del:删除节点

5. list_entry:取出节点

6. list_for_each:遍历链表

(如果我们不知道这些函数的参数以及函数内部实现,学习查阅这些函数的参数或者实现代码最好的方法还是直接查看内核源码,结和前面的用sourceInsight工具直接搜索这些函数的名字)

 

下面举个例子:比如查阅INIT_LIST_HEAD函数,

这个是先将内核源码导入sourceInsight工程里面!源码可以在官网上下载,然后在Linux下解压(文件名Linux分大小写,windows不分大小写),然后通过Samba和映射网络驱动器功能(前面的sourceInsight博文有讲到),点击R图标左边的那个图标(像一个打开的一本书)

这样可以很快的查看到代码实现部分:在内核Mkregtale.c文件中

  1. /*
  2. * This is a simple doubly linked list implementation that matches the
  3. * way the Linux kernel doubly linked list implementation works.
  4. */
  5. struct list_head {
  6. struct list_head *next; /* next in chain */
  7. struct list_head *prev; /* previous in chain */
  8. };

这个不含数据域的链表,可以嵌入到任何数据结构中,例如可按如下方式定义含有数据域的链表:

  1. struct score
  2. {
  3. int num;
  4. int English;
  5. int math;
  6. struct list_head list;//链表链接域
  7. };
  8. struct list_head score_head;//所建立链表的链表头

INIT_LIST_HEAD(&score_head);//初始化链表头 完成一个双向循环链表的创建
上面的红色部分初始化一个已经存在的list_head对象,score_head为一个结构体的指针,这样可以初始化堆栈以及全局区定义的score_head对象。调用INIT_LIST_HEAD()宏初始化链表节点,将next和prev指针都指向其自身,我们就构造了一个空的双循环链表。


初始化一个空链表:其实就是链表头,用来指向第一个结点!定义结点并且初始化!然后双向循环链表就诞生了

static 加在函数前,表示这个函数是静态函数,其实际上是对作用域的限制,指该函数作用域仅局限于本文件。所以说,static 具有信息隐蔽的作用。而函数前加 inline 关键字的函数,叫内联函数,表 示编译程序在调用这个函数时,立即将该函数展开。
  1. /* Initialise a list head to an empty list */
  2. static inline void INIT_LIST_HEAD(struct list_head *list)
  3. {
  4. list->next = list;
  5. list->prev = list;
  6. }

list_add:在链表头插入节点

  1. /**
  2. * list_add - add a new entry
  3. * @new: new entry to be added
  4. * @head: list head to add it after
  5. *
  6. * Insert a new entry after the specified head.
  7. * This is good for implementing stacks.
  8. */
  9. static inline void list_add(struct list_head *new, struct list_head *head)
  10. {
  11. __list_add(new, head, head->next);
  12. }
  1. /*
  2. * Insert a new entry between two known consecutive entries.
  3. *
  4. * This is only for internal list manipulation where we know
  5. * the prev/next entries already!
  6. */
  7. #ifndef CONFIG_DEBUG_LIST
  8. static inline void __list_add(struct list_head *new,
  9. struct list_head *prev,
  10. struct list_head *next)
  11. {
  12. next->prev = new;
  13. new->next = next;
  14. new->prev = prev;
  15. prev->next = new;
  16. }
  17. #else
  18. extern void __list_add(struct list_head *new,
  19. struct list_head *prev,
  20. struct list_head *next);
  21. #endif

list_add_tail:在链表尾插入节点

  1. /**
  2. * list_add_tail - add a new entry
  3. * @new: new entry to be added
  4. * @head: list head to add it before
  5. *
  6. * Insert a new entry before the specified head.
  7. * This is useful for implementing queues.
  8. */
  9. static inline void list_add_tail(struct list_head *new, struct list_head *head)
  10. {
  11. __list_add(new, head->prev, head);
  12. }

用法示例:

struct score
{
int num;
int English;
int math;
struct list_head list;//链表链接域
};

struct list_head score_head;//所建立链表的链表头
//定义三个节点 然后插入到链表中
struct score stu1, stu2, stu3;

list_add_tail(&(stu1.list), &score_head);//使用尾插法

Linux 的每个双循环链表都有一个链表头,链表头也是一个节点,只不过它不嵌入到宿主数据结构中,即不能利用链表头定位到对应的宿主结构,但可以由之获得虚拟的宿主结构指针。

list_del:删除节点

  1. /* Take an element out of its current list, with or without
  2. * reinitialising the links.of the entry*/
  3. static inline void list_del(struct list_head *entry)
  4. {
  5. struct list_head *list_next = entry->next;
  6. struct list_head *list_prev = entry->prev;
  7. list_next->prev = list_prev;
  8. list_prev->next = list_next;
  9. }

list_entry:取出节点

  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)
  1. /**
  2. * container_of - cast a member of a structure out to the containing structure
  3. * @ptr:    the pointer to the member.
  4. * @type:   the type of the container struct this is embedded in.
  5. * @member: the name of the member within the struct.
  6. *
  7. */
  8. #define container_of(ptr, type, member) ({          \
  9. const typeof(((type *)0)->member)*__mptr = (ptr);    \
  10. (type *)((char *)__mptr - offsetof(type, member)); })

list_for_each:遍历链表

  1. #define list_for_each(pos, head) \
  2. for (pos = (head)->next; prefetch(pos->next), pos != (head); \
  3. pos = pos->next)</span></span>
可以看出,使用了辅助指针pos,pos是从第一节点开始的,并没有访问头节点,直到pos到达头节点指针head的时候结束。
而且 这种遍历仅仅是找到一个个结点的当前位置,那如何通过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 类型结构体的起始地址,即起始结点的地址。使用方法非常的巧妙!

比如下列用法:

struct score stu1, stu2, stu3;
struct list_head *pos;//定义一个结点指针
struct score *tmp;//定义一个score结构体变量

  1. //遍历整个链表,每次遍历将数据打印出来
  2. list_for_each(pos, &score_head)//这里的pos会自动被赋新值
  3. {
  4. tmp = list_entry(pos, struct score, list);
  5. printk(KERN_WARNING"num: %d, English: %d, math: %d\n", tmp->num, tmp->English, tmp->math);
  6. }

list_for_each_safe: 链表的释放

  1. /**
  2. * list_for_each_safe - iterate over a list safe against removal of list entry
  3. * @pos:the &struct list_head to use as a loop cursor.
  4. * @n:another &struct list_head to use as temporary storage
  5. * @head:</span>the head for your list.
  6. */
  7. #define list_for_each_safe(pos, n, head) \
  8. for (pos = (head)->next, n = pos->next; pos != (head); \
  9. pos = n, n = pos->next)

3. 内核链表实现分析

4. 移植内核链表(这里先贴出一个使用内核链表的内核模块小例程)

mylist.c文件

  1. #include<linux/module.h>
  2. #include<linux/init.h>
  3. #include<linux/list.h>//包含内核链表头文件
  4. struct score
  5. {
  6. int num;
  7. int English;
  8. int math;
  9. struct list_head list;//链表链接域
  10. };
  11. struct list_head score_head;//所建立链表的链表头
  12. //定义三个节点 然后插入到链表中
  13. struct score stu1, stu2, stu3;
  14. struct list_head *pos;//定义一个结点指针
  15. struct score *tmp;//定义一个score结构体变量
  16. int mylist_init()
  17. {
  18. INIT_LIST_HEAD(&score_head);//初始化链表头 完成一个双向循环链表的创建
  19. stu1.num = 1;
  20. stu1.English = 59;
  21. stu1.math = 99;
  22. //然后将三个节点插入到链表中
  23. list_add_tail(&(stu1.list), &score_head);//使用尾插法
  24. stu2.num = 2;
  25. stu2.English = 69;
  26. stu2.math = 98;
  27. list_add_tail(&(stu2.list), &score_head);
  28. stu3.num = 3;
  29. stu3.English = 89;
  30. stu3.math = 97;
  31. list_add_tail(&(stu3.list), &score_head);
  32. //遍历整个链表,每次遍历将数据打印出来
  33. list_for_each(pos, &score_head)//这里的pos会自动被赋新值
  34. {
  35. tmp = list_entry(pos, struct score, list);
  36. printk(KERN_WARNING"num: %d, English: %d, math: %d\n", tmp->num, tmp->English, tmp->math);
  37. }
  38. return 0;
  39. }
  40. void mylist_exit()
  41. {
  42. //退出时删除结点
  43. list_del(&(stu1.list));
  44. list_del(&(stu2.list));
  45. printk(KERN_WARNING"mylist exit!\n");
  46. }
  47. module_init(mylist_init);
  48. module_exit(mylist_exit);

Makefile文件

  1. obj-m := mylist.o
  2. KDIR := /home/kernel/linux-ok6410
  3. all:
  4. make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
  5. clean:
  6. rm -f *.o *.ko *.order *.symvers

在终端上加载运行内核模块:

这里rmmod 时会有个错误!不过没大事!百度有很多解决方案!

Linux内核链表深度分析【转】的更多相关文章

  1. Linux内核链表深度分析

    链表简介:链表是一种常用的数据结构,它通过指针将一系列数据节点连接成一条数据链.相对于数组,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以高效地在链表中的任意位置实时插入 ...

  2. 链表的艺术——Linux内核链表分析

    引言: 链表是数据结构中的重要成员之中的一个.因为其结构简单且动态插入.删除节点用时少的长处,链表在开发中的应用场景许多.仅次于数组(越简单应用越广). 可是.正如其长处一样,链表的缺点也是显而易见的 ...

  3. linux内核链表分析

    一.常用的链表和内核链表的区别 1.1  常规链表结构        通常链表数据结构至少应包含两个域:数据域和指针域,数据域用于存储数据,指针域用于建立与下一个节点的联系.按照指针域的组织以及各个节 ...

  4. 深入分析 Linux 内核链表--转

    引用地址:http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/index.html 一. 链表数据结构简介 链表是一种常用的组织有序数据 ...

  5. 深入分析 Linux 内核链表

    转载:http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/   一. 链表数据结构简介 链表是一种常用的组织有序数据的数据结构,它通过指 ...

  6. Linux内核源代码情景分析系列

    http://blog.sina.com.cn/s/blog_6b94d5680101vfqv.html Linux内核源代码情景分析---第五章 文件系统  5.1 概述 构成一个操作系统最重要的就 ...

  7. Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7)

    http://blog.chinaunix.net/uid-20543672-id-3157283.html Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3 ...

  8. 2018-2019-1 20189221 《Linux内核原理与分析》第七周作业

    2018-2019-1 20189221 <Linux内核原理与分析>第七周作业 实验六 分析Linux内核创建一个新进程的过程 代码分析 task_struct: struct task ...

  9. 数据结构开发(10):Linux内核链表

    0.目录 1.老生常谈的两个宏(Linux) 1.1 offsetof 1.2 container_of 2.Linux内核链表剖析 3.小结 1.老生常谈的两个宏(Linux) Linux 内核中常 ...

随机推荐

  1. 数据库存储ATM机,开户、查询等信息

    package com.bank.unionpay; //银行卡的接口 public interface I_yinhangka { //抽象方法 //public abstract默认修饰抽象的 p ...

  2. odoo XMLRPC 新库 OdooRPC 尝鲜

    无意中发现了python居然有了OdoRPC的库,惊喜之下赶紧尝试一番,比XMLRPC简洁了不少,机制看样子是利用的JsonRPC. #原文出自KevinKong的博客http://www.cnblo ...

  3. liunx系统计划任务管理(at/crond调度)

    一.at命令 at命令格式at HH:MM YYYY-MM-DD 其中 HH(小时):MM(分钟) YYYY(年)-MM(月份)-DD(日) 启动atd进程 /etc/init.d/atd start ...

  4. java 通过控制台输入的数字打印菱形字母

    package com.rui.test; import java.util.Scanner; /** * @author sunshine * @version 1.0 * @date:2015年1 ...

  5. java 中的2个接口 Comparable和Comparator

    像Integer.String这些类型的数据都是已经实现Comparable接口的,所以对这些类型可以直接通过Arrays.sort(...)和Collections.sort(...)方法进行排序. ...

  6. BizTalk开发系列(十八) 使用信封拆分数据库消息

    之前写了一篇的<BizTalk开发系列(十七) 信封架构(Envelop)> 是关于信封架构及其拆装原理的,都是理论性的内容.信封在BizTalk开发过程中最常用的应该是在读取SQL Se ...

  7. 大量查询SQL语句 实例

    1.查看表结构语句:DESC   表名   2.查询所有列:select  *  from  表名   3.查询指定列:select  字段名  form  表名   4.查询指定行:SELECT * ...

  8. A trip through the Graphics Pipeline 2011_13 Compute Shaders, UAV, atomic, structured buffer

    Welcome back to what’s going to be the last “official” part of this series – I’ll do more GPU-relate ...

  9. 我的web框架设计

    做了很久的web开发,学了webform和mvc自己总结了,觉得当下的构架还是有改进的可能的. 其实首先说下我的一些认识(个人认知,欢迎讨论,谢绝砸砖). 我觉得对计算机和数据的操作,本身就是一个单向 ...

  10. 11高级网站构建:div和span

    用<div>元素把属于一个逻辑部分的元素包围起来.可以用id属性为<div>提供一个唯一的标签. <div>的作用:1.更深一步展示页面的基本逻辑结构(相当于一个逻 ...