Github: Bidirect-list
Email: BuddyZhang1 buddy.zhang@aliyun.com
目录
简介
Linux 内核提供了一套完整的双链表机制,使开发者可以在内核私有数据结构中轻松
构建双链表。双链表的操作包括很多种,其中遍历双链表是按正序或者倒序的方式遍历
双链表中的节点。Linux 提供的遍历接口如下:
从上面的各种遍历接口可以看出,双链表可以通过多种方式遍历。但在上面的遍历接口中,
存在一类带 safe 的接口,这类接口和不带 safe 接口有什么区别,以及实际运用场景如何?
例如:
这两个接口的功能都是从链表头开始,正序遍历所有的接口。但 safe 为安全的遍历所有的接口,
那安全在什么地方?首先查看两个接口的定义差别,如下:
从两个接口的定义差别只看出,list_for_each_safe() 的定义多了一个参数 n,这个参数 n
用于缓存下一个节点,其余并没有什么逻辑上的差异。接着可以查看一下两个函数实际的遍历
效果,如下:
从两个函数的遍历效果来看,并未什么差异。接下来分别用两个函数遍历所有的节点,并在
遍历的过程中,每遍历到一个节点,就删除一个节点。测试代码如下:
list_for_each 测试代码
运行上面的驱动,运行结果如下:
从上面的运行结果看出,使用 list_for_each() 函数遍历过程中,如果删除节点,那么
就会引起内核的 panic,内核会提示对 NULL 的引用。
list_for_each_safe 测试代码
运行上面的驱动,运行结果如下:
从上面的运行结果看出,使用 list_for_each_safe() 函数遍历过程中,如果删除节点,那么
不会引起内核的 panic。
原理分析
在上面的实践中,使用 list_for_each() 函数的时候,当每次遍历一个节点的时候,
list_for_each() 函数通过当前节点找到下一个节点,如下:
如果是正常的遍历,那么下一个节点可以从当前节点中获得。如果此时将当前节点从链表中
删除之后,此时当前节点的 next 和 prev 指针已经被指向了一个不可用的地址。如果此时
还再使用当前节点去查找下一个节点的会必然会引起内核 panic。因此此时需要使用 safe 类
的接口解决这个问题,正如 list_for_each_safe() 函数定义,如下:
每次遍历的时候,函数都会提前将下一个节点缓存在 n 参数里,如果当前节点被删除之后,
下一个节点也可以从 n 参数中获得,这样不会导致内核 panic。
实践总结
带 safe 类的接口和不带 safe 类的接口在遍历每个节点的时候,并没有差别。但如果在
遍历的时候需要删除当前节点,那么就需要使用带 safe 类的接口。
附录
BiscuitOS Home
Linux 双链表
BiscuitOS Driver
BiscuitOS Kernel Build
Linux Kernel
Bootlin: Elixir Cross Referencer
搭建高效的 Linux 开发环境
赞赏一下吧 🙂