Linux 内核list_head 学习
http://www.cnblogs.com/zhuyp1015/archive/2012/06/02/2532240.html
在Linux内核中,提供了一个用来创建双向循环链表的结构 list_head。虽然linux内核是用C语言写的,但是list_head的引入,使得内核数据结构也可以拥有面向对象的特性,通过使用操作list_head 的通用接口很容易实现代码的重用,有点类似于C++的继承机制(希望有机会写篇文章研究一下C语言的面向对象机制)。下面就是kernel中的list_head结构定义:
struct list_head {
struct list_head *next, *prev;
};
#define LIST_HEAD_INIT(name) { &(name), &(name) }
需要注意的一点是,头结点head是不使用的,这点需要注意。
使用list_head组织的链表的结构如下图所示:

list_head这个结构看起来怪怪的,它竟没有数据域!所以看到这个结构的人第一反应就是我们怎么访问数据?
其实list_head不是拿来单独用的,它一般被嵌到其它结构中,如:
struct file_node{
char c;
struct list_head node;
};
此时list_head就作为它的父结构中的一个成员了,当我们知道list_head的地址(指针)时,我们可以通过list.c提供的宏 list_entry 来获得它的父结构的地址。下面我们来看看list_entry的实现:
#define list_entry(ptr,type,member)\
container_of(ptr,type,member)
#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 offsetof(TYPE,MEMBER) ( (size_t)& ((TYPE *)0)-> MEMBER )
地址的地址我们还是可以访问的,这里用到一个取址运算符
地址强制转换为TYPE类型,((TYPE *)0)-> MEMBER 也就是从0址址找到TYPE 的成员MEMBER 。
我们结合上面的结构来看
struct file_node{
char c;
struct list_head node;
};
将实参代入 offset( struct file_node, node );最终将变成这样:
( (size_t) & ((struct file_node*)0)-> node );这样看的还是不很清楚,我们再变变:
struct file_node *p = NULL;
& p->node;
地址,从0地址开始算成员node的地址,也就是成员 node 在结构体 struct file_node中的偏移量。offset宏就是算MEMBER在TYPE中的偏移量的。
我们再看第二个宏
#define container_of(ptr,type,member) ( {\
const typeof( ((type*)0)->member ) *__mptr=(ptr);\
(type*)( (char*)__mptr - offsetof(type,member) );} )
这个宏是由两个语句组成,最后container_of返回的结果就是第二个表达式的值。这里__mptr为中间变量,这就是list_head指针类型,它被初始化为ptr的值,而ptr就是当前所求的结构体中list_head节点的地址。为什么要用中间变量,这是考虑到安全性因素,如果传进来一个ptr++,所有ptr++放在一个表达式中会有副作用,像 (p++)+(p++)之类。
(char*)__mptr 之所以要强制类型转化为char是因为地址是以字节为单位的,而char的长度就是一个字节。
container_of的值是两个地址相减,
刚说了__mptr是结构体中list_head节点的地址,offset宏求的是list_head节点MEMBER在结构体TYPE中的偏移量,那么__mptr减去它所在结构体中的偏移量,就是结构体的地址。
所以list_entry(ptr,type,member)宏的功能就是,由结构体成员地址求结构体地址。其中ptr 是所求结构体中list_head成员指针,type是所求结构体类型,member是结构体list_head成员名。通过下图来总结一下:

继续列举一些双链表的常用操作:
双向链表的遍历——list_for_each
//注:这里prefetch 是gcc的一个优化选项,也可以不要
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)
生成双向链表的头结点——LIST_HEAD()
LIST_HEAD() -- 生成一个名为name的双向链表头节点
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
双向链表的插入操作 -- list_add()
将new所代表的结构体插入head所管理的双向链表的头节点head之后: (即插入表头)
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
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;
}
从list中删除结点——list_del()
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
判断链表是否为空(如果双向链表head为空则返回真,否则为假)——list_empty()
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
make it simple, make it happen
Linux 内核list_head 学习的更多相关文章
- [转载]Linux内核list_head学习(二)
前一篇文章讨论了list_head 结构的基本结构和实现原理,本文主要介绍一下实例代码. 自己如果想在应用程序中使用list_head 的相应操作(当然应该没人使用了,C++ STL提供了list 用 ...
- [转载]Linux 内核list_head 学习(一)
在Linux内核中,提供了一个用来创建双向循环链表的结构 list_head.虽然linux内核是用C语言写的,但是list_head的引入,使得内核数据结构也可以拥有面向对象的特性,通过使用操作li ...
- Linux内核驱动学习(八)GPIO驱动模拟输出PWM
文章目录 前言 原理图 IO模拟输出PWM 设备树 驱动端 调试信息 实验结果 附录 前言 上一篇的学习中介绍了如何在用户空间直接操作GPIO,并写了一个脚本可以产生PWM.本篇的学习会将写一个驱动操 ...
- linux内核数据结构学习总结
目录 . 进程相关数据结构 ) struct task_struct ) struct cred ) struct pid_link ) struct pid ) struct signal_stru ...
- Linux内核驱动学习(三)字符型设备驱动之初体验
Linux字符型设备驱动之初体验 文章目录 Linux字符型设备驱动之初体验 前言 框架 字符型设备 程序实现 cdev kobj owner file_operations dev_t 设备注册过程 ...
- Linux 内核协议栈 学习资料
终极资料 1.<Understanding Linux Network Internals> 2.<TCP/IP Architecture, Design and Implement ...
- linux内核驱动学习指南
1. 参考链接 小白的博客 ONE_Tech 你为什么看不懂Linux内核驱动源码? 求教怎么学习linux内核驱动
- Linux内核驱动学习(六)GPIO之概览
文章目录 前言 功能 如何使用 设备树 API 总结 前言 GPIO(General Purpose Input/Output)通用输入/输出接口,是十分灵活软件可编程的接口,功能强大,十分常用,SO ...
- Linux内核中断学习
1.内核中断概述 (1)在OS环境下编写中断处理函数与之前在裸机中编写中断处理函数的方式是不一样的,在Linux内核中提供了一套用来管理硬件中断资源的软件体系架构. (2)在操作系统中,中断号与gpi ...
随机推荐
- java成神之——MySQL Connector/J 的基本使用
使用示例 DBCP连接池 结语 使用示例 public class demo { static Connection con = null; static Statement st = null; s ...
- scikit-learn和tensorflow的区别
1.功能不同 Scikit-learn(sklearn)的定位是通用机器学习库,而TensorFlow(tf)的定位主要是深度学习库.一个显而易见的不同:tf并未提供sklearn那种强大的特征工程, ...
- C# 泛型类型参数的约束
在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制.如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误.这些限制称为约束.约束是使用 where 上 ...
- leetcode539
public class Solution { public int FindMinDifference(IList<string> timePoints) { * ]; foreach ...
- Cable master(二分-求可行解)
Inhabitants of the Wonderland have decided to hold a regional programming contest. The Judging Commi ...
- 在linux中read、write函数
read函数从打开的设备或文件中读取数据. #include<</span>unistd.h> ssize_t read(int fd, void *buf, size_t ...
- Java多线程-新特征-阻塞栈LinkedBlockingDeque
对于阻塞栈,与阻塞队列相似.不同点在于栈是“后入先出”的结构,每次操作的是栈顶,而队列是“先进先出”的结构,每次操作的是队列头. 这里要特别说明一点的是,阻塞栈是Java6的新特征.. Java为阻塞 ...
- Tornado之自定义session
面向对象基础 面向对象中通过索引的方式访问对象,需要内部实现 __getitem__ .__delitem__.__setitem__方法 #!/usr/bin/env python # -*- ...
- 简单的互斥同步方式——synchronized关键字详解
目录 1. 关于synchronized关键字 2. synchronized的原理和实现细节 2.1 synchronized可以用在那些地方 2.2 synchronized是如何实现线程互斥访问 ...
- 简单tarjan》一道裸题(BZOJ1051)(easy)
这是一道水题,实际考察的是你会不会写强连通分量...(在BZOJ上又水了一道题) Description 每一头牛的愿望就是变成一头最受欢迎的牛.现在有N头牛,给你M对整数(A,B),表示牛A认为牛B ...