linux内核中的list.h文件中线性链表的分析(一)

一、背景

初次接触linux内核,从最简单的数据结构,线性表结构开始学习。使用cd命令进入/usr/src/kernel/3.10.0-229.el7.x86_64/include/linux中,找到其中的list.h文件并分析它。

二、分析过程

1、初始化链表头

我们可以看到这里有两个宏定义。第一个是带有变量的宏定义。第二个宏定义使用了第一个宏定义LIST_HEAD_INIT(name)。

这样,当要申明并初始化自己的链表头如head时,就可以直接调用宏,LIST_HEAD(head)。

如此就有初始化struct list_head head = {&(head),&(head)}。

那么结构体类型struct list_head是在哪里定义的呢?

往回翻我们可以看到头文件

这里一看就会发现头文件types.h很像是定义struct list_head的文件。

进入这个文件里会找到这样一行:

看到这个定义我们也就知道了,head的前趋与后继指针都指向了head自己(prev and next 与head同为struct list_head结构)。

现在考虑函数,关键字static表明该函数是一个内部函数,限制了函数的作用域,使函数只能在文件内部被调用。

关键字inline告诉编译程序这是一个内联函数,当然此修饰符可以不加,但为了减少内存的占用,还是加入为好。

该函数的作用与宏定义的函数是相同的。均是初始化链表头。

2、添加新结点

后面两个函数通过调用第一个函数实现new结点插入head结点之前或之后。

首先来看第一个调用函数的实现过程:将指向结构体类型struct list_head的指针new与head传入函数中。

现在我假设head结点后跟着的是data结点。

那么

1.next->prev = new

这一步会将head的下一个结点data的prev指针的地址更改为new的地址。

这样data的上一个结点变为了new。

2.new->next = next

这一步会将new的next指针的地址设置成head的本来的下一个结点的地址,即data的地址。

这样new的下一个结点变为了data。

3.new->prev = prev

这一步会将new的prev指针设置成head的地址。

4.prev->next = new

这一步会将head的next指针的地址更改为new的地址。

好了,通过这四步,成功的将new结点插入到了head结点之后的位置。

接下来看第二个调用函数的实现过程:

现在我假设head结点前跟着的是data结点。

那么

1.next->prev = new

这一步会将head结点的prev更改为new的地址。

这样head的前趋结点变为了new。

2.new->next = next

这一步会将new的next指针的地址设置为head的地址。

这样new的后继结点变为了head。

3.new->prev = prev

这一步会将new的prev指针地址设置为head的原来的前趋data的地址。

这样new的前趋结点就是data。

4.prev->next = new

这一步会将data的next指针更改为new的地址。

这样new就成了data的后继结点。

通过这四步,成功的将new结点插入到了head结点之前的位置。

3.删除结点

类似上面添加结点,这里删除结点也是有两个函数来调用另一个函数来实现。

由于两个调用函数功能类似,其中第二个调用函数比第一个调用函数功能上更加完善,

所以这里只就第二个调用函数进行分析。

假设有三个结点data_1,entry,data_2,entry为data_1的后继结点,data_2为entry的后继结点。

将entry的地址传入函数后,entry->prev表示的是data_1的地址,entry->next表示的是data_2的地址。

这两个实参在传入函数__list_del后,就有

data_2->prev = &data_1

data_1->next = &data_2

这就显而易见了,data_1的next指针不再指向entry,而是指向data_2,

并且data_2的prev指针也不再指向entry,而是指向data_1。

接下来的两个赋值语句是将entry的next指针指向变为LIST_POISON1;

将entry的prev指针指向变为LIST_POISON2。这两个均为宏定义,在头文件poison中被定义。

4.结点的替换

以上这两个函数都可以对old结点与new结点进行替换。

但是第二个函数功能更完善,它除了替换外还将old的prev指针与next指针改为了old结点自己。

现进行分析:

假设有结点data_1,old,data_2,且old结点是data_1的后继结点,data_2是old的后继结点。

那么将old替换为new结点的方法很显然是将new的prev指针改为data_1的地址。next指针改为data_2的地址。

并且将data_1的next指针改为new的地址,将data_2的prev指针改为new的地址。

第一个被调用的函数即为如此算法。这里不再赘述。

三、总结

这里先分析了几种线性链表的操作,往下还有许多线性链表的操作值得去分析,但分析方法大致相同。

并且有许多函数是调用其它函数进行操作。

linux内核中源代码的营养价值很高,一是代码的写作思路十分清晰,值得我们学习与借鉴。二是代码的写作可读性非常高,

每个变量与函数的定义都是经过数次推敲写出的,写作习惯值得我们学习与借鉴。三是从linux内核的源代码中我们可以对

许多的基础知识进行检验与实践。








  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值