转自:http://blog.chinaunix.net/uid-23069658-id-4725279.html

关于链表我们更多时候是对其进行遍历的需求,上一篇博文里我们主要认识了一下和链表操作比较常用的几个内核API接口,其入参全都是清一色的struct list_head{}类型。至于链表的遍历,内核也有一组基本的接口(其实都是宏定义的)供开发者调用。

首先是list_for_each(pos, head),参数pos是需要开发者在外部提供的一个临时struct list_head{}类型的指针对象,类似于for循环的i、j、k之类的游标,head是我们要遍历的链表头。常见用法:

点击(此处)折叠或打开

  1. LIST_HEAD(student_list);
  2. struct list_head *stu;
  3. list_for_each(stu, &student_list){
  4. //在这个作用域里,指针stu依次指向student_list里的每一个struct list_head{}成员节点
  5. }

当然stu指向的是struct list_head{}类型的对象,我们一般是需要指向struct student{}的才对,此时list_entry(ptr, type, member)就出场了,它完全是container_of(ptr, type, member)的一个别名而已。container_of()就是根据type类型结构体中的member成员的指针ptr,反身找到该member所在结构体对象的type首地址。废话不多说,上图:

 

此时的用法就变成下面这样子:

 

注意结合上图,领会一下list_entry(ptr,type,member)三个参数之间的关系。这样如果每次要遍历链表时既要定义临时的struct list_head{}指针变量,又要定义目标结构体对象指针变量,总感觉些许不爽。好在Linux感知到了你的J点,于是乎:

点击(此处)折叠或打开

  1. list_for_each_entry(pos, head, member)

横空出世。参数pos和member意义没有变,而head则指向我们要遍历的链表首地址,这样一来开发者不用再自己定义struct list_head{}类型临时指针变量,只要需要自己定义一个的目标数据结构的临时指针变量就可以了:

点击(此处)折叠或打开

  1. LIST_HEAD(student_list);
  2. struct student *st;
  3. list_for_each_entry(st, &student_list, stu_list){
  4. //Todo here … …
  5. }

此时指针变量st,就相当于for循环的游标变量i了。
   当然,内核能感知的远不止于此,还有一个名为list_for_each_entry_reverse(pos, head, member)的宏,用于对双向链表的逆向遍历,参数的意义和list_for_each_entry()完全一样,区别在它是对链表从尾部到首部进行依次遍历。该接口主要是为了提高链表的访问速度,考虑两种情况:

第一,如果你明确知道你要访问的节点会出现在链表靠后的位置;

第二,如果你需要用双向链表实现一个类似于“栈”的数据结构;

针对以上两种需求,相比于list_for_each_entry(),list_for_each_entry_reverse()的速度和效率明显优于前者。为了追求极致,内核开发者们就是这么任性,没办法。

上述两个接口在遍历链表时已经完全可以胜任,但还无法满足删除的需求,原因是…算了,都懒的说了,把那两个宏展开,在纸上画一下,如果要删除节点,会发生什么“神奇”的事情就一目了然了。那如果遍历链表过程中要删除节点,该怎么办?咱接着唠:

点击(此处)折叠或打开

  1. list_for_each_entry_safe(pos, n, head, member)

如果你还没看过list.h文件,那么单从list_for_each_entry_safe(pos,n,head,member)的四个入参命名上,应该可以读懂它们的意思和用法了吧!如果你已经在纸上画过了,那么新增的n很明显应该是pos指针所指元素的下一个节点的地址,注意,pos和n都是目标结构体的类型,而非struct list_head{}类型,本例中它们都是struct student{}类型的指针,童鞋们可不要犯迷糊了。现在用法就更简单了:

点击(此处)折叠或打开

  1. LIST_HEAD(student_list);
  2. struct student *st,*next;
  3. list_for_each_entry_safe (st, next,&student_list, stu_list){
  4. //在这里可以对st所指向的节点做包括删除在内的任意操作
  5. //但千万别操作next,它是由list_for_each_entry_safe()进行维护的
  6. }

不用多想,肯定也存在一个名为list_for_each_entry_safe_reverse(pos, n, head, member)的宏。简单小节一下:
   1)、list_for_each_entry()和list_for_each_entry_reverse(),如果只需要对链表进行遍历,这两个接口效率要高一些;
   2)、list_for_each_entry_safe()和list_for_each_entry_safe_reverse(),如果遍历过程中有可能要对链表进行删除操作,用这两个;
   实际项目中,大家可以根据具体场景而考虑使用哪种方式。另外,关于链表遍历,内核还有其他一些列list_for_*相关的宏可供调用,这里就不一一阐述了,list.h源码里面无论是注释还是实现都相当明确。

 

说了老半天,还是操练几把感受感受,模拟训练之“内核级精简版学生管理系统”:
   头文件student.h长相如下:

点击(此处)折叠或打开

  1. /*student.h*/
  2. #ifndef __STUDENT_H_
  3. #define __STUDENT_H_
  4. #include <linux/list.h>
  5. #define MAX_STRING_LEN 32
  6. typedef struct student
  7. {
  8. char m_name[MAX_STRING_LEN];
  9. char m_sex;
  10. int m_age;
  11. struct list_head m_list;  /*把我们的学生对象组织成双向链表,就靠该节点了*/
  12. }Student;
  13. #endif

源文件student.c长相也不丑陋:

点击(此处)折叠或打开

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/init.h>
  4. #include "student.h"
  5. MODULE_LICENSE("Dual BSD/GPL");
  6. MODULE_AUTHOR("Koorey Wung");
  7. static int dbg_flg = 0;
  8. LIST_HEAD(g_student_list);
  9. static int add_stu(char* name,char sex,int age)
  10. {
  11. Student *stu,*cur_stu;
  12. list_for_each_entry(cur_stu,&g_student_list,m_list){ //仅遍历是否有同名学生,所以用该接口
  13. if(0 == strcmp(cur_stu->m_name,name))
  14. {
  15. printk("Error:the name confict!\n");
  16. return -1;
  17. }
  18. }
  19. stu = kmalloc(sizeof(Student), GFP_KERNEL);
  20. if(!stu)
  21. {
  22. printk("kmalloc mem error!\n");
  23. return -1;
  24. }
  25. memset(stu,0,sizeof(Student));
  26. strncpy(stu->m_name,name,strlen(name));
  27. stu->m_sex = sex;
  28. stu->m_age = age;
  29. INIT_LIST_HEAD(&stu->m_list);
  30. if(dbg_flg)
  31. printk("(Add)name:[%s],\tsex:[%c],\tage:[%d]\n",stu->m_name,stu->m_sex,stu->m_age);
  32. list_add_tail(&stu->m_list,&g_student_list); //将新学生插入到链表尾部,很简单吧
  33. return 0;
  34. }
  35. EXPORT_SYMBOL(add_stu);    //导出该函数,后面我们要在其他模块里调用,为了便于测试,下面其他借个接口类似
  36. static int del_stu(char *name)
  37. {
  38. Student *cur,*next;
  39. int ret = -1;
  40. list_for_each_entry_safe(cur,next,&g_student_list,m_list){  //因为要删除链表的节点,所以必须有带有“safe”的宏接口
  41. if(0 == strcmp(name,cur->m_name))
  42. {
  43. list_del(&cur->m_list);
  44. printk("(Del)name:[%s],\tsex:[%c],\tage:[%d]\n",cur->m_name,\
  45. cur->m_sex,cur->m_age);
  46. kfree(cur);
  47. cur = NULL;
  48. ret = 0;
  49. break;
  50. }
  51. }
  52. return ret;
  53. }
  54. EXPORT_SYMBOL(del_stu);
  55. static void dump_students(void)
  56. {
  57. Student *stu;
  58. int i = 1;
  59. printk("===================Student List================\n");
  60. list_for_each_entry(stu,&g_student_list,m_list){  //同样,也仅遍历链表而已
  61. printk("(%d)name:[%s],\tsex:[%c],\tage:[%d]\n",i++,stu->m_name,\
  62. stu->m_sex,stu->m_age);
  63. }
  64. printk("===============================================\n");
  65. }
  66. EXPORT_SYMBOL(dump_students);
  67. static void init_system(void)
  68. {
  69. /*初始化时,向链表g_student_list里添加6个节点*/
  70. add_stu("Tom",'m',18);
  71. add_stu("Jerry",'f',17);
  72. add_stu("Alex",'m',18);
  73. add_stu("Conory",'f',18);
  74. add_stu("Frank",'m',17);
  75. add_stu("Marry",'f',17);
  76. }
  77. /*因为没有数据库,所以当我们的模块退出时,需要释放内存*/
  78. static void clean_up(void)
  79. {
  80. Student *stu,*next;
  81. list_for_each_entry_safe(stu,next,&g_student_list,m_list){
  82. list_del(&stu->m_list);
  83. printk("Destroy [%s]\n",stu->m_name);
  84. kfree(stu);
  85. }
  86. }
  87. /*模块初始化接口*/
  88. static int student_mgt_init(void)
  89. {
  90. printk("Student Managment System,Initializing...\n");
  91. init_system();
  92. dbg_flg = 1;   //从此以后,再调用add_stu()时,都会有有内核打印信息,详见实例训练
  93. dump_students();
  94. return 0;
  95. }
  96. static void student_mgt_exit(void)
  97. {
  98. clean_up();
  99. printk("System Terminated!\n");
  100. }
  101. module_init(student_mgt_init);
  102. module_exit(student_mgt_exit);

Makefile:

点击(此处)折叠或打开

  1. obj-m += student.o tools.o
  2. CURRENT_PATH:=$(shell pwd)
  3. LINUX_KERNEL:=$(shell uname -r)
  4. LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
  5. all:
  6. make -I. -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
  7. clean:
  8. make -I. -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

其中tools.c是一个辅助模块,用于实现从用户空间直接调用调用内核空间EXPORT_SYMBOL出来的任意一个API接口,比如add_stu()、del_stu()或者dump_students()等等。OK,万事俱备,只欠东风,一条make命令下去,然后好戏正式开始:

 

总的来说,Linux内核链表的使用还算比较简单基础,是内核学习的入门必修课。当然实际项目中,对链表进行插入或者删除时如果有同步或者互斥需求,则需要采用诸如互斥锁之类的内核保护手段,防止对链表操作时出现竞争冒险现象。

Linux内核【链表】整理笔记(2) 【转】的更多相关文章

  1. Linux内核分析课程笔记(一)

    linux内核分析课程笔记(一) 冯诺依曼体系结构 冯诺依曼体系结构实际上就是存储程序计算机. 从两个层面来讲: 从硬件的角度来看,冯诺依曼体系结构逻辑上可以抽象成CPU和内存,通过总线相连.CPU上 ...

  2. C语言 Linux内核链表(企业级链表)

    //Linux内核链表(企业级链表) #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> ...

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

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

  4. Linux 内核链表

    一 . Linux内核链表 1 . 内核链表函数 1.INIT_LIST_HEAD:创建链表 2.list_add:在链表头插入节点 3.list_add_tail:在链表尾插入节点 4.list_d ...

  5. linux内核链表分析

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

  6. 深入分析 Linux 内核链表

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

  7. Linux 内核 链表 的简单模拟(2)

    接上一篇Linux 内核 链表 的简单模拟(1) 第五章:Linux内核链表的遍历 /** * list_for_each - iterate over a list * @pos: the & ...

  8. Linux 内核 链表 的简单模拟(1)

    第零章:扯扯淡 出一个有意思的题目:用一个宏定义FIND求一个结构体struct里某个变量相对struc的编移量,如 struct student { int a; //FIND(struct stu ...

  9. linux内核链表的移植与使用

    一.  Linux内核链表为双向循环链表,和数据结构中所学链表类似,具体不再细讲.由于在内核中所实现的函数十分经典,所以移植出来方便后期应用程序中的使用. /********************* ...

  10. [国嵌攻略][108][Linux内核链表]

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

随机推荐

  1. 【bzoj1821】[JSOI2010]Group 部落划分 Group Kruskal

    题目描述 聪聪研究发现,荒岛野人总是过着群居的生活,但是,并不是整个荒岛上的所有野人都属于同一个部落,野人们总是拉帮结派形成属于自己的部落,不同的部落之间则经常发生争斗.只是,这一切都成为谜团了——聪 ...

  2. Django 2.0 学习(11):Django setuptools

    应用打包 当前状态的Python包与各种工具有点儿混乱,本结我们将学习使用setuptools来构建应用包.该工具是强烈推荐使用的打包工具,之后我们也会使用pip去安装和卸载它. Python打包指的 ...

  3. 【原创】U盘插入磁盘显示脱机解决

    问题说明:插入U盘,电脑可识别硬件,打开我的电脑,无显示U盘所在的磁盘.,并且在计算机管理的磁盘管理看到的U盘为脱机状态 解决方案:1.打开命令,输入 diskpart  回车,输入list disk ...

  4. Android 动画之View动画效果和Activity切换动画效果

    View动画效果: 1.>>Tween动画通过对View的内容进行一系列的图形变换(平移.缩放.旋转.透明度变换)实现动画效果,补间动画需要使用<set>节点作为根节点,子节点 ...

  5. 【BZOJ1941】Hide and Seek(KD-Tree)

    [BZOJ1941]Hide and Seek(KD-Tree) 题面 BZOJ 洛谷 题解 \(KD-Tree\)对于每个点搜一下最近点和最远点就好了 #include<iostream> ...

  6. HDOJ.2094 产生冠军(map)

    产生冠军 点我挑战题目 点我一起学习STL-MAP 题意分析 给出n组数据,代表a打败了b,让判断根据这n组数据是否能判断出来产生了冠军.一开始以为这道题很难,其实用map可以应付. 大原则,赢了的人 ...

  7. Markdown资料收集

    教程介绍 原生Markdown不支持表格,表格属于扩展Markdown语法 快速入门:https://github.com/riku/Markdown-Syntax-CN/blob/master/ba ...

  8. mysql语句进阶

    1.null mysql> create table worker(id int not null,name varchar(8) not null,pass varchar(20) not n ...

  9. mysql的主从复制原理与实现

    关于mysql的主从复制,之前一直在听说这个话题,一直没有实现,昨天学习了下,原来是这么回事: 既然是主从复制,那么肯定有主有从,也就说一个主数据库(一般为写库),一个从数据库(读库).主数据库更新了 ...

  10. 同一台服务器(电脑)运行多个Tomcat

    同一台电脑运行不能同时运行多个未修改过配置tomcat的原因在于:一台电脑的一个端口只能被一个程序使用,多个tomcat启动会因为端口号号被占用的原因而启动失败. 如果想要在一台电脑上同时运行多个to ...