内核链表
来源:互联网 发布:东方通达信软件下载 编辑:程序博客网 时间:2024/05/29 12:51
在研究Linux内核前和驱动开发之前,需要先了解一下内核中常用的一些工具,接下来的主题就是内核链表。
可能我们在数据结构里都听说过链表,或者学过链表。我们还知道链表这东西还挺麻烦,稍有不慎就导致整个程序崩溃了。
Linux中大量用到了内核链表,我们就来看看这个链表有什么特殊之处,可以被广泛使用。
首先,回顾一下我们在严蔚敏老太太书中呈现的链表,我们就直接看循环双链表吧。
顾名思义:需要1.首尾相连(循环)
2.指向下一个节点和上一个节点
3.有一个头指针,方便操作
4. 另外存放数据
回顾完这个经典的例子,我们来看看内核中使用的链表。
这里看起来似乎没有什么不同,可能细心的同学发现了,其实这个图画的也不够清晰。
这里我们直接找到内核链表中的结点的定义,我们就能发现有什么不同了。
include/linux/list.h
struct list_head {struct list_head *next, *prev;};
我们发现数据域并不存放到结点里面,这就是两个图片的区别。可能你会问这有什么好处呢?遍历的时候速度快一些吗?
当然,更重要的是这个链表和之前的比(通用性)是不能比的。
情景介绍,当我再编程中碰到了使用两个链表,一个存放double类型的链表,一个存放char型的链表,我发现我不得复制黏贴代码一次,然后做一点修改才能用上。
后来我想到
1. 用c++的模板,然后修改一点点代码。
2. 我把数据域的内容存为 void*类型。
3. 既然用到了C++ STL提供给我们的就可以了,方便快捷。
说了那么多标准C的做法只有2可行,由此也说明了模板在实际应用的重要性。 说了这么多,我们还是看看内核链表怎么解决这个问题的。
好!没有数据域那么别的数据结构就是这样用的:
struct ex
{
struct list;
int xx;
int xx2;
}
总而言之,我们任何的结构都可以嵌入一个链表节点把各个数据联系起来,这种链表结构避免了为每个数据项类型定义自己的链表的麻烦。
接着,我们继续看链表的操作:
1.初始化一个链表:
我们来看看LIST_HEAD()这个宏:
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)
比如:LIST_HEAD(mylist_head) --->struct list_head mylist_head = {.next =&mylist_head; ,prev=&mylist_head}
2.插入
对链表的插入操作有两种:在表头插入和在表尾插入。
static inline void list_add(struct list_head *new, struct list_head *head);
static inline void list_add_tail(struct list_head *new, struct list_head *head);
因为是循环双链表实际上头插和尾插差不多都是分别调用:
__list_add(new, head, head->next);
__list_add(new, head->prev, head);
3.删除
static inline void list_del(struct list_head *entry);
4. 搬移
Linux提供了将原本属于一个链表的节点移动到另一个链表的操作,并根据插入到新链表的位置分为两类:
static inline void list_move(struct list_head *list, struct list_head *head);
static inline void list_move_tail(struct list_head *list, struct list_head *head);
5.合并
除了针对节点的插入、删除操作,Linux链表还提供了整个链表的插入功能:
static inline void list_splice(struct list_head *list, struct list_head *head);
6.遍历
我们知道,Linux链表中仅保存了数据项结构中list_head成员变量的地址,那么我们如何通过这个list_head成员访问到作为它的所有者的节点数据呢?
我们可以通过 一个指向链节点的指针,整个存放链表节点的类型,和存放链表节点的数据结构中的参数名获取到整个结构的地址。
#define list_entry(ptr, type, member) container_of(ptr, type, member)
container_of宏定义在[include/linux/kernel.h]中:
这个宏当然不仅仅可以算出链表节点对应数据结构的地址,他当然用法在于 知道 成员的地址 便能得之整个结构的地址。
这里使用的是一个利用编译器技术的小技巧,即先求得结构成员在与结构中的偏移量,然后根据成员变量的地址反过来得出属主结构变量的地址。
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );
})
这里最有趣的地方是((type *)0)->member,它将0地址强制"转换"为type结构的指针,再访问到type结构中的member成员。
在container_of宏中,它用来给typeof()提供参数(typeof()是gcc的扩展,和sizeof()类似),以获得member成员的数据类型;
在offsetof()中,这个member成员的地址实际上就是type数据结构中member成员相对于结构变量的偏移量。
offsetof宏定义在[include/linux/stddef.h]中:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
这里才是遍历
函数首先定义一个(struct list_head *)指针变量i,然后调用list_for_each(i,&nf_sockopts)进行遍历。
在[include/linux/list.h]中,list_for_each()宏是这么定义的:
#define list_for_each(pos, head)
for (pos = (head)->next, prefetch(pos->next); pos != (head); pos = pos->next, prefetch(pos->next))
它实际上是一个for循环,利用传入的pos作为循环变量,从表头head开始,逐项向后(next方向)移动pos,直至又回到head。
大多数情况下,遍历链表的时候都需要获得链表节点数据项,也就是说list_for_each()和list_entry()总是同时使用。
对此Linux给出了一个list_for_each_entry()宏,与list_for_each()不同,这里的pos是数据项结构指针类型,而不是(struct list_head *)。
#define list_for_each_entry(pos, head, member)……
某些应用需要反向遍历链表,Linux提供了
list_for_each_prev()和list_for_each_entry_reverse()来完成这一操作,
使用方法和上面介绍的list_for_each()、list_for_each_entry()完全相同。
演示一下遍历的写法:
还是那之前的struct ex结构
struct list_head *i;
……
list_for_each(i, &e.list)
{
struct ex e = list_entry(i, struct list_head, list);
printf("xx %d xx2: %d/n",e->xx,e->xx2);
}
……
- Linux内核之—内核链表
- 内核部件之内核链表
- Linux嵌入式 -- 内核 - 内核链表
- Linux 内核开发 - 内核链表
- Zephyr OS 内核篇: 内核链表
- linux内核学习:内核链表
- 【内核数据结构】 内核链表分析
- linux内核链表
- linux内核链表
- Linux内核链表
- linux内核链表
- linux内核链表
- 内核链表
- Linux内核链表
- linux内核链表
- 内核链表
- Linux内核链表
- linux内核链表
- hibernate.hbm2ddl.auto设置成update的问题
- 为什么要花大精力招聘独立思考者?
- java面向对象学习笔记(2)
- PHP源码安装
- ie6 bug- position:relative
- 内核链表
- 线程小结
- Objective-C中nil与release的区别与用法
- ZigBee组网学习笔记(六)--网络通讯(组播)
- HTML颜色代码表
- 我的c语言
- 赠送 HttpClient 和HttpURLConnection 的轻型网络框架 ---》使用介绍
- Android ImageView图片自适应
- eclipse 插件地址