Linux内核链表理解与使用

一. Linux内核链表简介

        Linux内核中需要经常用到双链表,该链表只有指针域,没有数据域。在很多的数据结构中都会嵌入struct list_head结构体变量,它可以使结构体加入到一个双向链表中。链表的初始化,增加,删除等操作的接口在linux-x.x.x/include/linux/list.h里面,内核链表在内核中使用的是如此广泛,所以需要深刻的理解。

二. Linux内核链表使用介绍

1. 基本数据结构

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

        从结构体成员看,内核链表是一个双向链表,没有数据域,next指向下一个链表元素,prev指向上一个链表元素。

2. 链表的初始化

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

#define LIST_HEAD(name) \
	struct list_head name = LIST_HEAD_INIT(name)

        链表声明和初始化的通常用LIST_HEAD(name)处理。LIST_HEAD定义了一个变量struct list变量name,并且name的next和prev指针都指向name的地址。如下图所示

 3. 链表元素添加

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;
}

# 向头部添加 (在链表头指针和链表第一个元素之间插入元素)
static inline void list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}

# 向尾部添加 (在链表最后一个元素和链表头指针之间插入元素)
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
	__list_add(new, head->prev, head);
}

        __list_add函数的作用是在prev和next指针指向的元素之间插入new元素。如下图所示。

        list_add函数向__list_add函数传入的prev和next参数分别是head和head->next,所以list_add函数是在链表头指针和链表第一个元素之间插入new元素。

        list_add_tail函数向__list_add函数传入的prev和next指针分别是head->prev和head,head->prev就是指向链表的最后一个元素,所以list_add_tail是在链表最后一个元素和链表头指针之间插入元素。

4. 链表元素删除

static inline void __list_del(struct list_head * prev, struct list_head * next)
{
	next->prev = prev;
	prev->next = next;
}

static inline void __list_del_entry(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
}

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函数的作用是删除prev和next指针指向的元素之间的元素,如下图所示

         __list_del_entry函数向__list_del函数传入的的prev和next参数分别指向是要删除元素的前一个元素和后一个元素。

        list_del函数和__list_del_entry函数的区别是将删除元素的前向指针和后向指针都指向一个非法值,如元素从链表中删除后仍然反问,则会产生内存页错误。

5. 链表的遍历

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


/**
 * list_for_each_safe - iterate over a list safe against removal of list entry
 * @pos:	the &struct list_head to use as a loop cursor.
 * @n:		another &struct list_head to use as temporary storage
 * @head:	the head for your list.
 */
#define list_for_each_safe(pos, n, head) \
	for (pos = (head)->next, n = pos->next; pos != (head); \
		pos = n, n = pos->next)

/**
 * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
 * @pos:	the &struct list_head to use as a loop cursor.
 * @n:		another &struct list_head to use as temporary storage
 * @head:	the head for your list.
 */
#define list_for_each_prev_safe(pos, n, head) \
	for (pos = (head)->prev, n = pos->prev; \
	     pos != (head); \
	     pos = n, n = pos->prev)

/**
 * 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_struct 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函数是按照从前往后的顺序遍历链表,通过不断指向元素的next元素,直到元素的指针和链表头指针地址相同,则表示链表遍历完成。

        list_for_each_prev函数则是从链表的尾部元素向前遍历。

        list_for_each_safe函数引入了指针n,用于存储pos的下一个元素的地址。引入指针n可以方便在遍历链表的时候删除pos指向的元素,而不影响遍历。list_for_each无法做到这一点。

        list_for_each_prev_safe函数和list_for_each_safe函数的区别是从后往前遍历。

        list_for_each_entry函数是list_for_each和list_entry的结合,有pos,head,member三个参数,pos是一个中间变量,指向当前访问的链表元素,head指链表头,member指pos指向的结构体中链表成员变量的名称,示意图如下:

6. 查找链表元素

/**
 * 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_struct within the struct.
 */
#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)

        list_entry宏有三个参数ptr,type,member。ptr是指数据结构中struct list_head变量成员的地址,type是指数据结构的类型,member是指数据结构中struct list_head的变量名。list_entry宏的结果是ptr指向的type类型的数据结构的变量地址。

示意图如下:

        

代码示例

struct task_struct {
    ...
    struct list_head tasks;
    ...
};

        如果要获取某个struct task_struct类型的变量的地址,ptr指向该变量的tasks指针变量的地址,则可以这么写

struct task_struct *addr = list_entry(ptr, struct task_struct, tasks);

举例:list_for_each和list_entry配合使用

        list_for_each函数用于获取数据结构中struct list_head成员的地址,list_entry则是通过struct list_head成员的地址获取到数据结构的地址。

代码示例:

list_for_each(pos, &list)
{
    struct task_struct *addr = list_entry(pos, struct task_struct, tasks);
}

三. 总结

代码示例:

#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/rculist.h>

struct stu_info
{
   char* name;
   int age;
   void (*print)(struct stu_info *stu);
   struct list_head list;
};

void print_info(struct stu_info *stu)
{
    printk("name = %s, age = %d\n", stu->name, stu->age);
}

struct stu_info students[8] = {
	{"Zhao", 20, print_info},
	{"Qian", 21, print_info},
	{"Sun", 22, print_info},
	{"Li", 23, print_info},
};

LIST_HEAD(stu);

static int student_list_init(void)
{
	struct list_head *pos;
	struct stu_info *s;

	list_add(&students[0].list, &stu);
	list_add(&students[1].list, &stu);
	list_add(&students[2].list, &stu);
	list_add(&students[3].list, &stu);

	list_for_each(pos, &stu)
	{
		s = list_entry(pos, struct stu_info, list);
		s->print(s);
	}
	

    return 0;
}

static void student_list_exit(void)
{
   printk("student_list_exit\n");

   return;
}

module_init(student_list_init);
module_exit(student_list_exit);
MODULE_LICENSE("Dual BSD/GPL");

输出结果:

[19764.110000] name = Li, age = 23
[19764.120000] name = Sun, age = 22
[19764.120000] name = Qian, age = 21
[19764.130000] name = Zhao, age = 20

  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
链表Linux 内核中最基础、最常用的数据结构之一,用于实现各种数据结构和算法,如队列、栈、哈希表、红黑树等。下面是 Linux 内核链表的完整使用流程: 1. 定义链表节点结构体 链表节点结构体一般包含两个成员:一个指向前一个节点的指针 prev,一个指向后一个节点的指针 next。定义链表节点结构体的代码如下: ``` struct list_head { struct list_head *prev, *next; }; ``` 2. 初始化链表头 初始化链表头时,需要将链表头的 prev 和 next 指针都指向自己,表示链表为空。初始化链表头的代码如下: ``` struct list_head my_list = {&my_list, &my_list}; ``` 3. 插入节点到链表中 通过调用 list_add 函数可以将一个节点插入到链表的头部。list_add 函数的第一个参数是要插入的节点的指针,第二个参数是链表头的指针。插入节点到链表中的代码如下: ``` struct list_head *node; // 初始化节点 list_add(node, &my_list); ``` 4. 遍历链表 通过遍历链表,可以访问链表中的每个节点,从而实现对链表的操作。遍历链表的代码如下: ``` struct list_head *pos; // 遍历链表 list_for_each(pos, &my_list) { // 访问链表节点 } ``` 5. 删除链表中的节点 通过调用 list_del 函数可以从链表中删除一个节点。list_del 函数的参数是要删除的节点的指针。删除链表中的节点的代码如下: ``` struct list_head *node; // 从链表中删除节点 list_del(node); ``` 这就是 Linux 内核链表的完整使用流程。在实际开发中,链表是一个非常常用的数据结构,掌握 Linux 内核链表使用方法对于理解使用内核中的其他数据结构和算法都非常重要。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值