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

文章详细介绍了Linux内核中双向链表的定义,包括structlist_head结构,链表的初始化、判空、插入、删除和遍历等操作。通过示例代码展示了如何使用这些基本操作,并提到了container_of宏用于获取节点的完整结构。此外,文章还强调了Linux链表的简洁性和通用性,它是构建其他复杂数据结构的基础。
摘要由CSDN通过智能技术生成

双向链表是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内核链表 1、普通链表的数据区域的局限性 之前定义数据区域时直接int data,我们认为我们的链表需要存储的是一个int类型的数。但是实际上现实编程链接的节点不可能这么简单,而是多种多样的。 一般实际项目链表,节点存储的数据其实是一个结构体,这个结构包含若干的成员,这些成员加起来构成了我们的节点数据区域。 2、一般性解决思路:即把数据区封装为一个结构体 (1)因为链表实际解决的问题是多种多样的,所以内部数据区域的结构体构成也是多种多样的。 这样也导致了不同程序当链表总体构成是多种多样的。 我们无法通过一套泛性的、普遍适用的操作函数来访问所有的链表,意味着我们设计一个链表就得写一套链表的操作函数(节点创建、插入、删除、遍历……)。 (2)实际上深层次分析会发现 不同的链表虽然这些方法不能通用需要单独写,但是实际上内部的思路和方法是相同的,只是函数的局部地区有不同。 实际上链表操作是相同的,而涉及到数据区域的操作就有不同 (3)问题 能不能有一种办法把所有链表操作方法里共同的部分提取出来用一套标准方法实现,然后把不同的部分留着让具体链表的实现者自己去处理。 3、内核链表的设计思路 (1)内核链表实现一个纯链表的封装,以及纯链表的各种操作函数 纯链表就是没有数据区域,只有前后向指针; 各种操作函数是节点创建、插入、删除、遍历。 这个纯链表本身自己没有任何用处,它的用法是给我们具体链表作为核心来调用。 4、list.h文件简介 (1)内核核心纯链表的实现在include/linux/list.h文件 (2)list.h就是一个纯链表的完整封装,包含节点定义和各种链表操作方法。 二、内核链表的基本算法和使用简介 1、内核链表的节点创建、删除、遍历等 2、内核链表的使用实践 (1)问题:内核链表只有纯链表,没有数据区域,怎么使用? 使用方法是将内核链表作为将来整个数据结构结构体的一个成员内嵌进去。类似于公司收购,实现被收购公司的功能。 这里面要借助container_of宏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值