linux内核学习——list链表
链表使用基础:
链表是linux内核中最简单,最普遍的数据结构。刚开始接触内核的人可能会对linux的链表操作有点不习惯。因为内核链表与大家平时用的链表不同,它不是把数据结构放到链表里面,而是把链表节点放到数据结构里面。linux内核的链表代码在<linux/list.h>中声明。
链表代码在头文件<linux/list.h>中声明,其数据结构很简单:
struct list_head{ struct list_head *next; struct list_head *prev; }next指针指向下一个链表节点,prev指向前一个。从这里可以看出,list链表是双向链表。但链表存储的具体内容是什么呢?其关键就在于理解list_head结构是怎么被使用的。
struct fox{ unsigned long tail_length; unsigned long weight; bool is_fantastic; struct list_head list; //所有fox结构体形成链表 }使用container_of()宏可以很方便地从链表指针找到父结构中包含的任何变量,这是因为在C语言中,一个给定结构的变量偏移在编译时地址就被ABI固定下来了。现在,如果我们要访问fox中的一个变量,得先获得该struct fox结构体的地址,如下:
struct fox *one = ***;
struct fox *another;
struct list_head *p = &one->list; //如果我们已经知道结构体中的一个变量地址
则another = container_of(p, struct fox, list)就获得了该结构体的指针,然后我们就可以访问结构体中任意成员了。
内核中contain_of()宏的实现:
#define container_of(ptr, type, member)({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char*)__mtr - offsetof(type, member) );}) #define list_entry(ptr, type, member) \ container_of(ptr, type, member)参数ptr为结构体中某一变量的地址,type为该结构体类型,member为该结构体类型中指针p对应的成员变量。
内核里链表实现为带有头节点的双向循环链表。所以我们定义一个链表时要有一个链表头,继续上面例子,我们定义并初始化一个关于fox的链表:
list_head fox_list;
static LIST_HEAD(fox_list); //链表头也是一个list_head类型
链表操作:
链表操作无非就是增加、删除和遍历,下面就逐一介绍。
向链表增加一个节点:
list_add(struct list_head *new, struct list_head *head)
该函数向指定链表的head节点后插入一个new节点,实际上就是把new节点放到了链表元素的第一个位置(头节点不包含数据的哦)。
假如我们创建一个新的struct fox节点,并把它加入fox_list,那么我们这样做:
struct fox f;
list_add(&f->list, &fox_list);
如果想想链表尾部添加一个节点,可以使用下面函数:
list_add_tail(struct list_head *new, struct list_head *head)
该函数向指定链表的head节点之前插入一个new节点。
看一下内核的实现:
/* * Insert a new entry between two known consecutive entries. * * This is only for internal list manipulation where we know * the prev/next entries already! */ 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 - add a new entry * @new: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } /** * list_add_tail - add a new entry * @new: new entry to be added * @head: list head to add it before * * Insert a new entry before the specified head. * This is useful for implementing queues. */ static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); }向链表删除一个节点:
list_del(struct list_head *entry)
该函数从链表中删除entry函数,但不会释放entry或释放包含entry的数据结构体锁占用的内存,该函数仅仅是把entry元素从链表中一走,如果需要释放内存,则另需操作。
其内核代码实现:
static inline void __list_del(struct list_head *prev, struct list_head *next) { next->prev = prev; prev->next = next; } /** * list_del - deletes entry from list. * @entry: the element to delete from the list. * Note: list_empty() on entry does not return true after this, the entry is * in an undefined state. */ #ifndef CONFIG_DEBUG_LIST static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = (void *)0xDEADBEEF; entry->prev = (void *)0xBEEFDEAD; }移动和合并链表节点:
把节点从一个链表移动到另一个链表:
list_move(struct list_head *list, struct list_head *head)
该函数从一个链表中移除list项,然后将其放入到另一个链表的head节点后面,如果想移动到另一个链表的末尾,则使用下面函数:
list_move_tail(struct list_head *list, struct list_head *head)
把两个未连接的链表合并到一起:
list_splice(struct list_head *list, struct list_head *head)
将list指向的链表插入到指定链表的head元素的后面。
遍历整个链表:
链表遍历访问是链表操作中非常重要的一个操作,也是经常使用的。我们创建链表的目的就是为了访问链表中的数据。
链表访问用法如下(还是用上面的例子):
struct list_head *p;
struct fox *f;
list_for_each(p, fox_list){
f = list_entry(p, struct fox, list); //p 指向链表中的元素
/* 开始对元素进行访问 */
}
链表遍历有个更方便的用法:list_for_each_entry(pos, head, member)
上面的例子也可以这样访问:
struct fox *f;
list_for_each_entry(f, &fox_list, list){
/* 开始对元素进行访问 */
}
反向遍历链表:
list_for_each_entry_reverse(pos, head, member)
遍历的同时删除:
标准的链表遍历方法在遍历链表的同时要想删除节点是不行的,具体原因就根据内核源码自己琢磨吧。
linux内核提供了如下的操作方法:list_for_each_entry_safe(pos, next, head, member)
list_for_each_entry_safe_reverse(pos, next, head, member)
其中next和pos是相同类型,也是list_head指针,内核实现:/** * 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_struct within the struct. */ #define list_for_each_entry_safe(pos, n, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member), \ n = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, typeof(*n), member)) /** * list_for_each_entry_safe_reverse * @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_struct within the struct. * * Iterate backwards over list of given type, safe against removal * of list entry. */ #define list_for_each_entry_safe_reverse(pos, n, head, member) \ for (pos = list_entry((head)->prev, typeof(*pos), member), \ n = list_entry(pos->member.prev, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.prev, typeof(*n), member))