第6章内核数据结构①链表
2014-12-14 22:09
232 查看
《Linux内核设计与实现》
第6章内核数据结构①链表
看内核代码经常遇到list_for_each_entry,索性就看一下内核中的数据结构,参考了《Linux内核设计与实现》陈老师的书,也参考了一些博客,最后会一一列出。1 链表
链表在Linux内核中经常被使用,随处可见,多为双向循环链表,且和《数据结构》(严版)使用的不太一样。为什么要使用链表,什么时候使用链表?链表多使用在需要遍历所有的数据或需要动态加入和删除数据,才会使用链表存放数据,如果需要随机访问数据,一般不使用链表。链表和静态数组还是不同的?
一般链表实现是在数据结构中添加一个指向数据的next节点指针,才能串联在链表,内核中的双向循环链表不是这样实现的,它是将链表节点塞入数据结构中。
链表节点结构:
struct list_head { structlist_head *next, *prev; };
Next指针指向下一个链表节点,prev指针指向前一个。
以struct inode为例,
struct inode { structlist_head i_dentry; };
可能现在还不能很清楚这个是如何使用的,只有到了使用时才会明白,看对链表的操作。
2 对链表的操作
看了对链表的操作,我觉得应该可以明白如何使用list_head这个结构体。我只看了几个操作:◎ 初始化链表:INIT_LIST_HEAD(structlist_head *list);
static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; }
◎ 还有这样初始化:
#define LIST_HEAD_INIT(name) {&(name), &(name) }
将name的地址直接分别赋值给next和prev,那么它们事实上都指向自己,也形成一个空链表,
#define LIST_HEAD(name) \
struct list_head name =LIST_HEAD_INIT(name)
虽然我们可以通过list_head指针,从链表中任何一个节点起遍历完链表中的所有节点,不过有时确实需要一个特殊指针索引到整个链表,这就是链表头。
LIST_HEAD(name)宏用来定义一个链表头,并使它的两个指针都指向自己。我们可以在程序的变量声明处,直接调用LIST_HEAD(name)宏,来定义并初始化一个名为name的链表。
static LIST_HEAD( list_name );
◎ 向链表中增加一个节点:
list_add(struct list_head *new, struct list_head *head);
之前有个想法是,head和new都是在相同结构体中的成员,后来发现内核中多不在相同结构体,head仅仅是个头。
list_add_tail(struct list_head *new, struct list_head *head);
◎ 从链表中删除一个节点:list_del(struct list_head *entry);
◎ 把节点从一个链表移到另一个链表:
list_move(struct list_head*list, struct list_head *head);
◎ 把两个未连接的链表合并在一起:
list_splice(const struct list_head *list,struct list_head *head);
3 list_for_each_entry
主要是迫不及待想写一下这部分,看代码时总是遇到。3.1 list_for_each_entry
/** *list_for_each_entry - iterate over list of given type * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry(pos, head, member) \ for(pos = list_entry((head)->next, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.next,typeof(*pos), member))
过程:它实际上是一个 for 循环,利用指向结构体的指针pos 作为循环变量,从表头 list_head类型的指针head 开始,逐项向后(next 方向)移动 pos,直至又回head(prefetch() 可以不考虑,用于预取以提高遍历速度)。
作用:遍历一遍从head开始的链表,返回的是指向当前链表中的第一个结构体的地址,赋给pos。
pos是指向结构体的指针,head是list_head类型的指针,指向链表头,member是list_head类型的指针,同时也是*pos结构体的成员。
注意:member是另外一个结构体中的list_head类型的成员,和head不在同一个结构体中,所以才需要从(head)->next开始。
为什么(head)(*pos)都加括号,是因为宏内的变量一般都加括号,否则宏展开时容易出错。
3.2 list_entry
/** * list_entry- get the struct for this entry * @ptr: the &struct list_head pointer. * @type: the type of the struct this is embeddedin. * @member: the name of the list_struct within the struct. */ #define list_entry(ptr, type, member) \ container_of(ptr,type, member)
接着看3.3中的container_of。
ptr是list_head类型的指针,type是结构体类型,member是结构体类型的成员。
3.3 container_of
/** *container_of - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. *@type: the type of the container structthis is embedded in. * @member:the name of the member within the struct. * */ #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member)*__mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type,member)); })
分析:container_of被预定义成一个函数,函数的第一句话,通过((type *)0)->member定义一个指向MEMBER型的指针__mptr,这个指针指向ptr,所以第一句话获取到了我们要求的结构体的成员 member的地址,接下来我们用这个地址减去成员member在结构体中的相对偏移量,就可以获取到所求结构体的地址, (char *)__mptr - offsetof(type,member)就实现了这个过程,最后再把这个地址强制转换成type型指针,就获取到了所求结构体指针,define预定义返回最后一句话的值,将所求结构体指针返回。
作用:通过指向结构体成员member的指针ptr获取指向整个结构体的指针。
这个指针指向ptr,自认为是有问题,参考别人的,请注意是typeof,不是typedef是有区别的。_mptr是指向member的指针,ptr也是指向member的指针,
注释已经这么写了,所以不能说_mptr指向ptr,那就成指针的指针。
第二次看补充:
ptr是指向list_head类型的指针,type是结构体类型,member是结构体的一成员。
3.4 offsetof
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)分析:TYPE *将整型常量0强制转换为TYPE型的指针,且这个指针指向的地址为0,也就是将地址0开始的一块存储空间映射为TYPE型的对象,接下来再对结构体中MEMBER成员进行取址,而整个TYPE结构体的首地址是0,这里获得的地址就是MEMBER成员在TYPE中的相对偏移量。再将这个偏移量强制转换成size_t型数据(无符号整型)。
offsetof:获得到成员member在结构体TYPE中的偏移地址。
之前犯的一个错误,念念不忘,纠缠了好久,不知道为什么会突然想起。
[1] 《Linux内核设计与实现》陈莉君等;
[2] http://blog.csdn.net/qq405180763/article/details/16881047,讲的很好;
[3] http://www.cnblogs.com/zhuyp1015/archive/2012/06/02/2532240.html 第二次看时,加深了理解。
相关文章推荐
- 内核数据结构之链表
- Linux内核数据结构之链表
- linux内核数据结构之链表
- Linux内核数据结构—链表
- 内核中常用的双向链表数据结构(list_head)详解
- linux内核分析--内核中的数据结构之双链表(一)
- linux内核数据结构之链表【转】
- 内核数据结构--哈希链表
- 内核数据结构之链表
- 数据结构-内核的双向循环链表-简单实现
- linux内核分析--内核中的数据结构之双链表(续)
- AT&T汇编语言与GCC内嵌汇编,Linux内核数据结构之链表
- 【Linxu内核设计与实现】-第6章 内核数据结构(待续...)
- 内核数据结构——链表
- Linux内核分析--内核中的数据结构双向链表续【转】
- Linux内核数据结构之链表
- 内核数据结构之链表
- linux内核系列(二)内核数据结构之链表
- linux内核数据结构---链表(1)
- 【内核数据结构】 内核链表分析