使用要求
为了使用 list.h 中的链表操作宏,我们在定义结构体时需要对结构体进行一个改变,在结构体中要多添加一个
struct
list_head
类型的字段,例如:
typedef struct student
{
char first_name[MAX_STRING_LENGTH];
char last_name[MAX_STRING_LENGTH];
unsigned int age;
struct list_head node_student;
}student_t;
那么在 list.h 中 list_head 是用来干嘛的呢,我们看它的定义
// import from include/linux/types.h
struct list_head {
struct list_head *next, *prev;
};
很明显这个结构体可以用来记录前一个元素和后一个元素的位置。
初始化(INIT_LIST_HEAD)
在对链表进行操作之前,首先我们需要为链表指定好链表头,以方便管理,后面的操作接口中的参数很多也是要和这个链表头有关联,在操作之前,要先定义一个
struct
list_head
的变量用来充当链表头,然后经过
INIT_LIST_HEAD
接口去初始化好这个头节点。
struct list_head class;
INIT_LIST_HEAD(&class);
其中 INIT_LIST_HEAD 在 list.h 中定义如下
static inline void INIT_LIST_HEAD(struct list_head *list) {
list->next = list;
list->prev = list;
}
可以看到,他把 list_head 节点进行了初始化,使它首先指向自身。
添加元素(list_add_tail)
当定义好一个节点元素后,就可以使用 list_add_tail 宏将节点加入链表中
例如:
student_t *stu = NULL;
/* 创建节点. */
if ((stu = make_student("Pierre", "Dupont", 16)) == NULL)
{
fprintf(stderr, "Failed to create Pierre.\n");
return -1;
}
/*将 stu 加入链表*/
list_add_tail(&stu->node_student, &class);
可以看到,list_add_tail 的第一个参数是待加入节点的
struct
list_head
类型字段的地址,第二个参数是头节点的地址
list.h 中的定义
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
其中__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;
}
读者可自己画个读,就不难发现 list_add_tail 宏所实现的是一个类头插法,它是将节点插入在头节点的前面,但是头节点的位置不会变,也就是以我们一开始定义的头节点为头,新来的节点始终插入在这个头节点的前面,所以可以看到__list_add 中的操作就是将新节点插入在头节点和头节点前面元素之间。
遍历操作(list_for_each_entry)
操作示例:
student_t *stu = NULL;
/* Print all students in class. */
printf("All the students in class.\n");
list_for_each_entry(stu, &class, node_student)
{
printf("First name: %s\n", stu->first_name);
printf("Last name: %s\n", stu->last_name);
printf("Age: %u\n\n", stu->age);
}
再来看看 list.h 中是如何去定义
list_for_each_entry
的
/**
* list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*/
#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))
list_for_each_entry
的第一个参数是一个节点指针,用来访问节点元素,第二个参数是头节点指针,第三个参数是节点中定义的
struct
list_head
类型字段的名字,
list_first_entry
是返回头节点下一个节点的节点地址,因为头节点就是一个
struct
list_head
类型,没有数据元素域,只做管理用,
list_next_entry
是寻找下一个节点,所以 list_for_each_entry
管理了一个 for 循环,去遍历链表元素。
合并链表(list_splice_init)
这个操作是为了用另外一个头来管理这片链表或则将一个链表加入到另一个链表中,例如:
list_splice_init(&class, &bus);
上面代码的意思是将 class 管理的链表加入到 bus 中让 bus 管理。
其中 list.h 对
list_splice_init
的定义为:
/**
* list_splice_init - join two lists and reinitialise the emptied
list.
* @list: the new list to add.
* @head: the place to add it in the first list.
*
* The list at @list is reinitialised
*/
static inline void list_splice_init(struct list_head *list,struct list_head *head)
{
if (!list_empty(list))
{
__list_splice(list, head, head->next);
INIT_LIST_HEAD(list);
}
}
__list_splice 的定义如下
static inline void __list_splice(const struct list_head *list,struct list_head *prev,struct list_head *next)
{
struct list_head *first = list->next;
struct list_head *last = list->prev;
first->prev = prev;
prev->next = first;
last->next = next;
next->prev = last;
}
读者画图可以很清楚知道,list_splice_init
就是将 head 头节点管理的链表添加到 list 管理的链表中,INIT_LIST_HEAD
是将该头节点初始化,也就是脱离和原链表的联系。
删除元素(list_del)
使用示例:
student_t *tmp = NULL;
student_t *stu = NULL;
list_for_each_entry_safe(stu, tmp, &bus, node_student)
{
if (strcmp(stu->first_name, "Celine") == 0)
{
list_del(&stu->node_student);
free(stu);
}
}
该例子是删除链表中 first_name 元素名为 Celine 的节点,同时使用
list_for_each_entry_safe
对链表进行遍历,这个与前面我们讲到的
list_for_each_entry
是不一样的,我们先来看看 list.h 中怎么去定义
list_for_each_entry_safe
的
/**
* list_for_each_entry_safe - iterate over list of given type
safe against removal of list entry
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*/
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_first_entry(head, typeof(*pos), member), \
n = list_next_entry(pos, member); \
&pos->member != (head); \
pos = n, n = list_next_entry(n, member))
可以看到
list_for_each_entry_safe
相对与
list_for_each_entry
多了一个参数
n,
是用来做遍历用的,因为 list_for_each_entry_safe
是配合删除操作,所以
pos
指向的节点可能会被删除,而 n
指向可能被删除节点的下一个节点。
再来看看
list_del
在 list.h 中的定义
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
其中__list_del 定义为
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
next->prev = prev;
prev->next = next;
}
这个不难看出 list_del 就是实现了 entry 结点从结点中删除,其中 LIST_POISON1 和LIST_POISON2 是
用于验证是否没有人使用未初始化的列表项。
# define POISON_POINTER_DELTA (0)
#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)