【Linux内核源码分析】内核数据结构

链表

链表是Linux内核中最简单、最普通的数据结构

Linux内核中的实现

在2.1内核开发系列中,首次引入了官方内核链表实现

在<linux/list.h>中声明

c 复制代码
struct list_head {
	struct list_head *next, *prev;
};

使用宏container_of()可以方便地从链表指针找到父结构中包含的任何变量

c 复制代码
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->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)); })

使用container_of()宏,定义一个简单的函数可返回包含list_head的父类型结构体

c 复制代码
/**
 * 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()方法,内核提供了创建、操作以及其他链表管理的各种方法,所有这些方法都不需要知道list_head锁嵌入对象的数据结构

操作链表

内核中提供了一组函数来操作链表,这些函数都要使用一个list_head结构体指针做参数,函数都是用C语言以内联函数实现的
所有这些函数的复杂度是O(1)

  1. 向链表中添加一个节点
c 复制代码
static inline void list_add(struct list_head *new, struct list_head *head)

该函数向指定链表的head节点后插入new节点

  1. 从链表中删除节点
c 复制代码
static inline void list_del(struct list_head *entry)

该函数从链表中删除entry()元素,该操作并不会释放entry 或释放包含entry的数据结构体所占用的内存,只是仅仅将entry元素从链表中移走

c 复制代码
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
	next->prev = prev;
	prev->next = next;
}
static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
}

从链表中删除一个节点并对其重新初始化

c 复制代码
static inline void list_del_init(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	INIT_LIST_HEAD(entry);
}

虽然链表不再需要entry项,但是还可以再次使用包含entry的数据结构体

  1. 移动和合并链表节点

把一个节点从一个链表移到另一个链表

c 复制代码
static inline void list_move(struct list_head *list, struct list_head *head)

该函数从一个链表中移除list项,然后将其加入到另一个链表的head节点后面

把节点从一个链表移到另一个链表的末尾

c 复制代码
static inline void list_move_tail(struct list_head *list,
				  struct list_head *head)

该函数和list_move()函数一样,唯一不同的是将list项插入到head项前

检查链表是否为空

c 复制代码
static inline int list_empty(const struct list_head *head)

将两个未连接的链表合并在一起

c 复制代码
static inline void list_splice(const struct list_head *list,
				struct list_head *head)

它将list指向的链表插入到指定链表的head元素后面

把两个未连接的链表合并在一起,并重新初始化原来的链表

c 复制代码
static inline void list_splice_init(struct list_head *list,
				    struct list_head *head)

遍历链表

  1. 基本方法

使用list_for_each()宏,该宏使用两个list_head类型的参数

第一个参数指向当前项

第二个参数是需要遍历的链表的以头节点形式存在的list_head

每次遍历中,第一个参数在链表中不断移动指向下一个元素,直到所有元素都被访问为止

c 复制代码
#define list_for_each(pos, head) \
	for (pos = (head)->next; prefetch(pos->next), pos != (head); \
        	pos = pos->next)
  1. 可用的方法

多数内核代码采用list_for_each_entry()宏来遍历链表

这里的pos是一个包含list_head节点对象的指针,可将它看作是list_entry宏的返回值,head是一个指向头节点的指针

c 复制代码
#define list_for_each_entry(pos, head, member)				\
	for (pos = list_entry((head)->next, typeof(*pos), member);	\
	     prefetch(pos->member.next), &pos->member != (head); 	\
	     pos = list_entry(pos->member.next, typeof(*pos), member))

内核中的例子:

来自inotify:内核文件系统的更新通知机制

在inode结构串联起来的inotify_watches链表中,查找inotify_handle与所提供的句柄相匹配的inotify_watche项

c 复制代码
/*
 * inotify_find_handle - find the watch associated with the given inode and
 * handle
 *
 * Callers must hold inode->inotify_mutex.
 */
static struct inotify_watch *inode_find_handle(struct inode *inode,
					       struct inotify_handle *ih)
{
	struct inotify_watch *watch;

	list_for_each_entry(watch, &inode->inotify_watches, i_list) {
		if (watch->ih == ih)
			return watch;
	}

	return NULL;
}
  1. 反向遍历链表

沿着prev指针向后遍历

c 复制代码
#define list_for_each_entry_reverse(pos, head, member)			\
	for (pos = list_entry((head)->prev, typeof(*pos), member);	\
	     prefetch(pos->member.prev), &pos->member != (head); 	\
	     pos = list_entry(pos->member.prev, typeof(*pos), member))
  1. 遍历的同时删除

如果当前项在遍历循环中被删除,那么接下来的遍历就无法获取next/prev指针了

因此开发人员在潜在的删除操作之前保存next/prev指针到一个临时变量中,以便安全删除当前项

c 复制代码
#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))

例子:

c 复制代码
/**
 * inotify_inode_is_dead - an inode has been deleted, cleanup any watches
 * @inode: inode that is about to be removed
 */
void inotify_inode_is_dead(struct inode *inode)
{
	struct inotify_watch *watch, *next;

	mutex_lock(&inode->inotify_mutex);
	list_for_each_entry_safe(watch, next, &inode->inotify_watches, i_list) {
		struct inotify_handle *ih = watch->ih;
		mutex_lock(&ih->mutex);
		inotify_remove_watch_locked(ih, watch);
		mutex_unlock(&ih->mutex);
	}
	mutex_unlock(&inode->inotify_mutex);
}

反向遍历链表的同时删除

c 复制代码
#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))

队列

Linux内核通用队列实现称为kfifo,它实现在文件<kernel/kfifo.c>

kfifo

主要提供了两个操作:enqueue和dequeue

维护了两个偏移量:入口偏移量和出口偏移

创建队列

c 复制代码
int kfifo_alloc(struct kfifo *fifo, unsigned int size, gfp_t gfp_mask)

该函数创建并初始化一个大小为size的kfifo,内核使用gfp_mask标识分配队列

成功返回0,错误则返回一个负数错误码

自己分配缓冲,可以调用

c 复制代码
void kfifo_init(struct kfifo *fifo, void *buffer, unsigned int size)

该函数创建并初始化一个kfifo对象,它使用buffer指向size字节大小的内存

对于kfifo_allor()和kfifo_init(),size()必须是2的幂

静态声明kfifo更简单,但不大常用

c 复制代码
DECLARE_KFIFO(name, size);
INIT_KFIFO(name)

推入队列数据

推入队列数据需要通过kfifo_in()方法完成

c 复制代码
unsigned int kfifo_in(struct kfifo *fifo, const void *from,
				unsigned int len)

该函数把from指针所指的len字节数据拷贝到fifo所指的队列中

成功返回推入字节的大小,如果队列中空闲字节小于len,则该函数值最多可拷贝队列可用空间那么多的数据,这样的话,返回值可能小于len,也有可能返回0

摘取

相关推荐
chxii2 小时前
linux 下用 acme.sh 搞定 Nginx 免费 SSL 证书自动续期(下) 对于acme.sh命令安装详解
linux·运维·服务器
Bert.Cai2 小时前
Linux more命令详解
linux·运维
minji...2 小时前
Linux 多线程(四)线程等待,线程分离,线程管理,C++多线程,pthread库
linux·运维·开发语言·网络·c++·算法
ZGUIZ2 小时前
Ubuntu 25.10 无法外接显示器解决方案
linux·运维·ubuntu
H_BB2 小时前
DFS实现回溯算法
数据结构·c++·算法·深度优先
汀、人工智能2 小时前
[特殊字符] 第17课:滑动窗口最大值
数据结构·算法·数据库架构·图论·bfs·滑动窗口最大值
QJtDK1R5a2 小时前
V4L2 vs GStreamer vs FFmpeg:Linux多媒体处理的三个层级
linux·运维·ffmpeg
倔强的石头1062 小时前
【Linux指南】基础IO系列(四):文件描述符 fd——Linux 文件操作的 “万能钥匙”
linux·运维·服务器
wzb562 小时前
把 Vim 打造成 Nginx 开发 / 调试 IDE(WSL Ubuntu 完整教程)
linux·ide·nginx·ubuntu·vim·c/c++