linux内核中的链表实现
linux中内核中链表的实现方式与众不同,它不是将数据结构塞入链表中,而是将链表节点塞入数据结构。
链表代码在头文件<linux/list.h>中声明,其数据结构很简单:
struct list_head{
struct list_head *next;
struct list_head * prev;
};
next指针指向下一个链表节点,pre指针指向前一个。然后,似乎这里还看不出它们有多大的作用。到底什么才是链表存储的具体内容呢?
struct fox{
unsigned long tail_length;/*尾巴长度,以厘米为单位*/
unsigned long weight:/*重量,以千克为单位*/
bool is_fantastic:
struct list_head list;/*所有fox结构体形成链表*/
}
上述结构中,fox中的list.next指向下一个元素,list.pre指向前一个元素。现在链表已经能用了,但是显然还不够方便。因此内核又提供了一组链表操作流程。比如list_add()方法加入一个新节点到链表中。但是这些方法都有一个统一的特点:他们只接受list_head 结构作为参数。使用宏container_or()我们可以很方便地从链表指针找到父结构中包含的任何变量。这是因为在C语言中,一个给定结构中的变量偏移在编译时就被ABI固定下来了。
#define container_of(ptr,type,member) ({
const typeof( (type*)0 ->member *__mptr = (ptr));
(type*) ( (char*)__mptr - offsetof(type,member); );
})
核心作用是从一个结构体(struct)中某个成员的指针,计算出整个结构体的起始地址(指针)。这在处理复杂数据结构如链表、树或内核对象时非常有用,因为它允许你通过成员字段直接访问整个容器结构。
作用详解:
宏的功能:给定一个指向结构体成员的指针(ptr),结构体的类型(type),以及成员的名称(member),这个宏能返回一个指向整个结构体的指针。简单说,就是“通过成员找容器”。
为什么需要它:在C语言中,结构体成员在内存中是连续存储的。如果你只有某个成员的地址(比如链表节点的指针),但需要访问整个结构体(比如包含链表节点的数据结构),这个宏能快速计算出偏移量并定位到起始位置。
宏的解析(逐部分解释):
#define container_of(ptr, type, member) ({const typeof( ((type *)0)->member ) *__mptr = (ptr); // 步骤1: 定义临时指针变量(type *)( (char *)__mptr - offsetof(type, member) ); // 步骤2: 计算并转换地址
})
1、const typeof( ((type *)0)->member ) *__mptr = (ptr);: • ((type *)0)->member:这是一个技巧,它模拟一个指向type类型结构体的空指针(0地址),并访问其member成员。这样就能获取member的类型(使用typeof)。 • typeof(…):在编译时获取成员的类型,确保类型安全。 • *__mptr = (ptr):定义一个临时指针变量__mptr,赋值为传入的ptr(成员指针)。const修饰确保变量不被修改。
2. (type *)( (char *)__mptr - offsetof(type, member) );: • offsetof(type, member):这是一个标准宏(通常在stddef.h中定义),计算成员member在结构体type中的字节偏移量。 • (char *)__mptr:将成员指针转换为char *类型,因为偏移量是以字节为单位的,这样可以进行精确的指针算术。 • (char *)__mptr - offsetof(type, member):从成员指针减去偏移量,得到整个结构体的起始地址。 • (type *):最后将地址强制转换为指向type类型的指针,这样你就可以直接使用这个指针访问结构体的其他字段。
struct person {
int age;
char name[20];
struct list_head list; // 假设这是一个链表节点成员
};
• 如果有一个list_head *ptr指向person结构体中的list成员。 • 使用container_of(ptr, struct person, list): ◦ 计算list在struct person中的偏移量(比如8字节)。 ◦ 从ptr减去8字节,得到struct person的起始地址。 ◦ 这样,你就可以访问整个person结构体,如person_ptr->age或person_ptr->name。 优势与注意事项: • 优势:高效、类型安全,避免了手动计算偏移量的错误,常用于内核或嵌入式开发。 • 注意事项:这个宏依赖于GNU C扩展(如typeof和({…})语法),所以在非GCC编译器中可能不可用。使用时确保参数正确,否则会导致未定义行为(如野指针)。