Linux内核源码中的双链表结构(笔记)

双向链表是Linux中非常重要和基础的一个数据结构,它在Linux内核中是一个基本类型

Linux内核中的链表

一个常见的双向链表可以被定义为

struct my_list{
    void *mydata;
    struct my_list *next;
    struct my_list *prev;
};

不同的使用方法会构造出不同的数据结构

  • 先进先出是队列
  • 只对后继操作是栈
  • 两个节点指向子树就是二叉树

链表基本功能的实现

定义

Linux中的链表定义为:

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

在linux/list.h中有链表的声明和初始化的宏

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

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

判空

/**
 * list_empty - tests whether a list is empty
 * @head: the list to test.
 */
static inline int list_empty(const struct list_head *head)
{
	return head->next == head;
}

非常简单清晰,判断head的next是否指向head本身即可。

插入

/*
 * Insert a new entry between two known consecutive entries.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
#ifndef CONFIG_DEBUG_LIST
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;
}
#else
extern void __list_add(struct list_head *new,
			      struct list_head *prev,
			      struct list_head *next);
#endif

这里主要是提供了一个默认的实现方式,或者通过extern实现也行。
默认的方式很简单清晰,就是分别将prev的后继指向new,next的前驱指向new,new的前驱和后继分别指向prev和next。

遍历

Linux中通过定义一个宏来实现遍历

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

定位

找到节点的位置如何定位到该节点呢?
linux提供了一个定位的宏

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

这里用container_of对宏进行了封装, 本体应该是这样的:

#define list_entry(ptr, tpye, member) \ 
    ((type*)((char*)(ptr) - (unsigned long)(&((type*)0)->member)))

这个实现的功能就是,获得ptr的位置,然后减去member的偏移量,就可以得到该节点的起始地址了,就可以对节点进行操作,而不只单纯的对节点内部的某一成员进行操作。

删除

双向节点的删除操作

/*
 * 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;
	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_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;
}
#else
extern void __list_del_entry(struct list_head *entry);
extern void list_del(struct list_head *entry);
#endif

外部调用list_del函数,传入一个entry的指针,让该entry的前驱和后继相互指向,本身置空。

大道至简,linux中的双链表定义非常简单,无需畏难。之后的很多数据结构都需要基于双链表进行构造。

参考资料

  • 学堂在线-Linux内核分析与应用:https://next.xuetangx.com/course/XIYOU08091001441/14767915

你可能感兴趣的:(Linux内核编程,linux,笔记,链表)