咱们都知道Linux内核里的双向链表和学校里教给咱们的那种数据结构仍是些不同。Linux采用了一种更通用的设计,将链表以及其相关操做函数从数据自己进行剥离,这样咱们在使用链表的时候就不用本身去实现诸如节点的插入、删除、遍历等操做了。固然,Linux也是从2.1.x内核开始才对链表进行了这样的统一,和咱们目前看到的样子几乎差很少: css
- struct list_head {
- struct list_head *next, *prev;
- };
在2.6.21里这个数据结构定义在include/liinux/list.h头文件里,可是在3.4.1内核里,以及后面要介绍的哈希链表的定义都放在include/linux/types.h头文件里。而本文将以3.4.1内核为例进行介绍,其实对链表来讲内核的版本号几乎没什么影响,只要掌握了Linux设计链表的精髓,万变不离其宗。
今天咱们首先来聊聊链表。从上述定义代码咱们能够看出,Linux内核的链表是双向链表,若是咱们要将本身的数据结构以链表的形式进行组织,那么只要在咱们本身的数据结构里,增长一个struct list_head{}类型的结构体成员对象就能够了,这样,咱们就能够很方便地使用内核提供给咱们的一组标准接口来对链表进行各类操做。
若是咱们须要定义一个链表,内核有LIST_HEAD(name)这样的函数供咱们使用: 数据结构
- #define LIST_HEAD(name) \
- struct list_head name = LIST_HEAD_INIT(name)
其中#define
LIST_HEAD_INIT(name) { &(name), &(name) },其实这样的代码已经太简单不过了。若是咱们要定义一个名为
student_list的链表,直接LIST_HEAD(
student_list)就能够了,展开后等价于下面的代码:
- struct list_head student_list= { &(student_list), &(student_list) };
跟内核通知链相似,若是咱们已经有了一个链表对象
student_list,
INIT_LIST_HEAD()接口能够对它初始化。因此,LIST_HEAD(
student_list)代码和下面的代码是等价的:
- struct list_head student_list;
- INIT_LIST_HEAD (&student_list);
假如,咱们如今要定义一个学生的结构体,并让其组织成链表的形式,能够这样作:
设计
- #define NAME_MAX_SIZE 32
-
- typedef struct student{
- char name[NAME_MAX_SIZE]; /*姓名*/
- unsigned char sex; /*性别:m-男生;f-女生*/
- unsigned char age; /*年龄*/
- struct list_head stu_list; /*全部的学生最终经过这个结构串成链表*/
- }Student;
那么在写代码时,若是是经过
kmalloc之类的函数动态建立节点,咱们就能够用下面代码对链表节点进行初始化:
- Student *stu1;
- stu1 = kmalloc(sizeof(*stu1), GFP_KERNEL);
- strcpy(stu1->name,”xiaoming”);
- stu1->sex = ‘m’;
- stu1->age = 18;
- INIT_LIST_HEAD(&stu1-> stu_list); /*和下面的用法注意区别*/
若是是静态定义结构体变量的话就更简单了:
- Student stu2={
- .name={“xiaohong”},
- .sex=’f’,
- .age=18,
- .stu_list = LIST_HEAD_INIT(stu2.stu_list); /*和上面的用法注意区别*/
- };
有了数据节点,接下来就要对其进行操做了,内核提供了一组经常使用接口用于对双向链表操做,以下。
还有关于链表的分割list_cut_position(*list,*head,*entry)以及合并list_splice(*list,*head)、list_splice_init (*list,*head)、list_splice_tail (*list,*head)、list_splice_tail_init (*list,*head)这几个API用法也都很是简单,对照内核源码的注释很轻松就能够上手了。
经过上面的图咱们能够看出来,在内核中当咱们说起链表头的时候其实并无牵扯到咱们本身的结构体数据自己,链表头的next所指向的节点才是真正意义上的“链表头节点”,prev所指向的节点叫作“链表尾节点”。注意,不要把链表头和链表的头节点混为一谈。有了这个认识以后,咱们就知道若是链表头的next和prev都指向链表头自己的话,那么这个链表其实就是空的,例如list_empty()或者list_empty_careful()所作的事情就是给定一个链表头,判断其是否为空,便是否包含任何有效的数据节点。一样地,如何判断链表是否只有一个节点呢?看看list_is_singular()的实现就豁然开朗了,真的是so easy。
最后,将前面说起的API总结到下表2.1中,方便你们查阅。
须要注意的是,上述全部链表操做函数的入参都是struct list_head{}的指针类型,这一点须要时刻牢记在心。
未完,待续…