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

本文转载自: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. struct list_head {
  6. struct list_head *next; /* next in chain */
  7. struct list_head *prev; /* previous in chain */
  8. };

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

[html] view plain copy

  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 关键字的函数,叫内联函数,表 示编译程序在调用这个函数时,立即将该函数展开。

[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. list_next->prev = list_prev;
  8. list_prev->next = list_next;
  9. }

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. 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文件

[html] view plain copy

  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 时会有个错误!不过没大事!百度有很多解决方案!

时间: 2024-10-05 11:06:02

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

Linux内核链表深度分析

链表简介: 链表是一种常用的数据结构,它通过指针将一系列数据节点连接成一条数据链.相对于数组,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以高效地在链表中的任意位置实时插入或者删除数据.链表的开销主要是访问的顺序性和组织链的空间损失. 内核链表的好主要体现为两点,1是可扩展性,2是封装.可扩展性肯定是必须的,内核一直都是在发展中的,所以代码都不能写成死代码,要方便修改和追加.将链表常见的操作都进行封装,使用者只关注接口,不需关注实现.分析内核中的链表我们 可以做些什

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

引言: 链表是数据结构中的重要成员之中的一个.因为其结构简单且动态插入.删除节点用时少的长处,链表在开发中的应用场景许多.仅次于数组(越简单应用越广). 可是.正如其长处一样,链表的缺点也是显而易见的.这里当然不是指随机存取那些东西,而是因为链表的构造方法(在一个结构体中套入其同类型指针)使得链表本身的逻辑操作(如添加结点,删除结点,查询结点等),往往与其应用场景中的业务数据相互混杂.这导致我们每次使用链表都要进行手工打造,做过链表的人肯定对此深有了解. 是否能将链表从变换莫測的业务数据中抽象出

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

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

原文地址:Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938390.html 在构架相关的汇编代码运行完之后,程序跳入了构架无关的内核C语言代码:init/main.c中的start_kernel函数,在这个函数中Linux内核开始真正进入初始化阶段, 下面我就顺这代码逐个函数的解释,但是这里并不会过于深入

Linux内核源代码情景分析-内存管理之slab-回收

在上一篇文章Linux内核源代码情景分析-内存管理之slab-分配与释放,最后形成了如下图的结构: 图 1 我们看到空闲slab块占用的若干页面,不会自己释放:我们是通过kmem_cache_reap和kmem_cache_shrink来回收的.他们的区别是: 1.我们先看kmem_cache_shrink,代码如下: int kmem_cache_shrink(kmem_cache_t *cachep) { if (!cachep || in_interrupt() || !is_chaine

Linux内核源代码情景分析-共享内存

一.库函数shmget()--共享内存区的创建与寻找 asmlinkage long sys_shmget (key_t key, size_t size, int shmflg) { struct shmid_kernel *shp; int err, id = 0; down(&shm_ids.sem); if (key == IPC_PRIVATE) { err = newseg(key, shmflg, size);//分配一个共享内存区供本进程专用,最后返回的是一体化的标示号 } el

Linux内核源代码情景分析-mmap后,文件与虚拟区间建立映射

一.文件映射的页面换入 在mmap后,mmap参考Linux内核源代码情景分析-系统调用mmap(),当这个区间的一个页面首次受到访问时,会由于见面无映射而发生缺页异常,相应的异常处理程序do_no_page(). static inline int handle_pte_fault(struct mm_struct *mm, struct vm_area_struct * vma, unsigned long address, int write_access, pte_t * pte) {

Linux内核源代码情景分析-交换分区

在Linux内核源代码情景分析-共享内存中,共享内存,当内存紧张时是换出到交换分区. 在Linux内核源代码情景分析-mmap后,文件与虚拟区间建立映射中,文件映射的页面,当内存紧张时是换出到硬盘上的文件中. 这里的交换分区,就是是swap分区,记得给电脑安装ubuntu时,就有一项是swap分区. 交换分区和文件的区别是: 文件是在一个具体的文件系统之下的,交换分区没有这个必要,它可能是一个裸分区,不需要文件系统. 需要说明一点,并不是所有从物理内存中交换出来的数据都会被放到Swap中(如果这

Linux内核源代码情景分析-内存管理之用户页面的定期换出

我们已经看到在分配页面时,如果页面数不够,那么会调用page_launder,reclaim_page,__free_page将页面换出,并重新投入分配. 为了避免总是在CPU忙碌的时候,也就是在缺页异常发生的时候,临时再来搜寻可供换出的内存页面并加以换出,Linux内核定期地检查并且预先将若干页面换出,腾出空间,以减轻系统在缺页异常发生时的负担. 为此,在Linux内核中设置了一个专司定期将页面换出的"守护神"kswapd和kreclaimd. static int __init k