2.内核中链表的创建、挂接与遍历

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wangdapao12138/article/details/81711411

1.普通链表

普通链表是将数据结构中嵌入链表指针,比如next或者previous节点,可以制作单向链表、双向链表、环形链表等。

 

2.内核链表

内核链表是将链表节点塞入数据结构。链表代码在<linux/list.h>中声明。

19: struct list_head {
20: struct list_head *next, *prev;
21: };

Next指向下一个链表节点,prev指针指向前一个。但其中没有数据!!!struct list_head本身其实并没有意义。

3.内核链表怎么用起来的?它的内容在哪里?

我们将struct list_head list加入到一个数据结构中,就可以实现。其中list.next指向下一个元素,list.prev指向前一个元素。

struct student

{  

    int age;

    char name[20];

    struct list_head list;

};

3.1 struct list_head list这里面的元素指针是什么意思?

List元素到底指向的是什么?是数据?是上一个数据结构体struct student?

其实是上一次数据结构中struct list_head list中的指针,也就是说数据结构通过struct list_head list 串起来了,数据结构的串接与数据结构中的数据无关!

我们来画一张图看下普通链表和内核链表的区别:

 

3.2内核链表怎么样访问数据?

普通链表来说,知道结构体指针,就知道了整个结构体的数据,这就不说了,可以参考C语言的博客。但内核链表怎样完成数据访问呢?目前来说我们知道了一个结构体中的一个元素,也就是struct list_head list,那么我们怎样启用访问student结构体中的agename呢?

         从一个结构体元素反推结构体指针,使用的是container_of宏,终于用到了,找到C语言中的笔记来看看。

-------------------------------------------------------------------------------------

container_of(ptr, type, member)的完整宏展开:

#define container_of(ptr, type, member)                 

\  ({const typeof(((type *)0)->member) * __mptr = (ptr);

\(type *)((char *)__mptr - offsetof(type, member)); })

2,可能大伙看得有点蒙,先来分解一下这个语句的结构

我们定义一个变量的格式是:修饰符+变量类型+变量名 = 右值;

修饰符 变量类型 变量名 右值

const typeof( ((type*)0)->member ) *__mptr = (ptr) ;

现在看明白了吗,抛开具体细节,"typeof( ((type*)0)->member )"代表的是一种数据类型,那么它是什么样的数据类型呢?

((type*)0):它把0转换为一个type类型(也就是宿主结构体类型),为什么要这样做,且看后文:

((type*)0)->member:这个0指针指向结构体中的member成员,typeofgccc语言扩展保留字,用于获取变量的类型

typeof( ((type*)0)->member ) *:得出member的数据类型

所以,第2行的结果就是定义一个指向member的指针,并赋值为ptr

3行,我们先看后半部分 offset(type, member)

offsetof定义在/include/linux/stddef.h

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

根据前面的讲解可以知道:&((TYPE *)0)->MEMBER获得MEMBER的地址,此时&stu=0,因为结构体的基地址为0,所以MEMBER自然为其在结构体中的偏移地址。当然也可以把0换成1,这时&stu=1,要想得出功能成员相对于宿主结构体的偏移量还得在后面减去1,所以用0的好处就是使得功能成员的地址是相对于宿主结构体的偏移量。回到第2行,将0改为其他的任意正整数如"1",并不影响最后的结果,因为在第二行中的目的仅仅是获得member的数据类型而已。

(char *)__mptr - offset(type, member):用第2行获得的结构体成员地址减去其在结构体中的偏移值便可得到宿主结构体的地址

可能有些同学觉得疑问,为什么要把__mptr转换为char类型的指针呢,C语言中,一个指向特定数据类型的指针减1,实际上减去的是它所指向数据类型的字节大小sizeof(data),所以这里要把它转换成char类型,不然得不出正确结果

(type*)( (char *)__mptr - offset(type, member) ) 最后把地址转换成宿主结构体的类型type

由此,container_of()实现了根据一个已知结构体成员的指针和变量名得出宿主结构体的地址的功能

------------------------------------------------------------------------------------

那好了,通过container_of宏,通过struct list_head list元素得到结构体struct student的指针,然后通过结构体得到整个结构体中的所有元素!kernel中也定义了这种结构体。

 

从注释说明中,可以看出

输入:ptr为struct list_head的指针,type为大结构体的类型,struct list_head内嵌在大结构体中,memberstruct list_head的变量名。

输出:大结构体的结构体首地址。

作用:通过某个结构体中的某个成员变量的指针,反推这个结构体变量的指针。说白了,我定义一个结构体变量和结构体指针,变量.结构体中某一个成员(这个我是知道的),还有知道它的地址,也就是&(变量.结构体中某一个成员),通过这两个参数,还有结构体的type,返回得到的就是结构体的首地址。

依靠list_entry()方法,内核提供了创建、操作以及其他链表管理的各种例程-----所有的这些方法都不需要知道list_head所嵌入对象的数据结构。

这需要考虑到结构体对齐访问的一些概念,可以参考C语言专题的博客。

4.内核链表的初始化

链表list_head本身其实并没有意义,他需要被嵌入到你自己的数据结构中才能生效。

struct student

{  

    int age;

    char name[20];

    struct list_head list;

};

链表需要在使用前初始化,因为多数元素都是动态创建的,也许这就是需要链表的原因。因此常见的方式是在运行时初始化链表。

4.1第一种初始化:显式初始化

28: static inline void INIT_LIST_HEAD(struct list_head *list)
29: {
30: list->next = list;
31: list->prev = list;
32: }

将前后级指针都指向自己。

4.2第二种初始化:隐式初始化

25: #define LIST_HEAD(name) \
26: struct list_head name = LIST_HEAD_INIT(name)

#define LIST_HEAD_INIT(name) { &(name), &(name) }

宏展开:

#define LIST_HEAD(name) struct list_head name={ &(name), &(name) }

也就是初始化,将前后级指针指向自己。

5.内核链表节点插入

41: static inline void __list_add(struct list_head *new,struct list_head *prev,struct list_head *next)
44: {
45: next->prev = new;
46: new->next = next;
47: new->prev = prev;
48: prev->next = new;
49: }

 

重点是理解,这个函数的参数是什么?是链表结构体的指针,而非链表内部的指针。链表内部的指针实际指向的是结构体,不要搞乱了!

该函数向指定链表的head节点后插入new节点。因为链表是循环的,而且通常没有首尾节点的概念,所以你可以把任何一个节点当成head,如果把最后一个节点当做head的话,那么该函数可以用来实现一个栈。

6.内核尾部插入

78: static inline void list_add_tail(struct list_head *new, struct list_head *head)
79: {
80: __list_add(new, head->prev, head);
81: }

41: static inline void __list_add(struct list_head *new, struct list_head *prev,struct list_head *next)
44: {
45: next->prev = new;
46: new->next = next;
47: new->prev = prev;
48: prev->next = new;
49: }

第一个参数改成了链表的尾部节点,整个的意思就是尾节点和头结点之间插入一个节点!

7.内核链表的删除

83: /*
84: * Delete a list entry by making the prev/next entries
85: * point to each other.
86: *
87: * This is only for internal list manipulation where we know
88: * the prev/next entries already!
89: */
90: static inline void __list_del(struct list_head * prev, struct list_head * next)
91: {
92: next->prev = prev;
93: prev->next = next;
94: }

96: /**
97: * list_del - deletes entry from list.
98: * @entry: the element to delete from the list.
99: * Note: list_empty() on entry does not return true after this, the entry is
100: * in an undefined state.
101: */
102: #ifndef CONFIG_DEBUG_LIST
103: static inline void list_del(struct list_head *entry)
104: {
105: __list_del(entry->prev, entry->next);
106: entry->next = LIST_POISON1;
107: entry->prev = LIST_POISON2;
108: }

正常情况下,后节点的prev指针指向本节点,本节点指针的prev指向前节点。删除之后,就是将后节点的prev指针指向前节点的prev指针,本节点删掉了!

8.内核链表的遍历

 

342: /**
343: * list_entry - get the struct for this entry
344: * @ptr: the &struct list_head pointer.
345: * @type: the type of the struct this is embedded in.
346: * @member: the name of the list_struct within the struct.
347: */
348: #define list_entry(ptr, type, member) \
349: container_of(ptr, type, member)

list_entry用于获取struct list_head结构体指针所在结构体变量的首地址

351: /**
352: * list_first_entry - get the first element from a list
353: * @ptr: the list head to take the element from.
354: * @type: the type of the struct this is embedded in.
355: * @member: the name of the list_struct within the struct.
356: *
357: * Note, that list is expected to be not empty.
358: */
359: #define list_first_entry(ptr, type, member) \
360: list_entry((ptr)->next, type, member)

返回的是后一个节点的prev指针指向的那个结构体。
list_first_entry用于获取链表中第一个节点所在结构体的首地址。
362: /**
363: * list_for_each - iterate over a list
364: * @pos: the &struct list_head to use as a loop cursor.
365: * @head: the head for your list.
366: */
367: #define list_for_each(pos, head) \
368: for (pos = (head)->next; prefetch(pos->next), pos != (head); \
369: pos = pos->next)

list_for_each遍历一个链表。第一个参数用来指向当前项,这是一个你必须要提供的临时变量,第二个参数是需要遍历的链表的以头节点形式存在的list_head。每次遍历中,第一个参数再链表中不断移动指向下一个元素,直到表中的所有元素被访问为止。

342: /**
343: * list_entry - get the struct for this entry
344: * @ptr: the &struct list_head pointer.
345: * @type: the type of the struct this is embedded in.
346: * @member: the name of the list_struct within the struct.
347: */
348: #define list_entry(ptr, type, member) \
349: container_of(ptr, type, member)

414: /**
415: * list_for_each_entry - iterate over list of given type
416: * @pos: the type * to use as a loop cursor.
417: * @head: the head for your list.
418: * @member: the name of the list_struct within the struct.
419: */
420: #define list_for_each_entry(pos, head, member) \
421: for (pos = list_entry((head)->next, typeof(*pos), member); \
422: prefetch(pos->member.next), &pos->member != (head); \
423: pos = list_entry(pos->member.next, typeof(*pos), member))
424:

这里pos是一个指向包含list_head节点对象的指针,可将它看做是list_entry宏的返回值。head是一个指向头节点的指针,即遍历开始位置。

我们将所求结构体类型的指针变量pos、链表的头head和所求结构体内struct list_head的变量名member传到list_for_each_entry之后, list_entry的第一个参数用head->next指向下一个节点,此节点的地址也就是在所属结构体内的struct list_head成员变量的地址,第二个参数用typeof(*pos)求得pos的结构体类型,第三个参数为所求结构体内struct list_head的变量名。

9.链表头

链表需要一个标准的索引指针指向整个链表,即链表的头指针。

内核链表表现中最杰出的特性:每个节点都包含一个list_head指针,于是我们可以从任何一个节点起遍历链表,知道我们看到所有节点。这种方式确实优美,不过有时确实也需要一个特殊指针索引到整个链表,而不是从一个链表节点触发。有趣的是,整个特殊的索引节点事实上也是一个常规的list_head

猜你喜欢

转载自blog.csdn.net/wangdapao12138/article/details/81711411