Linux 内核数据结构:双向链表
Linux 内核提供一套双向链表的实现,你可以在 include/linux/list.h 中找到。我们以双向链表着手开始介绍 Linux 内核中的数据结构 ,因为这个是在 Linux 内核中使用最为广泛的数据结构,具体你可以 查看 这里。
首先让我们看一下主要的结构体:
struct list_head {
struct list_head *next, *prev;
};
你可以看到其与常见的结构体实现有显著不同,比如 glib 中所使用到的双向链表实现。
struct GList {
gpointer data;
GList *next;
GList *prev;
};
通常来说,链表结构体要包括一个指向数据的指针,不过1216.www.qixoo.qixoo.com/ Linux 内核的链表却不包含此实现。那么首要的疑问:链表是用什么方式存储数据的?。Linux 内核所实现的是一种被称为侵入式的链表(Intrusive list),这种链表并不在链表结构中包含数据,而仅提供用于维护前向与后向访问结构的指针。这种实现方式使得链表数据结构非常通用,因为它并不需要关注链表所维护的具体数据类型。
比如:
struct nmi_desc {
spinlock_t lock;
struct list_head head;
};
接下来让我们看一些内核使用 list_head 的具体例子。正如在前文所述的,Linux 内核中诸多模块都使用了 list_head。这里我们以内核杂项字符设备驱动(miscellaneous character drivers)部分实现为例。驱动的 API 在 drivers/char/misc.c 中,其实现了简单硬件外设以及虚拟设备的驱动,这个驱动共享主设备号(Major number):
#define MISC_MAJOR 10
每个设备有自己的次设备号,具体可以看这个列子:
现在我们看看设备驱动是如何使用链表维护设备列表的,首先,我们看一下 miscdevice 的 struct 定义:
struct miscdevice
{
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
可以看到 miscdevice 的第四个成员 list ,这个就是用于维护已注册设备链表的结构。在源代码文的首部,我们可以看到以下定义:
static LIST_HEAD(misc_list);
这个定义宏展开,可以看到是用于定义 list_head 类型变量:
#define LIST_HEAD(name)
struct list_head name = LIST_HEAD_INIT(name)
LIST_HEAD_INIT 这个宏用于对定义的变量进行双向指针的初始化:
#define LIST_HEAD_INIT(name) { &(name), &(name) }
现在我看可以看一下函数 misc_register 是如何进行设备注册的。首先是用 INIT_LIST_HEAD 对 miscdevice->list 成员变量进行初始化:
INIT_LIST_HEAD(&misc->list);
这个操作与 LIST_HEAD_INIT 宏一致:
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
接下来,在通过函数 device_create 进行设备创建,同时将设备添加到 Misc 设备列表中:
list_add(&misc->list, &misc_list);
内核的 list.h 文件提供向链表添加节点的 API,这里是添加操作的实现:
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
函数实现很简单,就是入参转换为三个参数后调用内部 __list_add :
new – new entry;
head – list head after which will be inserted new item;
head->next – next item after list head.
_list_add 函数的实现更加简单:
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;
}
这里设置了新添加结点的 prev 与 next 指针,通过这些操作,就将先前使用 LIST_HEAD_INIT 所定义的 misc 链表的双向指针与 miscdevice->list 结构关联起来。
这里还有一个问题,就是如何获取链表中的数据,list_head 提供了一个特殊的宏用于获取数据指针。
#define list_entry(ptr, type, member)
container_of(ptr, type, member)
这里有三个参数
ptr:list_head 结构指针
type:数据对应的 struct 类型
member:数据中 list_head 成员对应的成员变量名
举例如下:
const struct miscdevice *p = list_entry(v, struct miscdevice, list)
接下来我们就够访问 miscdevice 的各个成员,如 p->minor、p->name 等等,我们看一下 list_entry 的实现:
#define list_entry(ptr, type, member)
container_of(ptr, type, member)
其实现非常简单,就是使用入参调用 container_of 宏,宏的实现如下:
#define container_of(ptr, type, member) ({
const typeof( ((type *)0)->member ) *__mptr = (ptr);
(type *)( (char *)__mptr - offsetof(type,member) );})
注意,宏使用了大括号表达式,对于大括号表达式,编译器会展开所有表达式,同时使用最后一个表达式的结果进行返回。
举个例子:
#include <stdio.h>
int main() {
int i = 0;
printf("i = %dn", ({++i; ++i;}));
return 0;
}
输出结果为 2 。
另一个关键是 typeof 关键字,这个非常简单,这个正如它的名字一样,这个关键字返回的结果是变量的类型。当我第一次看到这个宏时,最让我觉得奇怪的是表达式 ((type*)0) 中的 0 值,实际上,使用 0 值作为地址这个是成员变量取得 struct 内相对偏移地址的巧妙实现,我们再来看个例子:
#include <stdio.h>
struct s {
int field1;
char field2;
char field3;
};
int main() {
printf("%pn", &((struct s*)0)->field3);
return 0;
}
输出结果为 0x5 。
还有一个专门用于获取结构体中某个成员变量偏移的宏,其实现与前面提到的宏非常类似:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
这里对 container_of 宏做个综述,container_of 宏通过 struct 中的 list_head 成员返回 struct 对应数据的内存地址。在宏的第一行定义了指向 list_head 成员的指针 __mptr ,并将 ptr 地址赋给 __mptr 。从技术实现的角度来看,实际并不需要这一行定义,但这个对于类型检查而言非常有意义。这一行代码确保结构体( type )中存在 member 对应的成员。第二行使用 offsetoff 宏计算出包含 member 的结构体所对应的内存地址,就是这么简单。
当然 list_add 与 list_entry 并非是 <linux/list.h> 中的全部函数,对于双向链表 list_head ,内核还提供了以下的接口:
list_add
list_add_tail
list_del
list_replace
list_move
list_is_last
list_empty
list_cut_position
list_splice
未了,需要说的是,内核代码中并不仅仅只有上述这些接口。
Linux 内核数据结构:双向链表的更多相关文章
- Linux内核中双向链表的经典实现
概要 前面一章"介绍双向链表并给出了C/C++/Java三种实现",本章继续对双向链表进行探讨,介绍的内容是Linux内核中双向链表的经典实现和用法.其中,也会涉及到Linux内核 ...
- linux内核数据结构之链表
linux内核数据结构之链表 1.前言 最近写代码需用到链表结构,正好公共库有关于链表的.第一眼看时,觉得有点新鲜,和我之前见到的链表结构不一样,只有前驱和后继指针,而没有数据域.后来看代码注释发现该 ...
- Linux 内核数据结构:Linux 双向链表
Linux 内核提供一套双向链表的实现,你可以在 include/linux/list.h 中找到.我们以双向链表着手开始介绍 Linux 内核中的数据结构 ,因为这个是在 Linux 内核中使用最为 ...
- linux内核数据结构学习总结
目录 . 进程相关数据结构 ) struct task_struct ) struct cred ) struct pid_link ) struct pid ) struct signal_stru ...
- linux内核数据结构--进程相关
linux里面,有一个结构体task_struct,也叫“进程描述符”的数据结构,它包含了与进程相关的所有信息,它非常复杂,每一个字段都可能与一个功能相关,所以大部分细节不在我的研究范围之内,在这篇文 ...
- 深度剖析linux内核万能--双向链表,Hash链表模版
我们都知道,链表是数据结构中用得最广泛的一种数据结构,对于数据结构,有顺序存储,数组就是一种.有链式存储,链表算一种.当然还有索引式的,散列式的,各种风格的说法,叫法层出不穷,但是万变不离其中,只要知 ...
- linux内核数据结构之kfifo
1.前言 最近项目中用到一个环形缓冲区(ring buffer),代码是由linux内核的kfifo改过来的.缓冲区在文件系统中经常用到,通过缓冲区缓解cpu读写内存和读写磁盘的速度.例如一个进程A产 ...
- linux内核数据结构之kfifo【转】
1.前言 最近项目中用到一个环形缓冲区(ring buffer),代码是由linux内核的kfifo改过来的.缓冲区在文件系统中经常用到,通过缓冲区缓解cpu读写内存和读写磁盘的速度.例如一个进程A产 ...
- Linux内核数据结构之kfifo详解
本文分析的原代码版本: 2.6.24.4 kfifo的定义文件: kernel/kfifo.c kfifo的头文件: include/linux/kfifo.h kfifo是内核里面的一个First ...
随机推荐
- tomcat 应用部署的几点注意
将应用部署到Tomcat根目录的目的是可以通过"http://[ip]:[port]"直接访问应用,而不是使用"http://[ip]:[port]/[appName]& ...
- asp.net mvc 之旅—— 第三站 路由模板中强大的自定义IRouteConstraint约束
我们在写mvc的时候,经常会配置各种url模板,比如controller,action,id 组合模式,其实呢,我们还可以对这三个参数进行单独的配置,采用的方式自然 就是MapRoute中的const ...
- SQL周、日、月、年数据统计
本文只是记录在项目中用到的统计的SQL语句,记一笔以防忘了 /// <summary> /// 获取统计数据 /// </summary> /// <param name ...
- BOOST.Asio——扫盲
以下内容来自互联网. 鉴于版权之类的东西,我只贴出标题和URL. (无法考证下述资料是否原创.) asio串口编程 ...
- js 输出数组最大值
,,,,]; ]; ; i < run.length; i++) { if (max<run[i]) { max=run[i]; }else{ max=max; } } alert(max ...
- Centos 6.5 安装ELK
安装java 查看java安装路径 修改环境变量 添加如下内容: export JAVA_HOME=/usr/java/jdk1.8.0_91 export PATH=$JAVA_HOME/bin:$ ...
- 编程中Foo, Bar 到底什么意思?
1 前言 在很多国外计算机书本和一些第三份开源软件的Demo中经常用到两个英文单词Foo,Bar.这到底是什么意思呢?从步入屌丝界的IT生活见到这两个单词到现在我还是不知道这两个单词的真正含义,今天有 ...
- UNIX文件的权限之“设置用户ID位”
用stat函数可以获取一个文件的状态信息,原型是这样的: int stat(const char *path, struct stat *buf); 其中结构体stat的结构: struct stat ...
- MultiThread
Stephen Toub From MicroSoft Crop. Stephen Cleary It's All About the SynchronizationContext How would ...
- [WPF系列]-基础系列 Property Trigger, DataTrigger & EventTrigger
So far, we worked with styles by setting a static value for a specific property. However, using trig ...