[linux内核笔记-2]linux中的数据结构(1)---- list_head

时间:2022-12-01 23:39:24

/* @author = s1n */

/*文中出现的源码均来自linux 3.16.41内核*/

1.实现双向循环链表的一般方法:

 1 struct list_node1{
 2     struct list_node1 *next,*prev;
 3     type1 m1;
 4     type2 m2;
 5 };
 6 struct list_node2{
 7     struct list_node2 *next,*prev;
 8     type1 m1;
 9     type2 m2;
10 };

如果对于每一种数据结构都定义了其特定的实现和对应的方法,对于具有大量不同数据结构,都要使用链表的系统中,无疑使代码变得重复和臃肿。因此需要实现一种通用的双向链表方法,对各种数据结构都能适用。

2.list_head的实现:

struct list_head {
    struct list_head *next, *prev;
};

1).list_head头节点定义的两个宏,定义在/linux/list.h中

1 #define LIST_HEAD_INIT(name) { &(name), &(name) } //就是用head的地址初始化其两个成员next和prev ,使其都指向自己。 
2 #define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) 
3 #define INIT_LIST_HEAD(ptr) do { \ 
4       (ptr)->next = (ptr); (ptr)->prev = (ptr); \ 
5 } while (0)

则定义一个双向链表的头节点:

1 /*方法1*/
2 struct list_head head; 
3 LIST_HEAD_INIT(head);
4 
5 /*方法2*/
6 LIST_HEAD(head);

这样,我们就定义并初始化了一个头节点

3.list_head操作:

这些函数都作为内联函数也都定义list.h中,这里要说明一下linux源码 的一个风格,在下面的这些函数中以下划线开始的函数是给内部调用的函数,而以符开始的函数就是对外使用的函数,这些函数一般都是调用以下划线开始的函数,或是说是对下划线开始的函数的封装
 
1).增加节点操作:
 1 /** 
 2  * __list_add - Insert a new entry between two known consecutive entries. 
 3  * @new: 
 4  * @prev: 
 5  * @next: 
 6  * 
 7  * This is only for internal list manipulation where we know the prev/next 
 8  * entries 
 9  */ 
10 static __inline__ void __list_add(struct list_head * new, 
11              struct list_head * prev, struct list_head * next) 
12 { 
13        next->prev = new; 
14        new->next = next; 
15        new->prev = prev; 
16        prev->next = new; 
17 } 
18 //这个函数在prev和next间插入一个节点new。 
19 
20 /** 
21  * list_add - add a new entry 
22  * @new: new entry to be added 
23  * @head: list head to add it after 
24  * 
25  * Insert a new entry after the specified head. 
26  * This is good for implementing stacks. 
27  */
 1 static __inline__ void list_add(struct list_head *new, struct list_head *head) 
 2 { 
 3        __list_add(new, head, head->next); 
 4 } 
 5 //这个函数在head节点后面插入new节点。 
 6 
 7 /** 
 8  * list_add_tail - add a new entry 
 9  * @new: new entry to be added 
10  * @head: list head to add it before 
11  * 
12  * Insert a new entry before the specified head. 
13  * This is useful for implementing queues. 
14  */ 
15 static __inline__ void list_add_tail(struct list_head *new, struct list_head *head) 
16 { 
17        __list_add(new, head->prev, head);
18 }

 

2).删除节点操作:

 1 /** 
 2  * __list_del - 
 3  * @prev: 
 4  * @next: 
 5  * 
 6  * Delete a list entry by making the prev/next entries point to each other. 
 7  * 
 8  * This is only for internal list manipulation where we know the prev/next 
 9  * entries 
10  */ 
11 static __inline__ void __list_del(struct list_head * prev, 
12              struct list_head * next) 
13 { 
14        next->prev = prev; 
15        prev->next = next; 
16 } 
17 
18 /** 
19  * list_del - deletes entry from list. 
20  * @entry: the element to delete from the list. 
21  * * Note: list_empty on entry does not return true after this, the entry is in 
22  * an undefined state. 
23  */ 
24 static __inline__ void list_del(struct list_head *entry) 
25 { 
26       __list_del(entry->prev, entry->next); 
27 } 
28 
29 /** 
30  * list_del_init - deletes entry from list and reinitialize it. 
31  * @entry: the element to delete from the list. 
32  */ 
33 static __inline__ void list_del_init(struct list_head *entry) 
34 { 
35       __list_del(entry->prev, entry->next); 
36        INIT_LIST_HEAD(entry); 
37 }
list_del(struct list_head *entry)是从链表中删除entry节点。 
list_del_init(struct list_head *entry) 不但从链表中删除节点,还把这个节点的向前向后指针都指向自己,即初始化。
 
3).判断链表是否为空操作:
1 /** 
2  * list_empty - tests whether a list is empty 
3  * @head: the list to test. 
4  */ 
5 static __inline__ int list_empty(struct list_head *head) 
6 { 
7       return head->next == head; 
8 }

 

4.一个结构体通过list_head实现双链表

1).如果某种结构体需要双向链表结构,则将list_head作为其中的成员:

1 struct kobject {
2     const char        *name;
3     struct list_head    entry;
4     struct kobject        *parent;
5     struct kset        *kset;
6 }

形成了如下的结构:

[linux内核笔记-2]linux中的数据结构(1)---- list_head

2).list_entry()

该函数是通过kobject 链表结构中的 list_head 成员entry访问下一个成员的宏:

 ,ptr : 是指向当前kobject结构对象中的数据成员entry,(char *)(ptr):entry成员地址,

 ,(char *)(ptr):entry成员地址----地址A;

 ,(unsigned long)(&((type *)0)->member)) : 将地址0转化为类型为type(struct kobject )对象,取member(entry)成员的地址----地址B

 ,(type *)(地址A-地址B): 得到kobject结构对象的首地址,转化为kobject对象

 

 1 /**  * list_entry - get the struct for this entry  
 2 * @ptr: the &struct list_head pointer.  
 3 * @type: the type of the struct this is embedded in.  
 4 * @member: the name of the list_struct within the struct.  */ 
 5 
 6  #define list_entry(ptr, type, member) \ 
 7          ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) 
 8  
 9  //访问链表成员
10  kobject *obj  = objList;
11  kobject *nextObj = (kobject *)list_entry(obj->entry->next,struct kobject,entry);

 

即为如下结构:

[linux内核笔记-2]linux中的数据结构(1)---- list_head

3).一个实例:

首先,定义如下结构体:

1 struct person 
2 { 
3       int age; 
4       int weight; 
5       struct list_head list; 
6 };

现定义一指针:

struct list_head *pos = person;

则获取这个指针所指向的结构的变量:

struct person *one = list_entry(pos, struct person, list);

代入到宏中:

((struct person *)((char *)(pos)-(unsigned long)(&((struct person *)0)->list)))
整个过程分成两部分:
PartI.(char *)(pos)减去(unsigned long)(&((struct person *)0)- >list)
PartII.转成(struct person *)类型的指针
解释:
(char *)(pos):将pos由struct list_head*转 成char*  
(unsigned long)(&((struct person *)0)->list):
StepI. (struct person *)0),把0 地址转成struct person指针
StepII.(struct person *)0)->list就是指向list变量
StepIII.&((struct person *)0)->list是取这个变量的地址
StepIIII.(unsigned long)(&((struct person *)0)->list)把这个变量的地址值转换成一个整形
 
总结一下,(unsigned long)(&((struct person *)0)->list)的意思就是取list 变量在struct person结构中的偏移量。
 
更进一步:
((char *)(pos) - (unsigned long)(&((struct person *)0)->list))
就是将pos指针往前移动所获取的偏移量的位置位置,即是本来pos是struct list_head类型,它即是list。即是把 pos指针往struct person结构的头地址位置移动过去,当pos移到struct person结构头后就转成(struct person *)指针,这样就可以得到struct person *变量了。 
 
5.list_head的遍历:
list_head的遍历宏定义如下:
 1 /** 
 2  * list_for_each - iterate over a list 
 3  * @pos: the &struct list_head to use as a loop counter. 
 4  * @head: the head for your list. 
 5  */ 
 6 #define list_for_each(pos, head) \ 
 7       for (pos = (head)->next; pos != (head); pos = pos->next) 
 8 
 9 /** 
10  * list_for_each_safe - iterate over a list safe against removal of list entry 
11  * @pos: the &struct list_head to use as a loop counter. 
12  * @n: another &struct list_head to use as temporary storage 
13  * @head: the head for your list. 
14  */ 
15 #define list_for_each_safe(pos, n, head) \ 
16       for (pos = (head)->next, n = pos->next; pos != (head); \ 
17             pos = n, n = pos->next)

list_for_each()遍历整个head链表中的每个元素,每个元素都用pos指向。 

而后者list_for_each_safe()使用了n作为一个临时的指针。若进行删除操作,当pos被删除后,还可以用n来获得下一个元素的位置。

6.总例:

 1 #include <stdio.h> 
 2 #include "list.h" 
 3 
 4 struct person 
 5 { 
 6       int age; 
 7 
 8       int weight; 
 9       struct list_head list; 
10 }; 
11 
12 int main(int argc, char* argv[]) 
13 { 
14       struct person *tmp; 
15       struct list_head *pos, *n; 
16       int age_i, weight_j; 
17 
18       // 定义并初始化一个链表头 
19       struct person person_head; 
20       INIT_LIST_HEAD(&person_head.list); 
21 
22       for(age_i = 10, weight_j = 35; age_i < 40; age_i += 5, weight_j += 5) 
23       { 
24             tmp =(struct person*)malloc(sizeof(struct person)); 
25             tmp->age = age_i; 
26             tmp->weight = weight_j; 
27 
28             // 把这个节点链接到链表后面 
29             // 这里因为每次的节点都是加在person_head的后面,所以先加进来的节点就在链表里的最 
30 后面 
31 
32             // 打印的时候看到的顺序就是先加进来的就在最后面打印 
33             list_add(&(tmp->list), &(person_head.list)); 
34 
35       } 
36 
37       // 下面把这个链表中各个节点的值打印出来 
38       printf("\n"); 
39       printf("=========== print the list ===============\n"); 
40       list_for_each(pos, &person_head.list) 
41       { 
42             // 这里我们用list_entry来取得pos所在的结构的指针 
43             tmp = list_entry(pos, struct person, list); 
44             printf("age:%d, weight: %d \n", tmp->age, tmp->weight); 
45       } 
46       printf("\n"); 
47 
48       // 下面删除一个节点中,age为20的节点 
49       printf("========== print list after delete a node which age is 20 
50 ==========\n"); 
51       list_for_each_safe(pos, n, &person_head.list) 
52       { 
53 
54         tmp = list_entry(pos, struct person, list); 
55               if(tmp->age == 20) 
56               { 
57                     list_del_init(pos); 
58                     free(tmp); 
59               } 
60 
61        } 
62 
63        list_for_each(pos, &person_head.list) 
64        { 
65               tmp = list_entry(pos, struct person, list); 
66               printf("age:%d, weight: %d \n", tmp->age, tmp->weight); 
67        } 
68 
69        // 释放资源 
70        list_for_each_safe(pos, n, &person_head.list) 
71        { 
72               tmp = list_entry(pos, struct person, list); 
73               list_del_init(pos); 
74               free(tmp); 
75        } 
76 
77        return 0; 
78 }