第6章内核数据结构①链表

来源:互联网 发布:rf仿真软件 编辑:程序博客网 时间:2024/06/12 20:38

《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的地址直接分别赋值给nextprev,那么它们事实上都指向自己,也形成一个空链表,

#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  第二次看时,加深了理解。

0 0