Linux内核list

    xiaoxiao2021-03-25  124

    Linux内核提供了一个创建双向链表的结构体——list_head。虽然Linux内核是用C语言编写的,但是list_head的引入使得内核的数据结构拥有了一些面向对象的特性。通过list_head可以很容易地实现C语言的代码重用,这一点类似于C++的继承机制。

    1.Linux内核中list_head数据结构的定义:

    struct list_head { struct list_head *next, *prev; }; 第一眼看上去我们就可以很清楚地看出这个数据结构可以构造出双向链表。但是,仔细一看我们会觉得它比较奇怪,是的它确实很奇怪,奇怪的地方在于它只有指针域没有数据域。那么你也许会问居然这个数据结构没有数据域那么又有什么意义呢?其实这个数据结构不会单独使用,而是嵌入到其他的数据结构中使用的,起到连接链表的作用,next指向下一个节点prev指向上一个节点,在程序里寻找数据时只需要寻找到相应的节点进而寻找到相应的数据。

    2.list_head双向链表头指针的定义和相关初始化函数

    /* 下面两条宏用于定义一个list_head类型的头指针name(链表的头指针和尾指针都指向name本身) */ #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) /* 下面的函数也用于定义一个list_head类型的头指针 */ static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; // next指针指向list本身 list->prev = list; // prev指针指向list本身 }

    3.增加节点

    3.1 在链表的头部插入节点

    static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); }

    3.2 在链表的尾部插入节点 

    static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); }

    3.3 __list_add函数的实现

    static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } 仔细分析上面的代码,就会发现上述代码虽然很简单但是代码的运用很巧妙。将函数做好封装以后,通过很简单的调用就可以实现节点的插入。

    4.删除节点

    4.1 第一种删除节点的方法

    static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; }

    4.2 第二种删除节点的方法

    static inline void list_del_init(struct list_head *entry) { __list_del_entry(entry); INIT_LIST_HEAD(entry); }

    4.3 __list_del函数源码

    static inline void __list_del(struct list_head * prev, struct list_head * next) { next->prev = prev; prev->next = next; } prev为要删除节点的前一个节点,next为要删除节点的下一个节点。该函数删除节点的思路是,要删除的节点的前一个节点的next指向要删除节点的后一个节点,要删除节点的后一个节点的prev指向要删除节点的前一个节点,这样就把要删除的节点从链表中剔除出去了。

    4.3 __list_del_entry函数源码

    static inline void __list_del_entry(struct list_head *entry) { __list_del(entry->prev, entry->next); } entry为要删除的节点,entry->prev为要删除节点的上一个节点,entry->next为要删除节点的下一个节点。删除节点的思路和__list_del相同。 从上面的代码我们可以看出,利用list_del函数和list_del_init函数都可以删除链表中的一个节点。但是,调用list_del函数来删除节点不安全。因为,链表中的链表删除完毕以后为了不让被删除的节点的指针乱指,需要对指针进行处理,在list_del中对这两个指针分别赋值为LIST_POISON1和LIST_POISON2这是两个宏,其定义为: #define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA) #define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA) 这两个值是两个用户不可用的地址,就相当于把prev和next屏蔽掉,不能再通过prev和next将被删除的节点切入链表,属于不安全的方式。我们再来看看list_del_init函数删除完节点以后对指针是怎样处理的,list_del_init函数在删除节点以后会调用了宏INIT_LIST_HEAD,将被删除链表的prev和next指向自己,从而实现了对节点的安全删除。

    5.遍历链表

    5.1 链表遍历宏list_for_each_entry(pos, head, member)

    #define list_for_each_entry(pos, head, member) \ for (pos = list_first_entry(head, typeof(*pos), member); \ &pos->member != (head); \ pos = list_next_entry(pos, member))

    5.2 辅助宏

    #define list_first_entry(ptr, type, member) \ list_entry((ptr)->next, type, member) #define list_next_entry(pos, member) \ list_entry((pos)->member.next, typeof(*(pos)), member) #define list_entry(ptr, type, member) \ container_of(ptr, type, member)

    5.3 list_for_each_entry(pos, head, member)宏展开

    #define list_for_each_entry(pos, head, member) \ for ( pos = container_of((head)->next, typeof(*pos), member);\ // 指针赋值 &pos->member != (head); \ // 如果没有回到头部则继续执行 pos = container_of((pos)->member.next, typeof(*(pos)), member)) // 指向下一个节点

    6.container_of宏深度剖析

    转自:揭开linux内核中container_of的神秘面纱

    Container_of在Linux内核中是一个常用的宏,用于从包含在某个结构中的指针获得结构本身的指针,通俗地讲就是通过结构体变量中某个成员的首地址进而获得整个结构体变量的首地址。

    6.1 container_of宏原型:

    #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) 参数说明: ptr:表示结构体中member的地址 type:表示结构体类型 member:表示结构体中的成员 通过ptr的地址返回所在结构体的首地址

    6.2 container_of宏分解

    (1)const typeof( ((type *)0)->member ) *__mptr = (ptr); 定义一个临时的ptr类型(通过typeof( ((type *)0)->member )获取ptr的数据类型)的指针变量__mptr,用来保存ptr的值(结构体中member的地址)。 (2)(type *) ( (char *)__mptr - offsetof(type,member) ); (char *)__mptr减去member在type结构体中的偏移量,得到的值就是整个type结构体变量的首地址(整个宏的返回值就是这个首地址)。

    6.3 offsetof宏原型

    #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

    其中,size_t是无符号长整形(long unsigned int),那么我们就可以知道最终我们得到的是一个长整形的数值。&((TYPE *)0)->MEMBER表示MEMBER相对于TYPE类型数据结构0地址(TYPE类型数据结构起始地址)的偏移值。这样我们就可以得到MEMBER在TYPE结构中的偏移值。 举例如下:

    #include <stdio.h> struct test { int i; int j; char k; }; int main(void) { struct test temp; printf("&temp = %p\n", &temp); // temp的起始地址 printf("&(temp.k) = %p\n", &(temp.k)); // temp.k的地址 printf("&(((struct test *)0)->k) = %ld\n", (size_t)&((struct test *)0)->k); //k在temp中的偏移量 return 0; } 运行结果: &temp = 0x7ffc7212e9e0 &(temp.k) = 0x7ffc7212e9e8 &(((struct test *)0)->k) = 8

    转载请注明原文地址: https://ju.6miu.com/read-6719.html

    最新回复(0)