Linux内核设计基础(八)之内核数据结构

我个人比较喜欢学习数据结构,而Linux内核中实现的数据结构会是我们去学习、理解和应用数据结构的一个很好途径。这里介绍内核中广泛应用的四种数据结构:链表、队列、映射和二叉树。

链表:

Linux内核讲求高效精简,所以有时需要我们动态去创建和分配内存,这时就要借助链表,我们根据实际情况分配内存后,只需修改链表的指针,仍能索引到刚分配的内存区。链表分单向链表、双向链表和循环链表。

  • 单向链表
struct list_element {
	void *data;
	struct list_element *next; /* 指向下一个元素的指针 */
};
  • 双向链表
struct list_element {
	void *data;
	struct list_element *next; /* 指向下一个元素的指针 */
	struct list_element *prev; /* 指向前一个元素的指针 */
};
  • 循环链表

链表最后一个元素的next指针不为NULL,而是指向链表首元素。循环链表中也分单向和双向。

因为循环双向链表提供了最大的灵活性(没有具体例子的对比,暂时还看不出来,希望懂的朋友们留下评论:),所以Linux内核的标准链表就是采用循环双向链表。

另外,如果需要随机访问数据,一般不使用链表,使用链表的理想情况是需要遍历所有数据或需要动态加入和删除数据时。

不过Linux内核中的链表可不像上面例子那样简单和原始,它不是将数据结构塞入链表,而是将链表节点塞入数据结构。

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

其实这就是所谓的链表节点,那它是如何塞入数据结构的呢?当然是作为数据结构的成员:

struct fox {
	unsigned long tail_length;
	unsigned long weight;
	bool is_fantastic;
	struct list_head list;  /* 所有fox结构体形成链表 */
};

也就是list不再是指向一个fox结构体,而是用.next和.prev指向前后两个list_head结构体。真奇怪,这样的话我们需要知道所指向的两个list_head结构体分别属于哪两个fox结构体,这需要借助宏container_of(),这样我们可以找到这个list_head结构体的父结构(也就是所属的fox结构体)中包含的任何变量。为什么要这样构造链表,希望懂的朋友们留下评论:)

队列:

在内核编程中,我们经常遇到生产者——消费者模型,这时队列是最理想的数据结构,生产者将数据放入队列,消费者则随后取出,先到先得。

映射:

映射指的是一种做法:有一个由唯一键组成的集合,每个键关联一个特定的值,由键得到值得这种关系我们称之为映射。

但用什么数据结构来实现,一般采用散列表(Hash)和自平衡二叉搜索树。也许散列表是我们最熟悉的,构造散列函数,输入唯一键,得到对应值。但其实更多的时候采用二叉树,Linux内核就是如此。

Linux内核中用idr维护一个自定义的映射,用

int idr_get_new(struct idr *idp, void *ptr, int *id);

将id(键)和ptr(值)的映射注入到idr中。而查找操作如下:

void *idr_find(struct idr *idp, int id);

根据id找到对应的指针ptr。

二叉树:

具体红黑树的介绍参见我的博文

红黑树(RED-BLACK TREES)基本概念

内核中每个i节点都有自己的rbtree,以关联在文件中的页偏移,这是红黑树在Linux中的一个应用。

Linux内核设计基础(八)之内核数据结构

时间: 2024-10-13 12:19:43

Linux内核设计基础(八)之内核数据结构的相关文章

Linux内核设计基础(九)之进程管理和调度

在Linux中进程用结构体task_struct来管理一个进程所需的所有信息(所以一般较大,在32位机上,大约有1.7KB).为了提高效率,Linux使用了一些卓越的技术. 通过slab分配task_struct结构 Linux创建进程迅速,正是因为slab分配器预先分配和重复使用task_struct,这样就避免了动态分配和释放所带来的资源消耗(毕竟一个task_struct较大,而且内核中进程的创建和消除很频繁). 将task_struct放置在内核栈的尾端 这样做是为了让那些像x86那样寄

Linux内核设计基础(五)之内存管理

我感觉学习操作系统首先要从内存分配和管理入手.首先我们应该知道现代操作系统是以页为单位进行内存管理的,32位体系结构支持4KB的页,而64位体系结构支持8KB的页.页是用来分配的,如何才能进行高效和充分的利用,这是内存管理单元(MMU)应当仔细考虑的. 页分配 内核用结构体struct page表示每个物理页.内核用这一结构来管理系统中所有的页,因为内核需要知道一个页是否空闲(也就是页有没有被分配),如果页已经被分配,内核需要知道谁拥有这个页,拥有者可能是用户空间进程.动态分配的内核数据.静态内

Linux内核设计基础(十)之内核开发与总结

(1)Linux层次结构: (2)Linux内核组成: 主要由进程调度(SCHED).内存管理(MM).虚拟文件系统(VFS).网络接口(NET)和进程间通信(IPC)等5个子系统组成. (3)与Unix的差异: Linux支持动态加载内核模块 支持对称多处理(SMP)机制 Linux内核可以抢占 Linux内核并不区分线程和其他的一般进程 Linux提供具有设备类的面向对象的设备模型.热插拔事件,以及用户空间的设备文件系统(sysfs) (4)内核开发的特点: 内核编程时既不能访问C库也不能访

Linux内核设计基础(六)之块I/O层

块设备是指能随机访问固定大小数据片的设备,如硬盘:字符设备(如串口和键盘)是按照字符流的方式有序访问.区别在于是否可以随机访问数据--也就是能否在访问设备时随意地从一个位置跳转到另一个位置.我们可以感觉到块设备的控制要比字符设备复杂多,实际上内核在块设备上下了大工夫--块I/O层. 基础概念 块设备中最小的可寻址单元是扇区. 文件系统的最小寻址单元是块. 所谓的缓冲区是块在内存中的表示. 对于一个缓冲区(块),内核需要知道它的控制信息,这时需要一个结构进行描述--缓冲区头. I/O调度机制 首先

Linux内核设计基础(四)之虚拟文件系统

先来看一下写文件函数write的执行过程: ret = write(fd, buf, len); write适用于各种文件系统,它首先执行sys_write(),而正是这个sys_write()进行实际文件系统类型的判别并执行该类型文件系统下的写操作.我们可以看出在多种多样的文件系统上抽象出了一个通用接口性质的虚拟文件系统. 我们这里非常关心Linux 2.6是如何去实现VFS的.先来看一下VFS中的四个主要的对象类型: 超级块对象,它代表一个具体的已安装文件系统. 索引节点对象,它代表一个具体

Linux内核设计基础(七)之系统调用

我理解的系统调用就是内核提供的一组用户进程与内核进行交互的接口.除异常和陷入外,系统调用是内核唯一的合法入口.像/proc也是通过系统调用进行访问的. 系统调用的意义: 让用户进程受限地访问硬件设备 为用户空间提供一种硬件的抽象借口 提供了创建新进程并与已有进程进行通信的机制 提供了申请操作系统其他资源的能力 保证系统稳定可靠,避免应用程序恣意妄为 系统调用的基本原理: 系统调用通常的入口是C库中定义的函数,也可以是自定义的函数(通过syscall进行调用).每个系统调用被赋予一个系统调用号,通

Linux内核设计基础(一)之中断处理

假设让内核定期对设备进行轮询.以便处理设备,那会做非常多无用功,假设能让设备在须要内核时主动通知内核,会是一个聪明的方式,这便是中断. 在响应一个特定中断时,内核会运行一个函数--中断处理程序. 中断处理程序与其它内核函数的差别在于,中断处理程序是被内核调用来响应中断的,而它们运行于我们称之为中断上下文的特殊上下文中. 我们期望让中断处理程序运行得快.并想让它完毕的工作量多,这两个目标相互制约,怎样解决--上下半部机制. 我们把中断处理切为两半.我们用网卡来解释一下这两半. 当网卡接受到数据包时

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 【转】

原文地址:Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938390.html 在构架相关的汇编代码运行完之后,程序跳入了构架无关的内核C语言代码:init/main.c中的start_kernel函数,在这个函数中Linux内核开始真正进入初始化阶段, 下面我就顺这代码逐个函数的解释,但是这里并不会过于深入