Linux内核链表深度分析【转】

news/2024/7/3 1:28:50

本文转载自: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文件中

 

[html]  view plain  copy
 
  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.   
  6. struct list_head {  
  7.     struct list_head *next; /* next in chain */  
  8.     struct list_head *prev; /* previous in chain */  
  9. };  

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

 

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

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

 

 
 

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

 

static 加在函数前,表示这个函数是静态函数,其实际上是对作用域的限制,指该函数作用域仅局限于本文件。所以说,static 具有信息隐蔽的作用。而函数前加 inline 关键字的函数,叫内联函数,表 示编译程序在调用这个函数时,立即将该函数展开。

 

 

[html]  view plain  copy
 
  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:在链表头插入节点

 

[html]  view plain  copy
 
  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. }  

 

[html]  view plain  copy
 
  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:在链表尾插入节点

 

[html]  view plain  copy
 
  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:删除节点

 

[html]  view plain  copy
 
  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.   
  8.     list_next->prev = list_prev;  
  9.     list_prev->next = list_next;  
  10.   
  11. }  



 

list_entry:取出节点

 

[html]  view plain  copy
 
  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)  

 

 

[html]  view plain  copy
 
  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:遍历链表

[html]  view plain  copy
 
  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结构体变量

 

[html]  view plain  copy
 
  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: 链表的释放

 

[html]  view plain  copy
 
  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文件

 

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


Makefile文件

 

 

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


 

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

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

转载于:https://www.cnblogs.com/zzb-Dream-90Time/p/6254953.html


http://www.niftyadmin.cn/n/3336801.html

相关文章

如何用SMPP发送WAP彩信

SMPP的头域GSM Features设置为0x40&#xff0c;用来表明SMPP的短信体携带UDHI。 SMPP的头域Data Coding设置为0xF5。 Body中前11个字节为0x0B 0X05 0X04 0X0B 0X84 0X23 0XF0 0X00 0X03 0XE1 0X01 0X01&#xff1b; Body后面就是MMSE封装的彩信号码、彩信大小和彩信的HTTP地…

php5.5+apache2.4+mysql5.7在windows下的配置

apache2.4下载和安装 下载apache2.4 https://www.apachelounge.com/download/VC11/ 提取解压目录Apache24到d:/dev/Apache24 修改d:/dev/Apache24/conf/httpd.conf ServerRoot "d:/dev/Apache24" DocumentRoot "d:/dev/Apache24/htdocs"<Directory &quo…

分布式系统的那些事儿(二) - 线程与进程

线程和进程在之前的文章有讲过&#xff0c;这里简单再说一下吧。 进程&#xff0c;一个程序可以称为一个进程 线程&#xff0c;一个程序中包含多个线程&#xff0c;线程的集合体可以称之为进程。 同一进程中的不同线程可以互相影响&#xff0c;也可以并行操作&#xff08;并发&…

printk函数日志级别的设置【转】

本文转载自&#xff1a; 下面执行cat /proc/sys/kernel/printk 打印出的四个数字分别代表&#xff1a; 控制台日志级别、默认的消息日志级别、最低的控制台日志级别和默认的控制台日志级别 只有当printk的日志级别小于console_loglevel时&#xff0c;消息才能显示出来。 以下为…

LINUX下C/C++常见的几种调试程序的方法

第一章&#xff1a;巧用宏变量 __FILE__:记录源代码文件。 __LINE__&#xff1a;记录源代码行数。(是整数类型) __DATE__&#xff1a;记录编译日期。 __TIME__: 记录编译时间 __func__:记录函数 #include <stdlib.h> #include <stdio.h> int main() { prin…

Linux应用程序访问字符设备驱动详细过程【转】

本文转载自&#xff1a;http://blog.csdn.net/coding__madman/article/details/51346532 下面先通过一个编写好的内核驱动模块来体验以下字符设备驱动 可以暂时先忽略下面的代码实现&#xff01; memdev.c [cpp] view plaincopy #include <linux/module.h> #include <…

Centos 7 Nginx + PHP 安装步骤

Centos 7 下的 PHP7/Nginx/Memcached 安装步骤。对的&#xff0c;没有Mysql,有RDS懒得装Mysql了。 一、获取相关开源程序 1、安装环境所需的程序库 sudo -s LANGC yum -y update yum remove mariadb* yum -y install patch make cmake gcc gcc-c bison flex file libtool libto…

中国区优化的Docker安装脚本

说明 为了方便中国区的用户安装不同版本的docker&#xff0c;我们在这里提供针对中国网络环境优化的安装脚本。它们使用中国的软件包仓库&#xff08;在此感谢USTC&#xff09;。 用法 使用需要的docker版本替换以下脚本中的<docker-version-you-want> curl -sSL https:/…