第四学 linux内核中最基本的数据组织方式——list.h分析(2)

7、遍历节点操作

        这节我们介绍双连表的遍历。遍历是一个非常重要的概念,学过STL的童鞋们都知道,C++的容器的大量的算法都是建立在遍历这个概念基础之上的,比如查找元素,元素排序,等等。我只想说遍历很重要,相当重要,非常重要。废话有点多,我们直接看源码。

        linux内核中定义了两个最基本的宏来遍历一个双连表,并且获取链表中某个节点的指针。以下是两个非常重要的基本宏,其他一些宏都是建立在这个两个宏之上的,或者说是对这两个宏的封装。

#define __list_for_each(pos, head) \
         for (pos = (head)->next; pos != (head); pos = pos->next)

        这个宏定义了一个遍历宏的for循环,pos是移动指针,千万注意,它指向一个节点的struct list_head成员,内核中把这个成员叫做menber,言下之意就是说,linux内核中遍历链表不是遍历指向每个节点的指针,而是遍历指向每个节点的menber成员的指针,pos依次指向下一个节点的menber成员,从而能把指针定位到任何一个节点的menber成员。这种方式当然是为了可扩展性,但是存在一个问题:我们遍历的是节点中的menber成员,而我们如何要获取节点中的数据成员呢。我们当然得获取指向这个节点的指针,然后再用这个指针去操作节点中的数据成员,那要怎么做呢。嘿嘿。linux内核想的太周到了,下边这个宏就可以为你完成这个工作,这个宏获取指向这个节点的指针。

#define list_entry(ptr, type, member) \
         container_of(ptr, type, member)

        其中ptr是指向当前节点menber成员的指针,type是你自定义的结构体类型,menber就是你结构体中的struct list_head成员。这个宏调用了container_of(ptr, type, member)这个宏来实现这个功能。这个宏时这样定义的:

#define container_of(ptr, type, member) ({                      \  
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \  
        (type *)( (char *)__mptr - offsetof(type,member) );}) 

        嘿嘿,看到这个宏有没想把自己弄死的感觉,太让人郁闷了是吧。别急,静下心来,我们慢慢来分析。

        三个参数的类型还是上边刚说过,这里不再解释了。我们先讲讲从一个节点的menber成员的指针获取一个指向节点的指针的思路:当我们定义好一个自己的结构体的时候,它的menber成员的位置就定下来了,那么这个结构体在存储的时候其相对于这个结构体的起始地址的偏移(也就是该节点的起始地址的偏移)也就定下来了,那我们要获取这个节点的首地址,只需要用menber成员的地址减去这个偏移量就行了。这里我们已经给出了menber成员的地址——ptr,那偏移量怎么计算呢?这里通过把地址为0的内存单元强制转换成一个结构体类型(type)的存储单元,那此时取这个结构体的menber成员的地址就刚好是相对于起始地址0的偏移量,用ptr将这个偏移量减去,就获得了我们当前节点的首地址。看起来也不是挺复杂是吧!下面我们看内核是怎么实现这个过程的。

        首先这个宏包含两条语句。第一条:const typeof( ((type *)0)->member ) *__mptr = (ptr);首先将0转化成type类型的指针变量(这个指针变量的地址为0×0),然后再引用member成员(对应就是((type *)0)->member ))。注意这里的typeof(x),是返回x的数据类型,那么 typeof( ((type *)0)->member )其实就是返回member成员的数据类型。那么这条语句整体就是将__mptr强制转换成member成员的数据类型,再将ptr的赋给它(ptr本身就是指向member的指针)。

        第二句中,我们先了解offsetof是什么?它也是一个宏被定义在:linux/include/stddef.h中。原型为:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER);

        ((TYPE *)0)->MEMBER)这个其实就是提取type类型中的member成员,那么&((TYPE *)0)->MEMBER)得到member成员的地址,再强制转换成size_t类型(unsigned int)。因为TYPE类型是从0地址开始定义的,那么我们现在得到的这个地址就是member成员在TYPE数据类型中的偏移量。

        我们再来看第二条语句, (type *)( (char *)__mptr – offsetof(type,member) )求的就是type的地址,即指向type的指针。不过这里要注意__mptr被强制转换成了(char *),为何要这么做?因为如果member是非char型的变量,比如为int型,并且假设返回值为offset,那么这样直接减去偏移量,实际上__mptr会减去sizeof(int)*offset!这一点和指针加一减一的原理相同。

        好,至此我们介绍完了这个令人抓狂的宏,那下边的事就小菜一碟了。

        下边这个宏获取链表中第一个节点的地址,具体不解释。

#define list_first_entry(ptr, type, member) \  
        list_entry((ptr)->next, type, member) 

        下边这个宏才是遍历的主角,prefetch()函数预取节点,为了提高速度。

#define list_for_each(pos, head) \  
        for (pos = (head)->next; prefetch(pos->next), pos != (head); \  
                pos = pos->next) 

        下边这个宏倒序遍历链表。

#define list_for_each_prev(pos, head) \  
        for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \  
                pos = pos->prev) 

        下面两个宏是上述两个便利宏的安全版,我们看它安全在那里?它多了一个与pos同类型的n,每次将下一个结点的指针暂存起来,防止pos被释放时引起的链表断裂。

#define list_for_each_safe(pos, n, head) \  
        for (pos = (head)->next, n = pos->next; pos != (head); \  
                pos = n, n = pos->next)  

    
#define list_for_each_prev_safe(pos, n, head) \  
        for (pos = (head)->prev, n = pos->prev; \  
             prefetch(pos->prev), pos != (head); \  
             pos = n, n = pos->prev) 

      

        前面我们说过,用在list_for_each宏进行遍历的时候,我们很容易得到pos,我们都知道pos存储的是当前结点前后两个结点的地址。而通过list_entry宏可以获得当前结点的地址,进而得到这个结点中其他的成员变量。而下面两个宏则可以直接获得每个结点的地址,我们接下来看它是如何实现的。pos是指向结构体的指针;head是结构体中menber成员的指针;member其实就是这个结构体中的list成员。

        在for循环中,首先通过list_entry来获得第一个结点的地址;&pos->member != (head)其实就是&pos->list!=(head);它是用来检测当前list链表是否到头了;最后在利用list_entry宏来获得下一个结点的地址。这样整个for循环就可以依次获得每个结点的地址,进而再去获得其他成员。理解了list_for_each_entry宏,那么list_for_each_entry_reverse宏就显而易见了。

#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))  

#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)) 

        下面这两个宏是从当前结点的下一个结点开始顺序(或倒序)遍历链表。

#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))  

#define list_for_each_entry_continue_reverse(pos, head, member)         \  
        for (pos = list_entry(pos->member.prev, typeof(*pos), member);  \  
             prefetch(pos->member.prev), &pos->member != (head);        \  
             pos = list_entry(pos->member.prev, typeof(*pos), member)) 

        下边这个宏从当前节点遍历链表。

#define list_for_each_entry_from(pos, head, member)                     \  
        for (; prefetch(pos->member.next), &pos->member != (head);      \  
             pos = list_entry(pos->member.next, typeof(*pos), member)) 

        当然这些遍历函数也有对应的安全版本,这里不在赘述。

        至此,我们介绍完了list.h中一些比较重要的函数和宏,下面给出一个双链表的应用的例子。









 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值