本文转载自: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. 【LeetCode】16. 4Sum

    题目:Given an array S of n integers, are there elements a, b, c, and d in S such that a + b + c + d = ...

  2. JavaScript:词法结构

    1.字符集JavaScript程序是用Unicode字符集编写的. 1.1 区分大小写 JavaScript是区分大小写的语言.也就是说,关键字.变量.函数名和所有标识符(identifier)都必须 ...

  3. .net 文件下载【转】

    方式一:TransmitFile实现下载.将指定的文件直接写入 HTTP 响应输出流,而不在内存中缓冲该文件.     protected void Button1_Click(object send ...

  4. javascript平时小例子①(移动的小div)

    css样式: #box{ width: 300px; height: 300px; background: deepskyblue; position: absolute; margin-right: ...

  5. java项目开发的一些准备工作

    做项目有一段时间了,每次接手一个新项目都要在开发前做些准备工作,方便开发. 有些东西在配置的时候经常会忘记,所有整理一份,方便以后查阅! 1.安装JDK及搭建环境,安装tomcat及搭建环境,这些一般 ...

  6. level分层次输出内容添加leve

    代码如下:function getSubComments($parent = 0, $level = 0) { $db = &JFactory::getDBO(); $sql = " ...

  7. centos安装vsftp

    yum install -y vsftpd chkconfig vsftpd on 配置问价在/etc/vsftpd/下面 chroot_list 默认没有 必须自己建立 vi /etc/vsftpd ...

  8. 虚拟机安装Macintosh探索

    想跟virtualbox安装一个mac os,在pcbeta找到网友分享的原版镜像,挂载安装,结果在安装的时候不是卡在进入界面,就是不停地安装,显然没有那么简单.virtualbox 在用户手册写着支 ...

  9. papi酱视频因违规遭下线整改,你知道原因吗?

    今日4月18日,在微信上的一篇<papi酱遭广电总局封杀 罗振宇1200万恐打水漂>的文章迅速转了起来,说的就是现在网红第一人“papi酱”的视频被广电总局责令下架的消息.箭头直指papi ...

  10. php执行root命令

    一.确定php的sysem等函数可用 二.编写c程序,如ipt.c #include <stdio.h>#include <stdlib.h>#include <sys/ ...