Linux内核list/hlist解读
转自:http://blog.chinaunix.net/uid-20671208-id-3763131.html
目录
3.9. list_for_each_entry_safe 12
3.10. list_for_each_entry_reverse 13
3.11. list_for_each_entry_continue 13
3.12. list_for_each_safe_rcu 14
1. 前言
Linux内核实现了一批优雅而功能强大的双向循环列表操作宏,它们位于/usr/include/linux/list.h(请注意直接#include会报编译错误),这些宏可以直接扣出来,在需要时使用。
2. 通用宏
2.1. typeof
请注意typeof并不是一个宏,而是GCC的一个内建操作符。
2.1.1. 定义
typeof(variable)
2.1.2. 用途
得到变量variable的类型,这个类型可以用来定义同类型的其它变量。
2.1.3. 示例
int m = 1;
typeof(m) n = m; // 等同于int n = m;
2.2. offset_of
2.2.1. 定义
// type:结构体类型
// member:结构体成员
#define offsetof(type, member) \
((size_t) &((type *)0)->member)
2.2.2. 作用
计算出一个结构体type中,指定成员member在该结构体中的位置。
2.2.3. 原理
(type *)0是一个类型为type的指针,其指针地址为0x0。&((type *)0)->member)是得到成员member的地址,由于结构体本身的地址为0x0,所以成员member的地址亦为member在该type中的偏移位置。
2.2.4. 示例
假设有如下一个结构体:
#pragma pack(4)
struct X
{
int32_t a;
int32_t b;
int32_t c;
};
#pragma pack()
那么offsetof(Struct X, b)的值等于4。
2.3. container_of
2.3.1. 定义
// ptr:结构体成员member的地址
// type:结构体类型
// member:结构体成员member
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
2.3.2. 作用
通过结构体一个成员的地址,得到该结构体的地址。
2.3.3. 示例
定义结构体变量:
struct X x;
那么container_of(&x.b, struct X, b)的值将和&x相等。
2.4. prefetch
2.4.1. 定义
// x:需要预读的变量
#define prefetch(x) __builtin_prefetch(x)
2.4.2. 作用
这里使用到的__builtin_prefetch是GCC内建函数,它的作用是告诉cpu括号中的x可能马上就要用到,以便cpu预取一下,这样可以提高效率。
3. list
3.1. list结构
3.1.1. 定义
struct list_head
{
struct list_head *next; // 指向下一个结点
struct list_head *prev; // 指向前一个结点
};
这个定义虽然简单,但却是核心。
3.1.2. 作用
用来实现通用的双向链表,只包括前后指针,并不包含数据成员。
3.1.3. 解读
struct list_head有点类似于C++基类:
class list_head
{
public:
list_head()
: next(NULL)
, prev(NULL)
{
}
public: // 实际使用时,应当改为private,采用成员函数操作方式
list_head* next;
list_head* prev;
};
3.1.4. 示例
在C++语言中,基类是子类实例的一个子对象。在设计模式中,有template模式与strategy两个可以相互转化的模式,template模式采用的是继承,而strategy模式采用的是子对象方式。
由于在C语言中,没有继承的概念,所以只能采用子对象的方式。因此需要这样使用:
struct MyData
{
// 它在结构体中出现的顺序没有要求,
// 因为可以通过下面将要介绍的container_of宏来取得它所归属结构体的地址
struct list_head list;
char* data;
};
如果是在C++中,则:
class MyData: pubic list_head
{
private:
char* _data;
};
在C++中,如果:
list_head* data = new MyData;
则可以这样:
MyData* mydata = dynamic_cast<mydata*>(data);
即可由基类子对象的地址,来得到子类对象的地址。而在C语言中,则需要借助container_of宏:container_of(data, MyData, list);
3.2. 遍历方向
双向循环列表头尾相连,有2个遍历方向:
1) 顺时针
2) 逆时针
3.3. list_entry
3.3.1. 定义
// ptr:结构体成员member的地址
// type:结构体类型
// member:结构体成员member
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
3.3.2. 作用
从list_entry的定义可以看出,它等同于container_of,即通过结构体type一个成员member的地址ptr,得到该结构本的地址。
3.4. list_for_each
3.4.1. 定义
// pos:指向当前结点的指针
// head:指向双向链表的头的指针
#define list_for_each(pos, head) \
for (pos = (head)->next; \
prefetch(pos->next), pos != (head); \
pos = pos->next)
这里的prefetch(pos->next)不是必须的,只是为提升效率。
3.4.2. 作用
以顺时针方向遍历双向循环链表,由于是双向循环链表,所以循环终止条件是“pos != (head)”。在遍历过程中,不能删除pos(必须保证pos->next有效),否则会造成SIGSEGV错误。
3.4.3. 示例
struct list_head cur;
struct list_head list;
list_for_each(cur, &list)
{
// 使用cur
}
3.5. __list_for_each
3.5.1. 定义
// pos:指向当前结点的指针
// head:指向双向链表的头的指针
#define __list_for_each(pos, head) \
for (pos = (head)->next; \
pos != (head); \
pos = pos->next)
3.5.2. 作用
功能和list_for_each相同,即以顺时针方向遍历双向循环链表,只不过省去了prefetch(pos->next)。在遍历过程中,不能删除pos(必须保证pos->next有效),否则会造成SIGSEGV错误。
3.6. list_for_each_prev
3.6.1. 定义
// pos:指向当前结点的指针
// head:指向双向链表的头的指针
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; \
prefetch(pos->prev), pos != (head); \
pos = pos->prev)
3.6.2. 作用
以逆时针方向遍历双向循环链表。在遍历过程中,不能删除pos(必须保证pos->prev有效),否则会造成SIGSEGV错误。
3.7. list_for_each_safe
3.7.1. 定义
// pos:指向当前结点的指针
// head:指向双向链表的头的指针
// n:临时用来保存指向pos的下一个结点的指针
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; \
pos != (head); \
pos = n, n = pos->next)
3.7.2. 作用
以顺时针方向遍历双向循环链表。在遍历过程中,允许删除pos,因为每次都保存了pos->next。
3.7.3. 区别
list_for_each_safe(pos, n, head) |
list_for_each(pos, head) |
遍历中,可删除pos |
遍历中,不可删除pos |
3.7.4. 示例
struct list_head cur;
struct list_head tmp; // 临时用来保存cur的next
struct list_head list;
list_for_each_safe(cur, tmp, &list)
{
// 使用pos
list_del(pos); // 将pos从链表中剔除
delete pos; // 删除pos
}
3.8. list_for_each_entry
3.8.1. 定义
为方便讲解,假设有:
struct MyNode
{
struct list_head list;
};
实际中,应当将MyNode替代成需要的类型的,但不管叫什么,总是聚合了struct list_head。
// pos:指向当前结点(在这里,类型为MyNode)的指针
// head:指向双向链表list_head的头的指针
// member:list_head类型在MyNode中的成员(在这里为list)
#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))
3.8.2. 作用
以顺时针方向遍历双向循环链表。它和list_for_each一样是做链表遍历,但pos的类型不一样。在list_for_each中,pos类型是“struct list_head*”,而在list_for_each_entry是typeof(*pos)类型。
3.8.3. 区别
list_for_each_entry(pos, head, member) |
list_for_each(pos, head) |
遍历链表,pos和head是typeof(*pos)类型 |
遍历链表,pos和head是struct list_head类型 |
3.8.4. 完整示例
// 这个示例,是可以编译成功,并可运行查看效果
// 假设文件名为x.cpp,则编译方法为:g++ -g -o x x.cpp
#include
#include
struct list_head {
struct list_head *next, *prev;
};
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
static inline void __list_add(struct list_head *inserted,
struct list_head *prev,
struct list_head *next)
{
next->prev = inserted;
inserted->next = next;
inserted->prev = prev;
prev->next = inserted;
}
static inline void list_add_tail(struct list_head *inserted, struct list_head *head)
{
__list_add(inserted, head->prev, head);
}
#define prefetch(x) __builtin_prefetch(x)
#define offsetof(type, member) \
((size_t) &((type *)0)->member)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)
#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))
// 以上除#include外的代码,是从/usr/include/linux/list.h直接抄过来的,
// 但请注意直接#include ,将无法编译通过
// 定义一个结构体
struct MyData
{
public:
struct list_head list; // 功能上相当于从list_head继承
char* data;
};
int main()
{
MyData* head = new MyData; // 必须有个空闲头结点
MyData* data1 = new MyData; // 第一个结点
data1->data = reinterpret_cast<char*>(0x01);
MyData* data2 = new MyData; // 第二个结点
data2->data = reinterpret_cast<char*>(0x02);
INIT_LIST_HEAD(&head->list);
list_add_tail(&data1->list, &head->list);
list_add_tail(&data2->list, &head->list);
// 请注意cur1和cur2的数据类型
MyData* cur1 = NULL;
list_head* cur2 = NULL;
// 请注意下面两个循环的区别,它们是互通的
list_for_each_entry(cur1, &head->list, list)
{
printf("%p\n", cur1->data);
}
list_for_each(cur2, &head->list)
{
MyData* dd = container_of(cur2, MyData, list);
printf("%p\n", dd->data);
}
return 0;
}
上段代码运行后,将输出以下四行信息:
0x1
0x2
0x1
0x2
3.9. list_for_each_entry_safe
3.9.1. 定义
// pos:指向当前结点(在这里,类型为MyNode)的指针
// head:指向双向链表list_head的头的指针
// member:list_head类型在MyNode中的成员(在这里为list)
// n:用来临时存储pos的下一个结点(类型和pos相同)
#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))
3.9.2. 作用
list_for_each_entry的可删除结点版本。
3.10. list_for_each_entry_reverse
3.10.1. 定义
#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))
3.10.2. 作用
作用和list_for_each_entry相同,只不过它是逆时针方向遍历。
3.11. list_for_each_entry_continue
3.11.1. 定义
#define list_for_each_entry_continue(pos, head, member) \
for (pos = list_entry(pos->member.next, typeof(*pos), member); \
prefetch(pos->member.next), &pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
3.11.2. 作用
以顺时针方向,从pos点开始遍历链表。
3.11.3. 区别
list_for_each_entry_continue(pos, head, member) |
list_for_each_entry(pos, head, member) |
从pos点开始遍历链表 |
从头开始遍历链表 |
传入的pos不能为NULL,必须是已经指向链表某个结点的有效指针 |
对传入的pos无要求,可以为NULL |
3.12. list_for_each_safe_rcu
这个牵涉到RCU(Read-Copy-Update),在这里不详细讲解了,如有兴趣,请参考《玩转多线程编程.ppt》一文。
4. hlist(hash list)
4.1. hlist(hash list)结构
4.1.1. 简述
hlist也是 一种双向链表,但不同于list_head,它的头部只有一个指针,常被用作哈希表的bucket数组,这样就可减少哈希bucket一半的内存消耗。
4.1.2. 定义
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;
};
Linux内核list/hlist解读的更多相关文章
- linux内核奇遇记之md源代码解读之四
linux内核奇遇记之md源代码解读之四 转载请注明出处:http://blog.csdn.net/liumangxiong 运行阵列意味着阵列经历从无到有,建立了作为一个raid应有的属性(如同步重 ...
- Linux 内核 hlist 详解
在Linux内核中,hlist(哈希链表)使用非常广泛.本文将对其数据结构和核心函数进行分析. 和hlist相关的数据结构有两个:hlist_head 和 hlist_node //hash桶的头结点 ...
- LINUX内核分析期末总结
韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.课程总结 1 ...
- 分析Linux内核中进程的调度(时间片轮转)-《Linux内核分析》Week2作业
1.环境的搭建: 这个可以参考孟宁老师的github:mykernel,这里不再进行赘述.主要是就是下载Linux3.9的代码,然后安装孟宁老师编写的patch,最后进行编译. 2.代码的解读 课上的 ...
- linux内核数据结构学习总结
目录 . 进程相关数据结构 ) struct task_struct ) struct cred ) struct pid_link ) struct pid ) struct signal_stru ...
- 深入分析 Linux 内核链表--转
引用地址:http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/index.html 一. 链表数据结构简介 链表是一种常用的组织有序数据 ...
- linux内核源码注解
轻松学习Linux操作系统内核源码的方法 针对好多Linux 爱好者对内核很有兴趣却无从下口,本文旨在介绍一种解读linux内核源码的入门方法,而不是解说linux复杂的内核机制:一.核心源程序的文件 ...
- 深入分析 Linux 内核链表
转载:http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/ 一. 链表数据结构简介 链表是一种常用的组织有序数据的数据结构,它通过指 ...
- 使用gdb跟踪Linux内核启动过程(从start_kernel到init进程启动)
本次实验过程如下: 1. 运行MenuOS系统 在实验楼的虚拟机环境里,打击打开shell,使用下面的命令 cd LinuxKernel/ qemu -kernel linux-/arch/x86/b ...
随机推荐
- MVC5使用EF6 Code First--创建EF数据模型(一)
此Web应用程序演示如何使用Entity Framework 6和Visual Studio 2015创建ASP.NET MVC 5应用程序.本教程使用“Code First ”即代码先行.有关如何在 ...
- python随笔(一)
(1) 两个乘号对不同的数据类型,其含义是不同的. 对整数来说,连乘表示幂次,比如2**2**3 = 256 对于字符串来说一个乘号的意义是,‘abc'*3 = 'abcabcabc' (2) 一个数 ...
- MySQL多线程备份工具mydumper
mydumper是一个针对MySQL和Drizzle的高性能多线程的备份和恢复工具.此工具的开发人员分别来自MySQL.Fackbook.SkySQL公司,目前已经有一些大型产品业务测试并使用了该工具 ...
- ModelAndView学习笔记
api: 构造函数摘要 ModelAndView() bean样式用法的默认构造函数:填充bean属性,而不是传递构造函数参数. ModelAndView(Object view) ...
- 洛谷P3387 【模板】缩点 题解
背景 今天\(loj\)挂了,于是就有了闲情雅致来刷\(luogu\) 题面 洛谷P3387 [模板]缩点传送门 题意 给定一个\(n\)个点\(m\)条边有向图,每个点有一个权值,求一条路径,使路径 ...
- 如何将文本编辑器嵌入框架--以Umeditor&CodeIgniter框架为例
转:http://blog.csdn.net/u013332865/article/details/52066211 最近接到一个给某私立贵族(小,初,高 12年只是学费近200W)学校做一个网站,时 ...
- Java编程的逻辑 (57) - 二进制文件和字节流
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- hdu 5441 (2015长春网络赛E题 带权并查集 )
n个结点,m条边,权值是 从u到v所花的时间 ,每次询问会给一个时间,权值比 询问值小的边就可以走 从u到v 和从v到u算不同的两次 输出有多少种不同的走法(大概是这个意思吧)先把边的权值 从小到大排 ...
- 原生js返回顶部
let backToTop = function() { let scrollToptimer = setInterval(function() { let top = document.body.s ...
- java数据结构之树
树定义和基本术语定义树(Tree)是n(n≥0)个结点的有限集T,并且当n>0时满足下列条件: (1)有且仅有一个特定的称为根(Root)的结点: (2)当n>1时,其余结 ...