Linux内核中经典链表 list_head 常见使用方法解析

匿名 (未验证) 提交于 2019-12-02 21:59:42

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

一. 创建链表

#define LIST_HEAD_INIT(name) { &(name), &(name) }  #define LIST_HEAD(name) \ 	struct list_head name = LIST_HEAD_INIT(name)  static inline void INIT_LIST_HEAD(struct list_head *list) { 	WRITE_ONCE(list->next, list); 	list->prev = list; }


   struct list_head mylist = {&mylist,  &mylist} ;   

struct  my_task_list {     int val ;     struct list_head mylist; }

创建第一个节点

struct my_task_list first_task =  { .val = 1,   .mylist = LIST_HEAD_INIT(first_task.mylist) };

这样mylist 就prev 和 next指针分别指向mylist自己了,如下图:



二. 添加节点

内核已经提供了添加节点的接口了

/**  * 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再调用__list_add接口

/*  * 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) { 	if (!__list_add_valid(new, prev, next)) 		return;  	next->prev = new; 	new->next = next; 	new->prev = prev; 	WRITE_ONCE(prev->next, new); }

其实就是在head 链表头后和链表头后第一个节点之间插入一个新节点。然后这个新的节点就变成了链表头后的第一个节点了。

依然用上面的my_task_list结构体举例子

 LIST_HEAD(header_task);

然后再创建实际的第一个节点

struct my_task_list my_first_task =  { .val = 1,   .mylist = LIST_HEAD_INIT(my_first_task.mylist) };

接着把这个节点插入到header_task之后

list_add(&my_first_task.mylist,  &header_task);



然后在创建第二个节点,同样把它插入到header_task之后

struct my_task_list my_second_task =  { .val = 2,   .mylist = LIST_HEAD_INIT(my_second_task.mylist) };
其实还可以用另外一个接口 INIT_LIST_HEAD 进行初始化(参数为指针变量), 如下:
struct my_task_list my_second_task; my_second_task.val = 2; INIT_LIST_HEAD(&my_second_task.mylist);

list_add(&my_second_task.mylist, &header_task)


以此类推,每次插入一个新节点,都是紧靠着header节点,而之前插入的节点依次排序靠后,那最后一个节点则是第一次插入header后的那个节点。最终可得出:先来的节点靠后,而后来的节点靠前,“先进后出,后进先出”。所以此种结构类似于 stack“堆栈”, 而 header_task就类似于内核stack中的栈顶指针esp, 它都是紧靠着最后push到栈的元素。

上面所讲的list_add接口是从链表头header后添加的节点。 同样,内核也提供了从链表尾处向前添加节点的接口list_add_tail. 让我们来看一下它的具体实现。

/**  * 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); }

从注释可得出:(1)在一个特定的链表头前面插入一个节点

(2)这个方法很适用于队列的实现 (why?)

进一步把__list_add ()展开如下:

/*  * 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) { 	if (!__list_add_valid(new, prev, next)) 		return;  	next->prev = new; 	new->next = next; 	new->prev = prev; 	WRITE_ONCE(prev->next, new); }

(1)创建一个 链表头(实际上应该是表尾), 同样可调用 LIST_HEAD(header_task);





依此类推,每次插入的新节点都是紧挨着 header_task表尾,而插入的第一个节点my_first_task排在了第一位,my_second_task排在了第二位,可得出:先插入的节点排在前面,后插入的节点排在后面,“先进先出,后进后出”,这不正是队列的特点吗(First in First out)!

三. 删除节点

static inline void list_del(struct list_head *entry) { 	__list_del_entry(entry); 	entry->next = LIST_POISON1; 	entry->prev = LIST_POISON2; }
/*  * Delete a list entry by making the prev/next entries  * point to each other.  *  * This is only for internal list manipulation where we know  * the prev/next entries already!  */ static inline void __list_del(struct list_head * prev, struct list_head * next) { 	next->prev = prev; 	WRITE_ONCE(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.  */ static inline void __list_del_entry(struct list_head *entry) { 	if (!__list_del_entry_valid(entry)) 		return;  	__list_del(entry->prev, entry->next); }

四. 链表遍历

/**  * list_for_each	-	iterate over a list  * @pos:	the &struct list_head to use as a loop cursor.  * @head:	the head for your list.  */ #define list_for_each(pos, head) \ 	for (pos = (head)->next; pos != (head); pos = pos->next)

/**  * list_for_each_prev	-	iterate over a list backwards  * @pos:	the &struct list_head to use as a loop cursor.  * @head:	the head for your list.  */ #define list_for_each_prev(pos, head) \ 	for (pos = (head)->prev; pos != (head); pos = pos->prev)

五. 宿主结构


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

struct  my_task_list {     int val ;     struct list_head mylist; }
那我们如何根据mylist这个字段的地址而找到宿主结构my_task_list的位置呢???

container_of(ptr, type, member)

/**  * list_entry - get the struct for this entry  * @ptr:	the &struct list_head pointer.  * @type:	the type of the struct this is embedded in.  * @member:	the name of the list_head within the struct.  */ #define list_entry(ptr, type, member) \ 	container_of(ptr, type, member)

/**   * container_of - cast a member of a structure out to the containing structure   * @ptr:    the pointer to the member.   * @type:   the type of the container struct this is embedded in.   * @member: the name of the member within the struct.   *   */   #define container_of(ptr, type, member) ({          \       const typeof( ((type *)0)->member ) *__mptr = (ptr); \       (type *)( (char *)__mptr - offsetof(type,member) );})  

而offsetof定义在/include/linux/stddef.h如下:

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

看下container_of宏的注释:

(1)根据结构体重的一个成员变量地址导出包含这个成员变量mem的struct地址。

(2)参数解释:




struct  my_task_list {     int val ;     struct list_head mylist; }
struct my_task_list first_task =  { .val = 1,   .mylist = LIST_HEAD_INIT(first_task.mylist) };

而container_of宏的功能就是根据 first_task.mylist字段的地址得出first_task结构的其实地址。

把上面offsetof的宏定义代入container_of宏中,可得到下面定义:

#define container_of(ptr, type, member) ({          \       const typeof( ((type *)0)->member ) *__mptr = (ptr); \       (type *)( (char *)__mptr - ((size_t) &((type *)0)->member) );})  

再把宏中对应的参数替换成实参:

   const typeof( ((struct my_task_list *)0)->mylist ) *__mptr = (&first_task.mylist); \       (struct my_task_list *)( (char *)__mptr - ((size_t) &((struct my_task_list *)0)->mylist) );})  

GNU对C新增的一个扩展关键字,用于获取一个对象的类型,比如这里((struct my_task_list *)0)->mylist 是把0地址强制转换成struct my_task_list 指针类型,然后取出mylist元素。 然后再对mylist元素做typeof操作,其实就是获取 my_task_list结构中mylist字段的数据类型struct list_head,所以这行语句最后转化为:

第二条语句中在用 __mptr这个指针 减去 mylist字段在 my_task_list中的偏移(把0地址强制转换成struct my_task_list指针类型,然后取出mylist的地址,此时mylist的地址也是相对于0地址的偏移,所以就是mylist字段相对于宿主结构类型struct my_task_list的偏移) 正好就是宿主结构的起始地址。C语言的灵活性得到了很好的展示!!!


2. 宿主结构的遍历

/**  * 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_first_entry - get the first element from a list  * @ptr:	the list head to take the element from.  * @type:	the type of the struct this is embedded in.  * @member:	the name of the list_head within the struct.  *  * Note, that list is expected to be not empty.  */ #define list_first_entry(ptr, type, member) \ 	list_entry((ptr)->next, type, member)  /**  * list_next_entry - get the next element in list  * @pos:	the type * to cursor  * @member:	the name of the list_head within the struct.  */ #define list_next_entry(pos, member) \ 	list_entry((pos)->member.next, typeof(*(pos)), 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))


struct my_task_list *pos_ptr = NULL ;  list_for_each_entry (pos_ptr, & header_task, mylist )      {           printk ("val =  %d\n" , pos_ptr->val);      }


参考文档:https://kernelnewbies.org/FAQ/LinkedLists


            
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!