linux内核设计与实现--链表的启发

在这里插入图片描述

背景

最近在抽空看《linux 内核设计与实现》这本经典之作。看到了内核的数据结构一章,发现内核链表的实现确实很优秀。(因为我平时写应用代码也会涉及到链表的操作,相比较而言,就显得我很low,没有对比就没有伤害)。今天就总结一下自己的启发。

工作中的链表使用

我相信做linux 应用开发的工程师,工作中肯定会因为业务需求接触过链表的实现。无论你的链表是用来保存什么数据。它总归会涉及到以下几个接口:

链表的插入:xxx_insert
链表的删除:xxx_delete
链表的遍历:xxx_traverse

因为也就是不同类型的链表,就需要定义不一样的接口,虽然接口内部的逻辑一样,但是形参不一样,还是需要重新定义的。这种的编码方式,我想大部分写应用的工程师都是这样的逻辑。(大牛除外,毕竟山外有山)

但是这样的设计模式,我们就会发现一点不简洁,并且当你链表的类型很多时,就会出现大量的重复代码,十分不简洁。通过内核的链表实现方式,我才受到启发。不得不佩服写内核的大佬们的厉害之处。

内核的链表使用

在内核里面其实也是需要使用到大量的不同类型的链表。但是如果按照我们上面的表述,那么就会有很多的链表操作接口,用来针对不同类型的链表。这样的情况肯定是不会出现在内核里面的(大佬们不允许啊)。于是他们就设计出了一个统一的链表操作接口。任何类型的链表都可以使用。接下来我们就一起来看看是内核是如何实现的。(其中有用到C语言的技巧)

内核里面操作链表的接口,入参都是list_head类型:

struct list_head {
	struct list_head *next;
	struct list_head *prev;
};

比如我们现在有一个fox数据结构,其内容如下:

struct fox {
	int tail_length;	 /*尾巴的长度*/
	int  weight;	  	/*重量*/
} 

在我们应用里面,如果使用该数据结构的链表,一般会如下定义:

struct fox {
	int tail_length;		 /*尾巴的长度*/
	int  weight;	  		/*重量*/
	struct fox * next;
	struct fox * prev;
} 

之后,在根据这个类型定义不同的链表操作接口。

但是内核就会这样定义数据结构。如下:

struct fox {
	int tail_length;		 /*尾巴的长度*/
	int  weight;	  		/*重量*/
	struct list_head list;	/*所有fox结构体形成一个链表*/
} 

从上面数据结构定义的不同,我们可知,内核它不是将数据结构塞入链表,而是将链表节点塞入数据结构

内核中提供了很多对链表操作的接口,比如:

1.  项链表中增加一个节点
list_add(struct list_head *new,struct list_head *head)
默认是将新节点插入到head节点的后面。需要注意的是,内核中的链表都是双向循环链表,因此任何一个节点都可以作为head头节点,进行遍历的开始

list_add_tail(struct list_head *new ,struct list_head head)
把节点增加到链表的尾部。即将节点插入到head的前面

2.从链表中删除节点
list_del(struct list_head* entry)

3.遍历链表 
list_for_each(p,list)
这个其实是一个宏定义:
#define list_for_each(pos, head) \
	 for (pos = (head)->next; pos != (head); pos = pos->next)
内核提供的操作接口有很多,我在这里就不一一说明了。

到了这里,我想大家应该对内核实现链表操作的方式都有了一定了解。觉得这种方式很帅气。但是我们使用链表时,都是想要使用节点里面的有效数据。但是内核的这种链表实现方式,怎么来访问有效数据呢?刚开始,我也有这样的疑惑。通过自己的探索,发现另一个厉害之处。内核找到链表的节点之后,一般都会使用到list_entry

#define list_entry(ptr, type, member) \
   container_of(ptr, type, member)

 #define container_of(ptr, type, member) \
 (type *)((char *)(ptr) - (char *) &((type *)0)->member)

乍看一下,可能看不懂,如果你仔细分析的话,就会发现他的强大之处:

假设我的们节点类型如下:
struct fox {
	int tail_length;		 /*尾巴的长度*/
	int  weight;	  		/*重量*/
	struct list_head list;	/*所有fox结构体形成一个链表*/
} 

并且通过链表操作接口找到了一个list_head节点。调用list_entry接口
list_entry(&list,struct fox,list);
其实它返回的时list_head节点所在数据结构的首地址。即,
fox *p = NULL;
p = list_entry(&list,struct fox,list);
之后就可以通过p之中访问fox数据结构中的任意成员了。

这样的方式和数据结构里面有多少成员没有关系,因此她才会有通用性。

通过了解到内核的这种实现方式,我建议以后在应用层,我们也应该这样做,提供一个通用的接口,这样可以减少我们的开发量,对代码的维护也会变得方便

若我的内容对您有所帮助,还请关注我的公众号。不定期分享干活,剖析案例,也可以一起讨论分享。
我的宗旨:
踩完您工作中的所有坑并分享给您,让你的工作无bug,人生尽是坦途
在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谢艺华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值