Linux内核中存在一个非常有用的数据结构list_head,其定义如下:struct list_head {
struct list_head *prev;
struct list_head *next;
};这个结构在内核的许多地方出现,是很多数据结构的组成部分。通过这个部件,这些数据很容易组成一个双向链表。相比为每个需要构造双向链表的结构体都提供构建链表相关的数据和操作,统一的链表管理在代码维护、代码简洁性、代码的可靠性方面更具优势。现在分几点进行讨论。
1. list_head的用法
list_head作为一个组件嵌入到其他的结构体中,如下图所示(来自understand the linux kernel)

特别要注意的是prev是指向上一个结构体中list_head的位置而不是指向上一个结构体,next也一样。
2. list_entry
这个宏的功能主要在于当你知道了某个结构体中list_head的地址时求出这个结构体的地址,是不是很高级
在include/linux/list.h中定义了list_entry
/** * list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
所以我们还得找到container_of的定义,它也是一个宏,定义在了
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})在更早期的版本中的定义有点不太一样:
#define list_entry(ptr, type, member) ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))现就更早版本进行说明,ptr是结构体中的member成员的指针,type是目标结构体的类型(我们的目的就是为type类型的结构体求出地址),其实思想非常简单,就是ptr的地址减去member成员在type中的偏移量,然后就得出了type结构体的地址。
现通过一个例子让读者更容易理解
#include <stdio.h>
#include <stdlib.h>
struct list_head { //
struct list_head *prev;
struct list_head *next;
};
struct test_list{
int testdata; // 用于测试的数据域
struct list_head list; // 将list_head类型嵌套进来
};
int main(void)
{
struct test_list a;
struct test_list *p;
a.testdata = 5; // 将数据赋值
// 求出地址
p = (struct test_list *)((char *)(&a.list)
- (unsigned long)(&(((struct test_list *)0)->list)));
//通过输出证明正确性
printf("testdata = %d\n", p->testdata);
return 0;
}
本文深入探讨了Linux内核中list_head数据结构的使用方法、相关宏解释及其在代码维护、简洁性和可靠性方面的优势,并通过实例展示了如何利用list_head和list_entry宏来构建和操作双向链表。

756

被折叠的 条评论
为什么被折叠?



