idr 机制 radix tree

来自:http://blog.csdn.net/orz415678659/article/details/8539794

一.结构体

1.idr结构体

[cpp]  view plain  copy
  1. struct idr {  
  2.     struct idr_layer __rcu *top;    //idr_layer顶层,32叉树的根  
  3.     struct idr_layer *id_free;      //指向idr_layer的空闲链表  
  4.     int layers;     //idr_layer的层数量  
  5.     int id_free_cnt;    //idr_layer空闲链表中剩余的idr_layer个数  
  6.     spinlock_t  lock;  
  7. };  

2.idr_layer结构体

[cpp]  view plain  copy
  1. struct idr_layer {  
  2.     unsigned long   bitmap; //标记位图,标记使用情况  
  3.     struct idr_layer __rcu  *ary[1<<IDR_BITS];        //子idr_layer数组ary[32]  
  4.     int count;  //ary数组使用情况  
  5.     int layer;  //层号  
  6.     struct rcu_head rcu_head;  
  7. };  

在32位系统中IDR_BITS的取值为5

[cpp]  view plain  copy
  1. #if BITS_PER_LONG == 32  
  2.     # define IDR_BITS 5  
  3.     # define IDR_FULL 0xfffffffful  
  4.     # define TOP_LEVEL_FULL (IDR_FULL >> 30)  
  5. #elif BITS_PER_LONG == 64  
  6.     # define IDR_BITS 6  
  7.     # define IDR_FULL 0xfffffffffffffffful  
  8.     # define TOP_LEVEL_FULL (IDR_FULL >> 62)  
  9. #else  
  10.     # error "BITS_PER_LONG is not 32 or 64"  
  11. #endif  

 

二.idr的初始化

[cpp]  view plain  copy
  1. #define IDR_INIT(name)      \  
  2. {               \  
  3.     .top        = NULL, \  
  4.     .id_free        = NULL, \  
  5.     .layers         = 0,    \  
  6.     .id_free_cnt    = 0,    \  
  7.     .lock       = __SPIN_LOCK_UNLOCKED(name.lock),  \  
  8. }  
  9. #define DEFINE_IDR(name)    struct idr name = IDR_INIT(name)  

定义一个idr结构体并赋值

三.分配id

1.idr_pre_get

[cpp]  view plain  copy
  1. int idr_pre_get(struct idr *idp, gfp_t gfp_mask)  
  2. {  
  3.     while (idp->id_free_cnt < IDR_FREE_MAX) { //IDR_FREE_MAX=14  
  4.         struct idr_layer *new;  //定义新的idr_layer结构体指针  
  5.         new = kmem_cache_zalloc(idr_layer_cache, gfp_mask); //分配*new内存空间  
  6.         if (new == NULL)  
  7.             return (0);  
  8.         move_to_free_list(idp, new);    //-->move_to_free_list  
  9.     }  
  10.     return 1;  
  11. }  
  12. EXPORT_SYMBOL(idr_pre_get);  

move_to_free_list

[cpp]  view plain  copy
  1. static void move_to_free_list(struct idr *idp, struct idr_layer *p)  
  2. {  
  3.     unsigned long flags;  
  4.     spin_lock_irqsave(&idp->lock, flags);  
  5.     __move_to_free_list(idp, p);    //-->__move_to_free_list  
  6.     spin_unlock_irqrestore(&idp->lock, flags);  
  7. }  

__move_to_free_list

[cpp]  view plain  copy
  1. static void __move_to_free_list(struct idr *idp, struct idr_layer *p)  
  2. {  
  3.     p->ary[0] = idp->id_free;  
  4.     idp->id_free = p;  
  5.     idp->id_free_cnt++;  
  6. }  

第一次循环结果

接着循环

再接着

一直这样下去直到循环结束(14次)

2.idr_get_new和idr_get_new_above

idr_get_new

[cpp]  view plain  copy
  1. int idr_get_new(struct idr *idp, void *ptr, int *id)  
  2. {  
  3.     int rv;  
  4.     rv = idr_get_new_above_int(idp, ptr, 0);  
  5.     if (rv < 0)  
  6.         return _idr_rc_to_errno(rv);  
  7.     *id = rv;  
  8.     return 0;  
  9. }  
  10. EXPORT_SYMBOL(idr_get_new);  

idr_get_new_above

[cpp]  view plain  copy
  1. int idr_get_new_above(struct idr *idp, void *ptr, int starting_id, int *id)  
  2. {  
  3.     int rv;  
  4.     rv = idr_get_new_above_int(idp, ptr, starting_id);  
  5.     if (rv < 0)  
  6.         return _idr_rc_to_errno(rv);  
  7.     *id = rv;  
  8.     return 0;  
  9. }  
  10. EXPORT_SYMBOL(idr_get_new_above);  

两个函数都会调用idr_get_new_above_int函数,差别在于starting_id不同

下面分情况讨论,先以id为0走个过场

idr的top简称为根top,free简称为根free均为idr_layer指针类型,分别指向使用中和空闲idr_layer链表头

[cpp]  view plain  copy
  1. static int idr_get_new_above_int(struct idr *idp, void *ptr, int starting_id)  
  2. {  
  3.     struct idr_layer *pa[MAX_LEVEL];    //MAX_LEVEL=7  
  4.     int id;  
  5.     id = idr_get_empty_slot(idp, starting_id, pa);  //-->idr_get_empty_slot  
  6.     if (id >= 0) {  
  7.         rcu_assign_pointer(pa[0]->ary[id & IDR_MASK],(struct idr_layer *)ptr  
  8.         //pa[0]->ary[0]=ptr 也就是idr_layer14->ary[0]=ptr  
  9.         pa[0]->count++;  //idr_layer14->count++  
  10.         idr_mark_full(pa, id);  //设置其位图-->走完0过场的效果见图c  
  11.     }  
  12.     return id;  
  13. }  

idr_get_empty_slot

[cpp]  view plain  copy
  1. static int idr_get_empty_slot(struct idr *idp, int starting_id,struct idr_layer **pa)  
  2. {  
  3.     struct idr_layer *p, *new;  
  4.     int layers, v, id;  
  5.     unsigned long flags;  
  6.   
  7.     id = starting_id;   //按常规出牌吧,假设这个为0  
  8. build_up:  
  9.     p = idp->top;    //根top指向的idr_layer NULL  
  10.     layers = idp->layers;    //获取layers层数量(0)  
  11.     if (unlikely(!p)) { //第一次运行idp->top=NULL,所以if条件为真,执行if分支的结果参考 图A  
  12.         if (!(p = get_from_free_list(idp))) //>>>1-->get_from_free_list 从根free中获取一个idr_layer14  
  13.             return -1;  
  14.         p->layer = 0;    //指定idr_layer14的层号为0  
  15.         layers = 1; //layers层数量设为1  
  16.     }  
  17.     //layers<6 && id>=2^(layers*5) 看需不需要增加层数 见图B  
  18.     while ((layers < (MAX_LEVEL - 1)) && (id >= (1 << (layers*IDR_BITS)))) {  
  19.         layers++;     
  20.         if (!p->count) {  
  21.             p->layer++;  
  22.             continue;     
  23.         }  
  24.         if (!(new = get_from_free_list(idp))) {  
  25.             spin_lock_irqsave(&idp->lock, flags);  
  26.             for (new = p; p && p != idp->top; new = p) {  
  27.                 p = p->ary[0];  
  28.                 new->ary[0] = NULL;  
  29.                 new->bitmap = new->count = 0;  
  30.                 __move_to_free_list(idp, new);  
  31.             }  
  32.             spin_unlock_irqrestore(&idp->lock, flags);  
  33.             return -1;  
  34.         }  
  35.         new->ary[0] = p;  
  36.         new->count = 1;  
  37.         new->layer = layers-1;  
  38.         if (p->bitmap == IDR_FULL)  
  39.             __set_bit(0, &new->bitmap);  
  40.         p = new;  
  41.     }  
  42.     rcu_assign_pointer(idp->top, p); //根top指向idr_layer14  
  43.     idp->layers = layers;    //设置更新idr->layers层数量  
  44. //----------------------------------------------分割线----------------------------------------------  
  45. //以上部分主要处理layer相关,以下部分主要处理id相关  
  46.     v = sub_alloc(idp, &id, pa);    //>>>2-->sub_alloc  
  47.     if (v == IDR_NEED_TO_GROW)  //IDR_NEED_TO_GROW=-2需要扩大  
  48.         goto build_up;  
  49.     return(v);  
  50. }  

图A:

图B

>>>get_from_free_list 从idr空闲idr_layer链表中获取第一个idr_layer

[cpp]  view plain  copy
  1. static struct idr_layer *get_from_free_list(struct idr *idp)  
  2. {  
  3.     struct idr_layer *p;    //定义一个idr_layer指针  
  4.     unsigned long flags;  
  5.     spin_lock_irqsave(&idp->lock, flags);  
  6.     if ((p = idp->id_free)) {    //根free获取一个空闲idr_layer  
  7.         idp->id_free = p->ary[0]; //idr空闲链表指针指向第二个idr_layer  
  8.         idp->id_free_cnt--;  //idr的空闲idr_layer个数减1(14-1)  
  9.         p->ary[0] = NULL;    //断开第一个idr_layer和第二个idr_layer的联系  
  10.     }  
  11.     spin_unlock_irqrestore(&idp->lock, flags);  
  12.     return(p);  
  13. }  

这里先穿插一下32进制的计算,上面图B中2^0,2^5,2^10,2^15,2^20,2^25可以(32=2^5)理解成32^0,32^1,32^2,32^3,32^3,32^4,32^5

那么用32进制表达一个十进制数id可以套用一下公式

a的值属于[0,31]

an的值如何获得id/(32^n)即可,等同于id/(2^5^n)等同于id/((1<<5)^n)

an-1的值如何获得id>>(5*(n-1))即可

>>>sub_alloc

[cpp]  view plain  copy
  1. static int sub_alloc(struct idr *idp, int *starting_id, struct idr_layer **pa)  
  2. {  
  3.     int n, m, sh;  
  4.     struct idr_layer *p, *new;  
  5.     int l, id, oid;  
  6.     unsigned long bm;  
  7.   
  8.     id = *starting_id;  
  9.  restart:  
  10.     p = idp->top;    //根top  
  11.     l = idp->layers; //l=1  
  12.     pa[l--] = NULL; //p[1]=NULL;l=0  
  13.     while (1) {  
  14.         n = (id >> (IDR_BITS*l)) & IDR_MASK;  //计算对应的n值,属于[0,31]  
  15.         bm = ~p->bitmap; //取反位图  
  16.         m = find_next_bit(&bm, IDR_SIZE, n);    //>>>1 find_next_bit 位图中偏移量为n处查找'1'  
  17.         if (m == IDR_SIZE) {    //位图满了  
  18.             l++;  
  19.             oid = id;  
  20.             id = (id | ((1 << (IDR_BITS * l)) - 1)) + 1;  
  21.             if (id >= 1 << (idp->layers * IDR_BITS)) {  
  22.                 *starting_id = id;  
  23.                 return IDR_NEED_TO_GROW;  
  24.             }  
  25.             p = pa[l];  
  26.             BUG_ON(!p);  
  27.             sh = IDR_BITS * (l + 1);  
  28.             if (oid >> sh == id >> sh)  
  29.                 continue;  
  30.             else  
  31.                 goto restart;  
  32.         }  
  33.         if (m != n) {   //期望的n值被占用,但可找到可用的m值  
  34.             sh = IDR_BITS*l;  
  35.             id = ((id >> sh) ^ n ^ m) << sh;    //>>>2 重新计算id值  
  36.         }  
  37.         if ((id >= MAX_ID_BIT) || (id < 0))  
  38.             return IDR_NOMORE_SPACE;  
  39.         if (l == 0) //l==0跳出while循环  
  40.             break;  
  41.         if (!p->ary[m]) {  
  42.             new = get_from_free_list(idp);  
  43.             if (!new)  
  44.                 return -1;  
  45.             new->layer = l-1;  
  46.             rcu_assign_pointer(p->ary[m], new);  
  47.             p->count++;  
  48.         }  
  49.         pa[l--] = p;  
  50.         p = p->ary[m];  
  51.     }  
  52.   
  53.     pa[l] = p;  //pa[0]=p 也就是idr_layer14  
  54.     return id;  
  55. }  

>>>find_next_bit

[cpp]  view plain  copy
  1. #define find_next_bit(p,sz,off) _find_next_bit_le(p,sz,off)     //>>_find_next_bit_le  

该宏的意思是在p指向的(大小为sz的)位图表中的第off个位置开始找寻可用(为"1")的格子,找到返回该位

_find_next_bit_le是汇编代码实现的定义在/arch/arm/lib/findbit.S

[cpp]  view plain  copy
  1. ENTRY(_find_next_bit_le)  
  2.         teq r1, #0  
  3.         beq 3b  
  4.         ands    ip, r2, #7  
  5.         beq 1b          @ If new byte, goto old routine  
  6.  ARM(       ldrb    r3, [r0, r2, lsr #3]    )  
  7.  THUMB(     lsr r3, r2, #3      )  
  8.  THUMB(     ldrb    r3, [r0, r3]        )  
  9.         movs    r3, r3, lsr ip      @ shift off unused bits  
  10.         bne .L_found  
  11.         orr r2, r2, #7      @ if zero, then no bits here  
  12.         add r2, r2, #1      @ align bit pointer  
  13.         b   2b          @ loop for next bit  
  14. ENDPROC(_find_next_bit_le)  

.L_found找到合适的跳转

[cpp]  view plain  copy
  1. .L_found:  
  2. #if __LINUX_ARM_ARCH__ >= 5  
  3.         rsb r0, r3, #0  
  4.         and r3, r3, r0  
  5.         clz r3, r3  
  6.         rsb r3, r3, #31  
  7.         add r0, r2, r3  
  8. #else  
  9.         tst r3, #0x0f  
  10.         addeq   r2, r2, #4  
  11.         movne   r3, r3, lsl #4  
  12.         tst r3, #0x30  
  13.         addeq   r2, r2, #2  
  14.         movne   r3, r3, lsl #2  
  15.         tst r3, #0x40  
  16.         addeq   r2, r2, #1  
  17.         mov r0, r2  
  18. #endif  
  19.         cmp r1, r0          @ Clamp to maxbit  
  20.         movlo   r0, r1  
  21.         mov pc, lr  

>>>id值的计算的补充说明

首先前面n的取值n = (id >> (IDR_BITS*l)) & IDR_MASK;

IDR_MASK的定义#define IDR_MASK ((1 << IDR_BITS)-1)也就是说IDR_MASK=31等于2进制的1,1111b

所以&IDR_MASK只是框定n值落在0~31之间,掩码作用

那么不出意外的话n = (id >> (IDR_BITS*l))

接着

sh = IDR_BITS*l;
id = ((id >> sh) ^ n ^ m) << sh;

带入表达式中

id=((id >> IDR_BITS*l) ^ (id >> (IDR_BITS*l)) ^ m) << IDR_BITS*l;

异或的操作是相同为1,不同为0,结合起来化简得

id = ((1 ^ m) << sh=m<<sh

图C

^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_^_

 已经借用id0走了过场,下面分析下其他情况

[cpp]  view plain  copy
  1. static int idr_get_new_above_int(struct idr *idp, void *ptr, int starting_id)  
  2. {  
  3.     struct idr_layer *pa[MAX_LEVEL];    //定义父idr_layer数组  
  4.     int id;  
  5.     id = idr_get_empty_slot(idp, starting_id, pa);  //获取id  
  6.     if (id >= 0) {  
  7.         rcu_assign_pointer(pa[0]->ary[id & IDR_MASK],(struct idr_layer *)ptr);  
  8.         //pa[0]->ary[id]=ptr  
  9.         pa[0]->count++;  //idr_layer->count++  
  10.         idr_mark_full(pa, id);  //标记id位图  
  11.     }  
  12.     return id;  
  13. }  
  14.   
  15. static int idr_get_empty_slot(struct idr *idp, int starting_id,struct idr_layer **pa)  
  16. {  
  17.     struct idr_layer *p, *new;  
  18.     int layers, v, id;  
  19.     unsigned long flags;  
  20.   
  21.     id = starting_id;  
  22. build_up:  
  23.     p = idp->top;    //获取根top  
  24.     layers = idp->layers;    //获取层数量 layers=1  
  25.     if (unlikely(!p)) { //FALSE  
  26.         if (!(p = get_from_free_list(idp)))  
  27.             return -1;  
  28.         p->layer = 0;  
  29.         layers = 1;  
  30.     }  
  31.   
  32.     while ((layers < 6) && (id >= (1 << (layers*5)))) { //参考图B,如果id值超过或等于对应层所能容纳的最大数,则进入循环  
  33.         layers++;       //增加层数量  
  34.         if (!p->count) { //0~31没使用,直接使用32就属于这种情况  
  35.             p->layer++;  //由于32需要添加1层的,所以之前的层的层号需要+1  
  36.             continue;    //层数量也需要加1   
  37.         }  
  38.         if (!(new = get_from_free_list(idp))) { //空闲链表中获取新的idr_layer  
  39.             spin_lock_irqsave(&idp->lock, flags);    //分配失败,--空闲idr_layer链表缺货  
  40.             for (new = p; p && p != idp->top; new = p) { //p指针还原  
  41.                     p = p->ary[0];  
  42.                 new->ary[0] = NULL;  
  43.                 new->bitmap = new->count = 0;  
  44.                 __move_to_free_list(idp, new);  //分配更多空闲链表  
  45.             }  
  46.             spin_unlock_irqrestore(&idp->lock, flags);  
  47.             return -1;  
  48.         }  
  49.         new->ary[0] = p; //新的idr_layer->ary[0]指向旧的idr_layer  
  50.         new->count = 1;  //新的idr_layer计数加1  
  51.         new->layer = layers-1;   //设置新的idr_layer的层号  
  52.         if (p->bitmap == IDR_FULL)   //若旧的(叶子)idr_layer的id全用过了  
  53.             __set_bit(0, &new->bitmap);  //那么标记下新(父)idr_layer位图的第0位  
  54.         p = new;    //根top指向新的idr_layer  
  55.     }  
  56.     rcu_assign_pointer(idp->top, p); //设置根top  
  57.     idp->layers = layers;    //更新层数量  
  58.     v = sub_alloc(idp, &id, pa);    //获取id  
  59.     if (v == IDR_NEED_TO_GROW)  //该层id号全用完了,必须扩大idr_layer层数量  
  60.         goto build_up;  
  61.     return(v);  
  62. }  
  63.   
  64. static int sub_alloc(struct idr *idp, int *starting_id, struct idr_layer **pa)  
  65. {  
  66.     int n, m, sh;  
  67.     struct idr_layer *p, *new;  
  68.     int l, id, oid;  
  69.     unsigned long bm;  
  70.   
  71.     id = *starting_id;  
  72.  restart:  
  73.     p = idp->top;    //获取根top  
  74.     l = idp->layers; //获取层数量l=1  
  75.     pa[l--] = NULL; //pa[1]=NULL,l=0  
  76.     while (1) {  
  77.         n = (id >> (5*l)) & IDR_MASK; //n做处理 属于[0,31]  
  78.         bm = ~p->bitmap; //位图取反  
  79.         m = find_next_bit(&bm, IDR_SIZE, n);    //查找n开始能用的位  
  80.         if (m == IDR_SIZE) {    //id表满了  
  81.             l++;    层数+1  
  82.             oid = id;  
  83.             id = (id | ((1 << (5 * l)) - 1)) + 1; //id或上掩码再+1   
  84.   
  85.             if (id >= 1 << (idp->layers * 5)) { //需要添加层  
  86.                 *starting_id = id;  
  87.                 return IDR_NEED_TO_GROW;  
  88.             }  
  89.             p = pa[l];  
  90.             BUG_ON(!p);  
  91.             sh = 5 * (l + 1);  
  92.             if (oid >> sh == id >> sh)  
  93.                 continue;  
  94.             else  
  95.                 goto restart;  
  96.         }  
  97.         if (m != n) {   //期望id给用但有可用id  
  98.             sh = 5*l;  
  99.             id = ((id >> sh) ^ n ^ m) << sh;    //id设置为可用id  
  100.         }  
  101.         if ((id >= MAX_ID_BIT) || (id < 0))  
  102.             return IDR_NOMORE_SPACE;  
  103.         if (l == 0) //一层层循环计算直到到达叶子处l才为0  
  104.             break;  
  105.         if (!p->ary[m]) {    //叶子m为空  
  106.             new = get_from_free_list(idp);  //从空闲链表拿一个idr_layer  
  107.             if (!new)  
  108.                 return -1;  
  109.             new->layer = l-1;    //设置新链表层数  
  110.             rcu_assign_pointer(p->ary[m], new);  //叶子m指向新链表  
  111.             p->count++;  //使用计数加1  
  112.         }  
  113.         pa[l--] = p;    //pa[大]=节点  
  114.         p = p->ary[m];   //p=节点->叶子m    
  115.     }  
  116.     pa[l] = p;      //pa[小]=叶子  
  117.     return id;  
  118. }  

来个效果图id=4吧


id=32情况(idr_layer13的位图1标记错了)

 

1024情况



四.查找id
1.idr_find

[cpp]  view plain  copy
  1. void *idr_find(struct idr *idp, int id)  
  2. {  
  3.     int n;  
  4.     struct idr_layer *p;  
  5.   
  6.     p = rcu_dereference_raw(idp->top);   //获取根top  
  7.     if (!p)  
  8.         return NULL;  
  9.     n = (p->layer+1) * IDR_BITS; //计算最外层的n值  
  10.     id &= MAX_ID_MASK;  
  11.     if (id >= (1 << n))  
  12.         return NULL;  
  13.     BUG_ON(n == 0);  
  14.     while (n > 0 && p) { //循环一层层查找  
  15.         n -= IDR_BITS;  
  16.         BUG_ON(n != p->layer*IDR_BITS);  
  17.         p = rcu_dereference_raw(p->ary[(id >> n) & IDR_MASK]); //一次获取an ... a0   
  18.     }  
  19.     return((void *)p);  
  20. }  
  21. EXPORT_SYMBOL(idr_find);  

前面讲过32进制的id值算法


当构建完idr机制之后

id=top->ary[an]->ary[a(n-1)]->....->ary[a0]来获得

 借助图片分析下(idr_layer13的位图标记有错)

 

五idr操作

1. idr_remove  idr_remove_all 移除

[cpp]  view plain  copy
  1. void idr_remove(struct idr *idp, int id)  
  2. {  
  3.     struct idr_layer *p;  
  4.     struct idr_layer *to_free;  
  5.     id &= MAX_ID_MASK;  
  6.   
  7.     sub_remove(idp, (idp->layers - 1) * IDR_BITS, id);  
  8.     if (idp->top && idp->top->count == 1 && (idp->layers > 1) &&  
  9.         idp->top->ary[0]) {  
  10.         to_free = idp->top;  
  11.         p = idp->top->ary[0];  
  12.         rcu_assign_pointer(idp->top, p);  
  13.         --idp->layers;  
  14.         to_free->bitmap = to_free->count = 0;  
  15.         free_layer(to_free);  
  16.     }  
  17.     while (idp->id_free_cnt >= IDR_FREE_MAX) {  
  18.         p = get_from_free_list(idp);  
  19.         kmem_cache_free(idr_layer_cache, p);  
  20.     }  
  21.     return;  
  22. }  
  23. EXPORT_SYMBOL(idr_remove);  

移除全部

[cpp]  view plain  copy
  1. void idr_remove_all(struct idr *idp)  
  2. {  
  3.     int n, id, max;  
  4.     int bt_mask;  
  5.     struct idr_layer *p;  
  6.     struct idr_layer *pa[MAX_LEVEL];  
  7.     struct idr_layer **paa = &pa[0];  
  8.   
  9.     n = idp->layers * IDR_BITS;  
  10.     p = idp->top;  
  11.     rcu_assign_pointer(idp->top, NULL);  
  12.     max = 1 << n;  
  13.   
  14.     id = 0;  
  15.     while (id < max) {  
  16.         while (n > IDR_BITS && p) {  
  17.             n -= IDR_BITS;  
  18.             *paa++ = p;  
  19.             p = p->ary[(id >> n) & IDR_MASK];  
  20.         }  
  21.   
  22.         bt_mask = id;  
  23.         id += 1 << n;  
  24.         /* Get the highest bit that the above add changed from 0->1. */  
  25.         while (n < fls(id ^ bt_mask)) {  
  26.             if (p)  
  27.                 free_layer(p);  
  28.             n += IDR_BITS;  
  29.             p = *--paa;  
  30.         }  
  31.     }  
  32.     idp->layers = 0;  
  33. }  
  34. EXPORT_SYMBOL(idr_remove_all);  


 

2.idr_replace 替换

[cpp]  view plain  copy
  1. void *idr_replace(struct idr *idp, void *ptr, int id)  
  2. {  
  3.     int n;  
  4.     struct idr_layer *p, *old_p;  
  5.   
  6.     p = idp->top;  
  7.     if (!p)  
  8.         return ERR_PTR(-EINVAL);  
  9.   
  10.     n = (p->layer+1) * IDR_BITS;  
  11.   
  12.     id &= MAX_ID_MASK;  
  13.   
  14.     if (id >= (1 << n))  
  15.         return ERR_PTR(-EINVAL);  
  16.   
  17.     n -= IDR_BITS;  
  18.     while ((n > 0) && p) {  
  19.         p = p->ary[(id >> n) & IDR_MASK];  
  20.         n -= IDR_BITS;  
  21.     }  
  22.   
  23.     n = id & IDR_MASK;  
  24.     if (unlikely(p == NULL || !test_bit(n, &p->bitmap)))  
  25.         return ERR_PTR(-ENOENT);  
  26.   
  27.     old_p = p->ary[n];  
  28.     rcu_assign_pointer(p->ary[n], ptr);  
  29.   
  30.     return old_p;  
  31. }  
  32. EXPORT_SYMBOL(idr_replace);  

 

六.idr空闲链表的销毁

idr_destroy

[cpp]  view plain  copy
  1. void idr_destroy(struct idr *idp)  
  2. {  
  3.     while (idp->id_free_cnt) {  
  4.         struct idr_layer *p = get_from_free_list(idp);  
  5.         kmem_cache_free(idr_layer_cache, p);  
  6.     }  
  7. }  
  8. EXPORT_SYMBOL(idr_destroy);  

 

七.用法

1.api函数

[cpp]  view plain  copy
  1. void *idr_find(struct idr *idp, int id);    //查找id对应的指针  
  2. int idr_pre_get(struct idr *idp, gfp_t gfp_mask);   //分配idr_layer空闲链表  
  3. int idr_get_new(struct idr *idp, void *ptr, int *id);   //获取id,捆绑指针ptr  
  4. int idr_get_new_above(struct idr *idp, void *ptr, int starting_id, int *id);    //起始数值获取id,捆绑指针ptr  
  5. int idr_for_each(struct idr *idp,int (*fn)(int id, void *p, void *data), void *data);  
  6. void *idr_get_next(struct idr *idp, int *nextid);     
  7. void *idr_replace(struct idr *idp, void *ptr, int id);  //替换id捆绑的指针  
  8. void idr_remove(struct idr *idp, int id);   //移除id  
  9. void idr_remove_all(struct idr *idp);   //移除所有id  
  10. void idr_destroy(struct idr *idp);  //销毁idr_layer空闲链表  
  11. void idr_init(struct idr *idp); //初始化idr  

2.大致用法

1.idr_init声明设置idr

2.idr_pre_get分配空闲idr_layer链表

3.id_get_new/idr_get_new_above分配id并将id与指针ptr捆绑

4.利用idr_find根据id获取指针ptr

5.idr_remove/idr_remove_all移除分配的id

6.idr_destroy销毁空闲idr_layer链表

 7.idr_replace替换id




基数(radix)树


Linux基数树(radix tree)是将指针与long整数键值相关联的机制,它存储有效率,并且可快速查询,用于指针与整数值的映射(如:IDR机制)、内存管理等。
IDR(ID Radix)机制是将对象的身份鉴别号整数值ID与对象指针建立关联表,完成从ID与指针之间的相互转换。IDR机制使用radix树状结构作为由id进行索引获取指针的稀疏数组,通过使用位图可以快速分配新的ID,IDR机制避免了使用固定尺寸的数组存放指针。IDR机制的API函数在lib/idr.c中实现,这里不加分析。
Linux radix树最广泛的用途是用于内存管理,结构address_space通过radix树跟踪绑定到地址映射上的核心页,该radix树允许内存管理代码快速查找标识为dirty或writeback的页。Linux radix树的API函数在lib/radix-tree.c中实现。


(1)radix树概述


radix树是通用的字典类型数据结构,radix树又称为PAT位树(Patricia Trie or crit bit tree)。Linux内核使用了数据类型unsigned long的固定长度输入的版本。每级代表了输入空间固定位数。
radix tree是一种多叉搜索树,树的叶子结点是实际的数据条目。每个结点有一个固定的、2^n指针指向子结点(每个指针称为槽slot),并有一个指针指向父结点。

Linux内核利用radix树在文件内偏移快速定位文件缓存页,图4是一个radix树样例,该radix树的分叉为4(22),树高为4,树的每个叶子结点用来快速定位8位文件内偏移,可以定位4x4x4x4=256页,如:图中虚线对应的两个叶子结点的路径组成值0x00000010和0x11111010,指向文件内相应偏移所对应的缓存页。 


图4 一个四叉radix树


Linux radix树每个结点有64个slot,与数据类型long的位数相同,图1显示了一个有3级结点的radix树,每个数据条目(item)可用3个6位的键值(key)进行索引,键值从左到右分别代表第1~3层结点位置。没有孩子的结点在图中不出现。因此,radix树为稀疏树提供了有效的存储,代替固定尺寸数组提供了键值到指针的快速查找。 


图1 一个3级结点的radix树及其键值表示


(2)radix树slot数


Linux内核根用户配置将树的slot数定义为4或6,即每个结点有16或64个slot,如图2所示,当树高为1时,64个slot对应64个页,当树高为2时,对应64*64个页。 


图2 高为1和2、slot数为64的radix树


Linux内核radix树的slot数定义如下(在lib/radix-tree.c中):


#ifdef __KERNEL__
/*值为6时,表示每个结点有2^6=64个slot,值为4时,表示有2^4=16个slot*/
#define RADIX_TREE_MAP_SHIFT (CONFIG_BASE_SMALL ? 4 : 6)
#else
#define RADIX_TREE_MAP_SHIFT 3 /* 用于更有强度的测试 */
#endif
/*表示1个叶子结点可映射的页数,如:1<<6=64,表示可映射64个slot映射64页*/
#define RADIX_TREE_MAP_SIZE (1UL << RADIX_TREE_MAP_SHIFT)
#define RADIX_TREE_MAP_MASK (RADIX_TREE_MAP_SIZE-1)
/*定义slot数占用的long类型长度个数,每个slot用位图1位进行标记,如:64个slot时,值为2*/
#define RADIX_TREE_TAG_LONGS \
((RADIX_TREE_MAP_SIZE + BITS_PER_LONG - 1) / BITS_PER_LONG)


(3)结点结构


树的根结点结构radix_tree_root列出如下(在include/linux/radix-tree.h中):
#define RADIX_TREE_MAX_TAGS 2 /*每个slot需要的最大标签位数*/

/*根结点的标签存放在gfp_mask中,通过__GFP_BITS_SHIFT移位得到 */
struct radix_tree_root {
    unsigned int height; /* 从叶子向上计算的树高度 */
    gfp_t gfp_mask;
    /*间接指针指向结点而非数据条目,通过设置root->rnode的低位表示是否是间指针*/
    struct radix_tree_node *rnode;
#ifdef CONFIG_RADIX_TREE_CONCURRENT
    struct radix_tree_context *context;
    struct task_struct *owner;
#endif
};


结构radix_tree_context列出如下:
struct radix_tree_context {
    struct radix_tree_root *root;
#ifdef CONFIG_RADIX_TREE_CONCURRENT
    spinlock_t *locked;
#endif
}


树的结点结构radix_tree_node列出如下(在lib/radix-tree.c中):

struct radix_tree_node {
    unsigned int height; /* 从叶子向上计算的树高度 */
    unsigned int count; /*非叶子结点含有一个count域,表示出现在该结点的孩子的数量*/

    struct rcu_head rcu_head;
    void *slots[RADIX_TREE_MAP_SIZE]; /*结点的slot指针*/
   /* 结点标签数组=每个slot需要的最大标签位数*slot数所需的long类型变量数 */
   unsigned long tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONGS];
};


Linux内核radix树高度是可变的,它依赖于存储的索引键值。radix树查询必须知道树高度,以便知道结点的slot是指向叶子结点的指针还是结点的其他层。Linux在结点结构中有成员height存入树高度信息。
Linux radix树的独特特征是标签扩展。图2显示了有2个标签,分别表示打开和关闭。这些标签是结点结构的成员tags,该标签用一个位图实现,在加锁的情况下设置和清除。每个标签基本上是一个在radix树顶层上的位图序号。标签与群查询一起用来在给定的范围内查找给定标签集的页。
标签在每结点位图中维护,在每个给定的级别上,可以判定下一个级别是否至少有一个标签集。

 
图2 带有2位图标签指示的8位radix树


在结点结构radix_tree_node中,tags域是一个二维数组,每个slot用2位标识,这是一个典型的用空间换时间的应用。tags域用于记录该结点下面的子结点有没有相应的标志位。目前RADIX_TREE_MAX_TAGS为2,表示只记录两个标志,其中tags[0]为PAGE_CACHE_DIRTY,tags[1]为PAGE_CACHE_WRITEBACK。如果当前节点的tags[0]值为1,那么它的子树节点就存在PAGE_CACHE_DIRTY节点,否则这个子树分枝就不存在着这样的节点,就不必再查找这个子树了。比如在查找PG_dirty的页面时,就不需要遍历整个树,而可以跳过那些tags[0]为0值的子树,这样就提高了查找效率。
“加标签查询”是返回有指定标签集radix树条目的查询,可以查询设置了标签的条目。加标签查询可以并行执行,但它们需要通过设置或清除标签从操作上互斥。


(4) 并行操作的优化


Linux radix树并行操作包括并行查询和并行修改,其中,并行修改在标准内核中未完没有实现,需要通过打补丁获得该功能。并行操作说明如下:


  • RCU并发查询


通过使用RCU,RCU Radix树可以进行完全并发的查询操作。RCU从根本上要求原子操作地移动指针从数据结构的一个版本到新的版本,保持旧版本直到系统经过静止状态。在静止状态点,旧版本数据结构已没有用户,因此可以被安全释放。
RCU radix树的修改操作之间还需要串行化,但是查询不再需要与修改操作串行化。


  • 并发修改


RCU可使RCU radix树查询完全并行化,但修改操作成了“瓶颈”。这可通过将全树的锁破碎成较小的锁进行改善,再明显的方法是对结点进行加锁而非对整个树加锁。
radix树修改操作可分为单向和双向操作。单向操作仅执行从根节点和叶子结点的单方向指针移动,它包括插入、更新和设置标签操作。双向操作较复杂,它需要在指针移到叶子后又回移,它包括删除和清除标签操作。
梯级加锁(Ladder Locking)和锁耦合(Lock-Coupling)技术常用于数据库方面,允许单向遍历结点加锁的树(双向可能产生死锁)。如果所有的修改者从树顶到树底进行修改,并且修改的结点持有锁,那么,向下遍历时对孩子加锁,在孩子被锁住时再释放该结点锁。在这种情况下并发操作是可能的,因为只要根结点解锁,另一个操作就可以自上向下进行。如果两操作的路径没有相同操作结点,后一个操作可能在前一个操作完成之前完成。最坏的情况是流水线操作,但这还是比串行化操作好很多。


双向操作包括删除和清除标签操作,分别说明如下:


1)清除标签


在radix树中清除一个标签包括向下遍历树、查找定位条目和清除条目标签的操作。只要孩子结点没有打标签的条目,就可以向上遍历结点清除标签。结束条件是:如果遍历遇到一个结点,在清除一个标签后,它还有一个或多个条目带有标签集,就可以结束向上遍历。为了与向下遍历期间有同样的结束点,将终止条件改为:向上遍历将在有比清除标签数更多标签的结点处结束。这样,不论何时遇到这样的结点,将作为上遍历树的结束点。


2)删除元素


删除元素在删除无用结点时还需要删除该条目的所有标签。它的终止条件需要满足这两个方面。向上回退遍历树时需要满足下面的条件:当遇到一个非空结点且没有无用的标签时应终止向上回退遍历树。
在向下遍历树时鉴别此点的条件是:当遇到有超过2个孩子的结点、并且每个标签来说结点有多于一个标签条目被清除时,结束向上遍历。该条件用来鉴别向上回退遍历的终止点。


(5)radix树API说明


  • 声明和初始化radix树

声明和初始化radix树的方法列出如下:


#include <linux/radix-tree.h>
/* gfp_mask表示如何执行内存分配,如果操作(如:插入)以原子性上下文中执行,其值为GFP_ATOMIC*/
RADIX_TREE(name, gfp_mask); /* 声明和初始化名为name的树*/

struct radix_tree_root my_tree;
INIT_RADIX_TREE(my_tree, gfp_mask);


  • 插入条目


插入条目的函数定义列出如下:

int radix_tree_insert(struct radix_tree_root *root, unsigned long index, void *item)


函数radix_tree_insert插入条目item到树root中,如果插入条目中内存分配错误,将返回错误-ENOMEM。该函数不能覆盖写正存在的条目。如果索引键值index已存在于树中,返回错误-EEXIST。插入操作成功是,返回0。
对于插入条目操作失败将引起严重问题的场合,下面的一对函数可避免插入操作失败:

int radix_tree_preload(gfp_t gfp_mask);
void radix_tree_preload_end(void);


函数radix_tree_preload尝试用给定的gfp_mask分配足够的内存,保证下一个插入操作不会失败。在调用插入操作函数之前调用此函数,分配的结构将存放在每CPU变量中。函数radix_tree_preload操作成功后,将完毕内核抢占。因此,在插入操作完成之后,用户应调用函数radix_tree_preload_end打开内核抢占。


  • 删除条目


删除条目的函数定义列出如下:

void *radix_tree_delete(struct radix_tree_root *root, unsigned long index)


函数radix_tree_delete删除与索引键值index相关的条目,如果删除条目在树中,返回该条目的指针,否则返回NULL。


  • 查询操作


用于查询操作的函数定义列出如下:

/*在树中查找指定键值的条目,查找成功,返回该条目的指针,否则,返回NULL*/
void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index);
/*返回指向slot的指针,该slot含有指向查找到条目的指针*/
void **radix_tree_lookup_slot(struct radix_tree_root *root, unsigned long index);
/*查询返回max_items条目在results中。查询时键值索引从first_index开始*/
radix_tree_gang_lookup(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items);


  • 标签操作


与标签操作相关的函数说明列出如下:

/*将键值index对应的条目设置标签tag,返回值为设置标签的条目*/
void *radix_tree_tag_set(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*从键值index对应的条目清除标签tag,返回值为清除标签的条目*/
void *radix_tree_tag_clear(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*检查键值index对应的条目是否为标签tag,如果键值不存在,返回0,如果键值存在,但标签未设置,返回-1;如果键值存在,且标签已设置,返回1*/
int radix_tree_tag_get(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*从first_index起查询树root中标签值为tag的条目,在results中返回*/
unsigned int radix_tree_gang_lookup_tag(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items, unsigned int tag);
/*如果树root中有任何条目使用tag标签,返回键值*/
int radix_tree_tagged(struct radix_tree_root *root, unsigned int tag);


(6)并行操作使用radix树API的方法


  • 查询获取slot操作


查询操作支持RCU无阻塞并行读操作,因此,需要遵循RCU的用法加RCU读锁,还需要将rcu_dereference()用于获得的slot,在写(或更新)操作时,需要给新的slot使用rcu_assign_pointer()。查询操作的使用方法列出如下:

struct page **slot, *page;
rcu_read_lock();
slot = radix_tree_lookup_slot(&mapping->page_tree, index);
page = rcu_dereference(*slot);
rcu_read_unlock();


  • 查询修改slot操作


Linux内核的radix树需要打补丁才支持并发修改。查询仅有一个全局状态:RCU静止状态,并发修改需要跟踪持有什么锁。锁状态对于操作来说必须是外部的,因此,我们需要实例化一个本地上下文跟踪这些锁。查询修改slot的方法列出如下:

struct page **slot;
DEFINE_RADIX_TREE_CONTEXT(ctx,&mapping->page_tree);
radix_tree_lock(&ctx); /*锁住了根结点*/
/* ctx.tree代替&mapping->page_tree作为根,可以传递上下文
slot = radix_tree_lookup_slot(tx.tree, index);
rcu_assign_pointer(*slot, new_page);
radix_tree_unlock(&ctx);


radix树API函数radix_tree_lookup_slot含有锁从树顶向下移动机制,锁移动的代码部分列出如下:

void **radix_tree_lookup_slot(struct radix_tree *root, unsigned long index)
{
    ...
    RADIX_TREE_CONTEXT(context, root); /*提供上下文和实际的root指针*、
    ...
    do {
        ...
        /* 从树顶向下移动锁*/
        radix_ladder_lock(context, node);
        ...
    } while (height > 0);
    ...
}


(7)radix树API的实现


树的操作通常包括查找、插入、删除和树调整,下面分别说明radix树这些操作的实现。


  • 查找单个条目


radix树通过索引键值进行查询,如图1所示,它按路径组成索引键值,图中3级结点对应3段6位表示的索引值,图中key对应的叶子slot的索引键值为“2,63,1”。通过叶子slot的指针slot[1]就可得到所存储的数据item。因此,查询迭代时,需要将索引键值“2,63,1”移去高2层“2,63”,得到叶子slot的指针索引键值“1”。

 
图1 一个3级结点的radix树及其键值表示


函数radix_tree_lookup执行查找操作,查找方法是:从叶子到树顶,通过数组索引键值值查看数组元素的方法,一层层地查找slot。其列出如下(在lib/radix-tree.c中):

void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index)
{
    unsigned int height, shift;
    struct radix_tree_node *node, **slot;

    node = rcu_dereference(root->rnode); /*获取根结点*/
    if (node == NULL)
        return NULL;


    /*间接指针指向结点而非数据条目,通过设置root->rnode的低位表示是否是间指针。对于间接指针来说,树高度值root->height大于0,但是RCU查找需要测试间接指针,因为root->height 值不可靠。这种问题仅的RCU下需要考虑*/ 
    if (!radix_tree_is_indirect_ptr(node)) { /*非间接指针,说明只有根结点*/
        if (index > 0)
            return NULL;
        return node;
    }


    /*获取真正结点指针,因为根结点指针的第0位设置为1表示为间接指针。当使用结点指针时,必须将第0位设置为0,因为地址以字对齐*/
    node = radix_tree_indirect_to_ptr(node);

    height = node->height;
    if (index > radix_tree_maxindex(height)) /*索引键值不能超过最大索引值*/
        return NULL;


    /*每层索引偏移值为RADIX_TREE_MAP_SHIFT,叶子索引值偏移基数为(树高-1)*每层索引偏移值*/ 
    shift = (height-1) * RADIX_TREE_MAP_SHIFT;

    do { /*从叶子到树顶,通过树路径组成的索引查找指定索引键值的slot*/
        slot = (struct radix_tree_node **)(node->slots + ((index>>shift) & RADIX_TREE_MAP_MASK)); /*如:slots +1*/
        node = rcu_dereference(*slot);
        if (node == NULL)
            return NULL;

        shift -= RADIX_TREE_MAP_SHIFT; /*向上移一层,再迭代查找*/
        height--;
    } while (height > 0);

   return node;
}


  • 查找多个条目


函数radix_tree_gang_lookup执行多个索引键值的查找,其列出如下:

unsigned int radix_tree_gang_lookup(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items)
{
    unsigned long max_index;
    struct radix_tree_node *node;
    unsigned long cur_index = first_index;
    unsigned int ret;

    node = rcu_dereference(root->rnode);
    if (!node)
        return 0;

    if (!radix_tree_is_indirect_ptr(node)) { /*如果为非间接指针,表示只有根节点*/
        if (first_index > 0)
            return 0;
        results[0] = node;
        return 1;
    }
    node = radix_tree_indirect_to_ptr(node); /*清除用于间接指针标识的第0位*/

    max_index = radix_tree_maxindex(node->height); /*获取树的最大索引键值*/

    ret = 0;
    while (ret < max_items) { /* max_items为查找的最大条目数*/
        unsigned int nr_found;
        unsigned long next_index; /* 下一个搜索的索引键值*/

        if (cur_index > max_index) /*已查询完所需查询的索引键值*/
            break;
        nr_found = __lookup(node, results + ret, cur_index,
        max_items - ret, &next_index);
        ret += nr_found;
        if (next_index == 0)
            break;
        cur_index = next_index;
    }

    return ret;
}


static unsigned int
__lookup(struct radix_tree_node *slot, void **results, unsigned long index, unsigned int max_items, unsigned long *next_index)
{
    unsigned int nr_found = 0;
    unsigned int shift, height;
    unsigned long i;

    height = slot->height;
    if (height == 0)
        goto out;
    /*所有叶子slot的索引键值基数偏移*/
    shift = (height-1) * RADIX_TREE_MAP_SHIFT; 

    /*从底层向树顶层,
    for ( ; height > 1; height--) { /*从叶子向树顶查找*/
        i = (index >> shift) & RADIX_TREE_MAP_MASK;
        for (;;) { /*遍历每一层的各个路径,由树顶到当前层一条路径组成索引键值*/
            /*如果slot不为空,那么它挂有子结点,跳出本循环,进入子结点层进行本循环*/
            if (slot->slots[i] != NULL)
                break;
           /*如果slot为空,就跳过slot对应的所有索引键值*/ 
           /*清除索引号低位.将索引号与该层slot的起始索引号对齐*/
           index &= ~((1UL << shift) - 1);
           /*跳过一个slot的索引键值数*/
          index += 1UL << shift;
          if (index == 0)
              goto out; /* 32-bit wraparound */
          i++; /*找到多个slot*/
          if (i == RADIX_TREE_MAP_SIZE)
              goto out;
       }

       shift -= RADIX_TREE_MAP_SHIFT; /*向上移一层,基数偏移减少*/
       slot = rcu_dereference(slot->slots[i]);
       if (slot == NULL)
           goto out;

    }

    /* 返回找到的多个slot*/
    for (i = index & RADIX_TREE_MAP_MASK; i < RADIX_TREE_MAP_SIZE; i++) {
        struct radix_tree_node *node;
        index++;
        node = slot->slots[i];
        if (node) {
            results[nr_found++] = rcu_dereference(node);
           if (nr_found == max_items)
               goto out;
        }
    }
    out:
    *next_index = index;
    return nr_found;
}


  • 插入条目


函数radix_tree_insert找到索引键值对应的结点,将item加到该结点的slot指针上。其列出如下:

int radix_tree_insert(struct radix_tree_root *root, unsigned long index, void *item)
{
    struct radix_tree_node *node = NULL, *slot;
    unsigned int height, shift;
    int offset;
    int error;

    BUG_ON(radix_tree_is_indirect_ptr(item));

    /* 如果树的高度不够,就扩展树。函数radix_tree_maxindex计算树高容纳的最大索引*/
    if (index > radix_tree_maxindex(root->height)) {
        error = radix_tree_extend(root, index);
        if (error)
            return error;
    }

    slot = radix_tree_indirect_to_ptr(root->rnode);

    height = root->height;
    shift = (height-1) * RADIX_TREE_MAP_SHIFT; /*计算偏移基数*/

    offset = 0; 
    while (height > 0) {
        if (slot == NULL) { /*如果slot为空,则需要加入孩子结点*/
            /* 分配slot */
            if (!(slot = radix_tree_node_alloc(root)))
                return -ENOMEM;
            slot->height = height;
            if (node) {/*添加slot*/
                rcu_assign_pointer(node->slots[offset], slot);
             node->count++;
         } else
             rcu_assign_pointer(root->rnode,radix_tree_ptr_to_indirect(slot));
        }

        /* 进入上一层*/
        offset = (index >> shift) & RADIX_TREE_MAP_MASK;
        node = slot;
        slot = node->slots[offset];
        shift -= RADIX_TREE_MAP_SHIFT;
        height--;
    }
    /*如果index对应的slot已有映射页面,返回-EEXIST*/
    if (slot != NULL)
        return -EEXIST;

    if (node) {
        node->count++; /*增加子结点的计数*/
        rcu_assign_pointer(node->slots[offset], item);
        BUG_ON(tag_get(node, 0, offset));
        BUG_ON(tag_get(node, 1, offset));
    } else { /*为顶层结点*/
        rcu_assign_pointer(root->rnode, item);
        BUG_ON(root_tag_get(root, 0));
        BUG_ON(root_tag_get(root, 1));
    }

    return 0;
}


  • 扩展树的高度


如果当前树高度不足以存放index,就需要扩展树,扩展方法是在旧树顶上加新的根结点,并将原根结点的tag信息移到新根结点的第1个slot。函数radix_tree_extend 列出如下:
static int radix_tree_extend(struct radix_tree_root *root, unsigned long index)
{
    struct radix_tree_node *node;
    unsigned int height;
    int tag;

    /* 计算扩展的深度*/
    height = root->height + 1;
    /*如果index超过树高所容纳的最大索引值,树高递增*/
    while (index > radix_tree_maxindex(height)) 
        height++;

    /*到这里已计算好合适的高度height*/ 
    if (root->rnode == NULL) { /*只有根结点,设置好树高度就可返回*/
        root->height = height;
        goto out;
    }

    /*将当前树扩展到高度height*/
    do {
        unsigned int newheight;
        if (!(node = radix_tree_node_alloc(root))) /*分配一个结点*/
            return -ENOMEM;

        /* 增加树高,在树顶点之上增加一个结点*/
        node->slots[0] = radix_tree_indirect_to_ptr(root->rnode);

        /*传播tag信息到新根结点*/
        /*以前的根结点现成为新顶结点的第1个插槽。 如果以前的根结点打上了tag,就将新增结点的第1个插槽对应的子节点打上相应的tag*/
        for (tag = 0; tag < RADIX_TREE_MAX_TAGS; tag++) {
            if (root_tag_get(root, tag))
               tag_set(node, tag, 0);
        }

        newheight = root->height+1;
        node->height = newheight;
        node->count = 1;
        node = radix_tree_ptr_to_indirect(node);
        rcu_assign_pointer(root->rnode, node);
        root->height = newheight;
    } while (height > root->height);
out:
    return 0;
}


  • 删除条目


函数radix_tree_delete删除index对应的条目,并返回删除条目的地址。其列出如下:
void *radix_tree_delete(struct radix_tree_root *root, unsigned long index)
{
    /*数组path用于保存路径上的结点及索引偏移值*/
    struct radix_tree_path path[RADIX_TREE_MAX_PATH + 1], *pathp = path;
    struct radix_tree_node *slot = NULL;
    struct radix_tree_node *to_free;
    unsigned int height, shift;
    int tag;
    int offset;

    height = root->height;
    if (index > radix_tree_maxindex(height)) /*index不能超过树的最大索引值*/
        goto out;

    slot = root->rnode;
    if (height == 0) { /*只有根结点*/
        root_tag_clear_all(root);
        root->rnode = NULL;
        goto out;
    }
    slot = radix_tree_indirect_to_ptr(slot);

    shift = (height - 1) * RADIX_TREE_MAP_SHIFT;
    pathp->node = NULL;
    /*获取删除条目的路径*/
    /*将index从根到叶子的路径所经过的结点及相应索引偏移值存放在数组pathp中*/
    do {
        if (slot == NULL)
            goto out;

        pathp++;
        offset = (index >> shift) & RADIX_TREE_MAP_MASK;
        pathp->offset = offset;
        pathp->node = slot;
        slot = slot->slots[offset];
        shift -= RADIX_TREE_MAP_SHIFT;
        height--;
    } while (height > 0);

    if (slot == NULL)
        goto out;

    /*清除与删除条目相关的所有标签*/
    for (tag = 0; tag < RADIX_TREE_MAX_TAGS; tag++) {
        if (tag_get(pathp->node, tag, pathp->offset))
            radix_tree_tag_clear(root, index, tag);
    }

    to_free = NULL;
    /*释放不再需要的结点 */
    while (pathp->node) { /*删除
        pathp->node->slots[pathp->offset] = NULL; /*清除slot*/
        pathp->node->count--; /*孩子数量减1*/
        /*调用RCU机制的函数call_rcu在最后一个引用结束时延迟释放结点to_free */
        if (to_free)
            radix_tree_node_free(to_free);

        if (pathp->node->count) { /*还有孩子存在*/
           if (pathp->node == radix_tree_indirect_to_ptr(root->rnode)) /*为根结点的孩子*/
                radix_tree_shrink(root); /*树缩小*/
           goto out;
        }

        /*释放有0个slot的结点 */
        to_free = pathp->node;
        pathp--;

    }
    /*运行到这里,说明是根结点*/
    root_tag_clear_all(root);
    root->height = 0;
    root->rnode = NULL;
    if (to_free)
        radix_tree_node_free(to_free);

out:
    return slot;
}


  • 缩小树的高度


函数radix_tree_shrink缩小树的高度到最小。其列出如下:

static inline void radix_tree_shrink(struct radix_tree_root *root)
{
    /* 尝试缩小树的高度*/
    while (root->height > 0) {
        struct radix_tree_node *to_free = root->rnode;
        void *newptr;

        BUG_ON(!radix_tree_is_indirect_ptr(to_free));
        to_free = radix_tree_indirect_to_ptr(to_free);

        /*候选结点多于一个孩子或孩子不在最左边slot,不能缩小树的高度,跳出循环*/
        if (to_free->count != 1)
            break;
        if (!to_free->slots[0])
            break;

        /*不需要调用rcu_assign_pointer(),因为仅从树的一部分到另一部分移动结点。如果释放旧指针的引用to_free->slots[0]是安全的,那么释放新指针的引用root->rnode也是安全的*/ 
       newptr = to_free->slots[0];
       if (root->height > 1)
           newptr = radix_tree_ptr_to_indirect(newptr);
       root->rnode = newptr;
       root->height--; 
       /* 仅释放0结点*/
       tag_clear(to_free, 0, 0);
       tag_clear(to_free, 1, 0);
       to_free->slots[0] = NULL;
       to_free->count = 0;
       radix_tree_node_free(to_free);
    }
}


基数(radix)树


Linux基数树(radix tree)是将指针与long整数键值相关联的机制,它存储有效率,并且可快速查询,用于指针与整数值的映射(如:IDR机制)、内存管理等。
IDR(ID Radix)机制是将对象的身份鉴别号整数值ID与对象指针建立关联表,完成从ID与指针之间的相互转换。IDR机制使用radix树状结构作为由id进行索引获取指针的稀疏数组,通过使用位图可以快速分配新的ID,IDR机制避免了使用固定尺寸的数组存放指针。IDR机制的API函数在lib/idr.c中实现,这里不加分析。
Linux radix树最广泛的用途是用于内存管理,结构address_space通过radix树跟踪绑定到地址映射上的核心页,该radix树允许内存管理代码快速查找标识为dirty或writeback的页。Linux radix树的API函数在lib/radix-tree.c中实现。


(1)radix树概述


radix树是通用的字典类型数据结构,radix树又称为PAT位树(Patricia Trie or crit bit tree)。Linux内核使用了数据类型unsigned long的固定长度输入的版本。每级代表了输入空间固定位数。
radix tree是一种多叉搜索树,树的叶子结点是实际的数据条目。每个结点有一个固定的、2^n指针指向子结点(每个指针称为槽slot),并有一个指针指向父结点。

Linux内核利用radix树在文件内偏移快速定位文件缓存页,图4是一个radix树样例,该radix树的分叉为4(22),树高为4,树的每个叶子结点用来快速定位8位文件内偏移,可以定位4x4x4x4=256页,如:图中虚线对应的两个叶子结点的路径组成值0x00000010和0x11111010,指向文件内相应偏移所对应的缓存页。 


图4 一个四叉radix树


Linux radix树每个结点有64个slot,与数据类型long的位数相同,图1显示了一个有3级结点的radix树,每个数据条目(item)可用3个6位的键值(key)进行索引,键值从左到右分别代表第1~3层结点位置。没有孩子的结点在图中不出现。因此,radix树为稀疏树提供了有效的存储,代替固定尺寸数组提供了键值到指针的快速查找。 


图1 一个3级结点的radix树及其键值表示


(2)radix树slot数


Linux内核根用户配置将树的slot数定义为4或6,即每个结点有16或64个slot,如图2所示,当树高为1时,64个slot对应64个页,当树高为2时,对应64*64个页。 


图2 高为1和2、slot数为64的radix树


Linux内核radix树的slot数定义如下(在lib/radix-tree.c中):


#ifdef __KERNEL__
/*值为6时,表示每个结点有2^6=64个slot,值为4时,表示有2^4=16个slot*/
#define RADIX_TREE_MAP_SHIFT (CONFIG_BASE_SMALL ? 4 : 6)
#else
#define RADIX_TREE_MAP_SHIFT 3 /* 用于更有强度的测试 */
#endif
/*表示1个叶子结点可映射的页数,如:1<<6=64,表示可映射64个slot映射64页*/
#define RADIX_TREE_MAP_SIZE (1UL << RADIX_TREE_MAP_SHIFT)
#define RADIX_TREE_MAP_MASK (RADIX_TREE_MAP_SIZE-1)
/*定义slot数占用的long类型长度个数,每个slot用位图1位进行标记,如:64个slot时,值为2*/
#define RADIX_TREE_TAG_LONGS \
((RADIX_TREE_MAP_SIZE + BITS_PER_LONG - 1) / BITS_PER_LONG)


(3)结点结构


树的根结点结构radix_tree_root列出如下(在include/linux/radix-tree.h中):
#define RADIX_TREE_MAX_TAGS 2 /*每个slot需要的最大标签位数*/

/*根结点的标签存放在gfp_mask中,通过__GFP_BITS_SHIFT移位得到 */
struct radix_tree_root {
    unsigned int height; /* 从叶子向上计算的树高度 */
    gfp_t gfp_mask;
    /*间接指针指向结点而非数据条目,通过设置root->rnode的低位表示是否是间指针*/
    struct radix_tree_node *rnode;
#ifdef CONFIG_RADIX_TREE_CONCURRENT
    struct radix_tree_context *context;
    struct task_struct *owner;
#endif
};


结构radix_tree_context列出如下:
struct radix_tree_context {
    struct radix_tree_root *root;
#ifdef CONFIG_RADIX_TREE_CONCURRENT
    spinlock_t *locked;
#endif
}


树的结点结构radix_tree_node列出如下(在lib/radix-tree.c中):

struct radix_tree_node {
    unsigned int height; /* 从叶子向上计算的树高度 */
    unsigned int count; /*非叶子结点含有一个count域,表示出现在该结点的孩子的数量*/

    struct rcu_head rcu_head;
    void *slots[RADIX_TREE_MAP_SIZE]; /*结点的slot指针*/
   /* 结点标签数组=每个slot需要的最大标签位数*slot数所需的long类型变量数 */
   unsigned long tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONGS];
};


Linux内核radix树高度是可变的,它依赖于存储的索引键值。radix树查询必须知道树高度,以便知道结点的slot是指向叶子结点的指针还是结点的其他层。Linux在结点结构中有成员height存入树高度信息。
Linux radix树的独特特征是标签扩展。图2显示了有2个标签,分别表示打开和关闭。这些标签是结点结构的成员tags,该标签用一个位图实现,在加锁的情况下设置和清除。每个标签基本上是一个在radix树顶层上的位图序号。标签与群查询一起用来在给定的范围内查找给定标签集的页。
标签在每结点位图中维护,在每个给定的级别上,可以判定下一个级别是否至少有一个标签集。

 
图2 带有2位图标签指示的8位radix树


在结点结构radix_tree_node中,tags域是一个二维数组,每个slot用2位标识,这是一个典型的用空间换时间的应用。tags域用于记录该结点下面的子结点有没有相应的标志位。目前RADIX_TREE_MAX_TAGS为2,表示只记录两个标志,其中tags[0]为PAGE_CACHE_DIRTY,tags[1]为PAGE_CACHE_WRITEBACK。如果当前节点的tags[0]值为1,那么它的子树节点就存在PAGE_CACHE_DIRTY节点,否则这个子树分枝就不存在着这样的节点,就不必再查找这个子树了。比如在查找PG_dirty的页面时,就不需要遍历整个树,而可以跳过那些tags[0]为0值的子树,这样就提高了查找效率。
“加标签查询”是返回有指定标签集radix树条目的查询,可以查询设置了标签的条目。加标签查询可以并行执行,但它们需要通过设置或清除标签从操作上互斥。


(4) 并行操作的优化


Linux radix树并行操作包括并行查询和并行修改,其中,并行修改在标准内核中未完没有实现,需要通过打补丁获得该功能。并行操作说明如下:


  • RCU并发查询


通过使用RCU,RCU Radix树可以进行完全并发的查询操作。RCU从根本上要求原子操作地移动指针从数据结构的一个版本到新的版本,保持旧版本直到系统经过静止状态。在静止状态点,旧版本数据结构已没有用户,因此可以被安全释放。
RCU radix树的修改操作之间还需要串行化,但是查询不再需要与修改操作串行化。


  • 并发修改


RCU可使RCU radix树查询完全并行化,但修改操作成了“瓶颈”。这可通过将全树的锁破碎成较小的锁进行改善,再明显的方法是对结点进行加锁而非对整个树加锁。
radix树修改操作可分为单向和双向操作。单向操作仅执行从根节点和叶子结点的单方向指针移动,它包括插入、更新和设置标签操作。双向操作较复杂,它需要在指针移到叶子后又回移,它包括删除和清除标签操作。
梯级加锁(Ladder Locking)和锁耦合(Lock-Coupling)技术常用于数据库方面,允许单向遍历结点加锁的树(双向可能产生死锁)。如果所有的修改者从树顶到树底进行修改,并且修改的结点持有锁,那么,向下遍历时对孩子加锁,在孩子被锁住时再释放该结点锁。在这种情况下并发操作是可能的,因为只要根结点解锁,另一个操作就可以自上向下进行。如果两操作的路径没有相同操作结点,后一个操作可能在前一个操作完成之前完成。最坏的情况是流水线操作,但这还是比串行化操作好很多。


双向操作包括删除和清除标签操作,分别说明如下:


1)清除标签


在radix树中清除一个标签包括向下遍历树、查找定位条目和清除条目标签的操作。只要孩子结点没有打标签的条目,就可以向上遍历结点清除标签。结束条件是:如果遍历遇到一个结点,在清除一个标签后,它还有一个或多个条目带有标签集,就可以结束向上遍历。为了与向下遍历期间有同样的结束点,将终止条件改为:向上遍历将在有比清除标签数更多标签的结点处结束。这样,不论何时遇到这样的结点,将作为上遍历树的结束点。


2)删除元素


删除元素在删除无用结点时还需要删除该条目的所有标签。它的终止条件需要满足这两个方面。向上回退遍历树时需要满足下面的条件:当遇到一个非空结点且没有无用的标签时应终止向上回退遍历树。
在向下遍历树时鉴别此点的条件是:当遇到有超过2个孩子的结点、并且每个标签来说结点有多于一个标签条目被清除时,结束向上遍历。该条件用来鉴别向上回退遍历的终止点。


(5)radix树API说明


  • 声明和初始化radix树

声明和初始化radix树的方法列出如下:


#include <linux/radix-tree.h>
/* gfp_mask表示如何执行内存分配,如果操作(如:插入)以原子性上下文中执行,其值为GFP_ATOMIC*/
RADIX_TREE(name, gfp_mask); /* 声明和初始化名为name的树*/

struct radix_tree_root my_tree;
INIT_RADIX_TREE(my_tree, gfp_mask);


  • 插入条目


插入条目的函数定义列出如下:

int radix_tree_insert(struct radix_tree_root *root, unsigned long index, void *item)


函数radix_tree_insert插入条目item到树root中,如果插入条目中内存分配错误,将返回错误-ENOMEM。该函数不能覆盖写正存在的条目。如果索引键值index已存在于树中,返回错误-EEXIST。插入操作成功是,返回0。
对于插入条目操作失败将引起严重问题的场合,下面的一对函数可避免插入操作失败:

int radix_tree_preload(gfp_t gfp_mask);
void radix_tree_preload_end(void);


函数radix_tree_preload尝试用给定的gfp_mask分配足够的内存,保证下一个插入操作不会失败。在调用插入操作函数之前调用此函数,分配的结构将存放在每CPU变量中。函数radix_tree_preload操作成功后,将完毕内核抢占。因此,在插入操作完成之后,用户应调用函数radix_tree_preload_end打开内核抢占。


  • 删除条目


删除条目的函数定义列出如下:

void *radix_tree_delete(struct radix_tree_root *root, unsigned long index)


函数radix_tree_delete删除与索引键值index相关的条目,如果删除条目在树中,返回该条目的指针,否则返回NULL。


  • 查询操作


用于查询操作的函数定义列出如下:

/*在树中查找指定键值的条目,查找成功,返回该条目的指针,否则,返回NULL*/
void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index);
/*返回指向slot的指针,该slot含有指向查找到条目的指针*/
void **radix_tree_lookup_slot(struct radix_tree_root *root, unsigned long index);
/*查询返回max_items条目在results中。查询时键值索引从first_index开始*/
radix_tree_gang_lookup(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items);


  • 标签操作


与标签操作相关的函数说明列出如下:

/*将键值index对应的条目设置标签tag,返回值为设置标签的条目*/
void *radix_tree_tag_set(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*从键值index对应的条目清除标签tag,返回值为清除标签的条目*/
void *radix_tree_tag_clear(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*检查键值index对应的条目是否为标签tag,如果键值不存在,返回0,如果键值存在,但标签未设置,返回-1;如果键值存在,且标签已设置,返回1*/
int radix_tree_tag_get(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*从first_index起查询树root中标签值为tag的条目,在results中返回*/
unsigned int radix_tree_gang_lookup_tag(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items, unsigned int tag);
/*如果树root中有任何条目使用tag标签,返回键值*/
int radix_tree_tagged(struct radix_tree_root *root, unsigned int tag);


(6)并行操作使用radix树API的方法


  • 查询获取slot操作


查询操作支持RCU无阻塞并行读操作,因此,需要遵循RCU的用法加RCU读锁,还需要将rcu_dereference()用于获得的slot,在写(或更新)操作时,需要给新的slot使用rcu_assign_pointer()。查询操作的使用方法列出如下:

struct page **slot, *page;
rcu_read_lock();
slot = radix_tree_lookup_slot(&mapping->page_tree, index);
page = rcu_dereference(*slot);
rcu_read_unlock();


  • 查询修改slot操作


Linux内核的radix树需要打补丁才支持并发修改。查询仅有一个全局状态:RCU静止状态,并发修改需要跟踪持有什么锁。锁状态对于操作来说必须是外部的,因此,我们需要实例化一个本地上下文跟踪这些锁。查询修改slot的方法列出如下:

struct page **slot;
DEFINE_RADIX_TREE_CONTEXT(ctx,&mapping->page_tree);
radix_tree_lock(&ctx); /*锁住了根结点*/
/* ctx.tree代替&mapping->page_tree作为根,可以传递上下文
slot = radix_tree_lookup_slot(tx.tree, index);
rcu_assign_pointer(*slot, new_page);
radix_tree_unlock(&ctx);


radix树API函数radix_tree_lookup_slot含有锁从树顶向下移动机制,锁移动的代码部分列出如下:

void **radix_tree_lookup_slot(struct radix_tree *root, unsigned long index)
{
    ...
    RADIX_TREE_CONTEXT(context, root); /*提供上下文和实际的root指针*、
    ...
    do {
        ...
        /* 从树顶向下移动锁*/
        radix_ladder_lock(context, node);
        ...
    } while (height > 0);
    ...
}


(7)radix树API的实现


树的操作通常包括查找、插入、删除和树调整,下面分别说明radix树这些操作的实现。


  • 查找单个条目


radix树通过索引键值进行查询,如图1所示,它按路径组成索引键值,图中3级结点对应3段6位表示的索引值,图中key对应的叶子slot的索引键值为“2,63,1”。通过叶子slot的指针slot[1]就可得到所存储的数据item。因此,查询迭代时,需要将索引键值“2,63,1”移去高2层“2,63”,得到叶子slot的指针索引键值“1”。

 
图1 一个3级结点的radix树及其键值表示


函数radix_tree_lookup执行查找操作,查找方法是:从叶子到树顶,通过数组索引键值值查看数组元素的方法,一层层地查找slot。其列出如下(在lib/radix-tree.c中):

void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index)
{
    unsigned int height, shift;
    struct radix_tree_node *node, **slot;

    node = rcu_dereference(root->rnode); /*获取根结点*/
    if (node == NULL)
        return NULL;


    /*间接指针指向结点而非数据条目,通过设置root->rnode的低位表示是否是间指针。对于间接指针来说,树高度值root->height大于0,但是RCU查找需要测试间接指针,因为root->height 值不可靠。这种问题仅的RCU下需要考虑*/ 
    if (!radix_tree_is_indirect_ptr(node)) { /*非间接指针,说明只有根结点*/
        if (index > 0)
            return NULL;
        return node;
    }


    /*获取真正结点指针,因为根结点指针的第0位设置为1表示为间接指针。当使用结点指针时,必须将第0位设置为0,因为地址以字对齐*/
    node = radix_tree_indirect_to_ptr(node);

    height = node->height;
    if (index > radix_tree_maxindex(height)) /*索引键值不能超过最大索引值*/
        return NULL;


    /*每层索引偏移值为RADIX_TREE_MAP_SHIFT,叶子索引值偏移基数为(树高-1)*每层索引偏移值*/ 
    shift = (height-1) * RADIX_TREE_MAP_SHIFT;

    do { /*从叶子到树顶,通过树路径组成的索引查找指定索引键值的slot*/
        slot = (struct radix_tree_node **)(node->slots + ((index>>shift) & RADIX_TREE_MAP_MASK)); /*如:slots +1*/
        node = rcu_dereference(*slot);
        if (node == NULL)
            return NULL;

        shift -= RADIX_TREE_MAP_SHIFT; /*向上移一层,再迭代查找*/
        height--;
    } while (height > 0);

   return node;
}


  • 查找多个条目


函数radix_tree_gang_lookup执行多个索引键值的查找,其列出如下:

unsigned int radix_tree_gang_lookup(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items)
{
    unsigned long max_index;
    struct radix_tree_node *node;
    unsigned long cur_index = first_index;
    unsigned int ret;

    node = rcu_dereference(root->rnode);
    if (!node)
        return 0;

    if (!radix_tree_is_indirect_ptr(node)) { /*如果为非间接指针,表示只有根节点*/
        if (first_index > 0)
            return 0;
        results[0] = node;
        return 1;
    }
    node = radix_tree_indirect_to_ptr(node); /*清除用于间接指针标识的第0位*/

    max_index = radix_tree_maxindex(node->height); /*获取树的最大索引键值*/

    ret = 0;
    while (ret < max_items) { /* max_items为查找的最大条目数*/
        unsigned int nr_found;
        unsigned long next_index; /* 下一个搜索的索引键值*/

        if (cur_index > max_index) /*已查询完所需查询的索引键值*/
            break;
        nr_found = __lookup(node, results + ret, cur_index,
        max_items - ret, &next_index);
        ret += nr_found;
        if (next_index == 0)
            break;
        cur_index = next_index;
    }

    return ret;
}


static unsigned int
__lookup(struct radix_tree_node *slot, void **results, unsigned long index, unsigned int max_items, unsigned long *next_index)
{
    unsigned int nr_found = 0;
    unsigned int shift, height;
    unsigned long i;

    height = slot->height;
    if (height == 0)
        goto out;
    /*所有叶子slot的索引键值基数偏移*/
    shift = (height-1) * RADIX_TREE_MAP_SHIFT; 

    /*从底层向树顶层,
    for ( ; height > 1; height--) { /*从叶子向树顶查找*/
        i = (index >> shift) & RADIX_TREE_MAP_MASK;
        for (;;) { /*遍历每一层的各个路径,由树顶到当前层一条路径组成索引键值*/
            /*如果slot不为空,那么它挂有子结点,跳出本循环,进入子结点层进行本循环*/
            if (slot->slots[i] != NULL)
                break;
           /*如果slot为空,就跳过slot对应的所有索引键值*/ 
           /*清除索引号低位.将索引号与该层slot的起始索引号对齐*/
           index &= ~((1UL << shift) - 1);
           /*跳过一个slot的索引键值数*/
          index += 1UL << shift;
          if (index == 0)
              goto out; /* 32-bit wraparound */
          i++; /*找到多个slot*/
          if (i == RADIX_TREE_MAP_SIZE)
              goto out;
       }

       shift -= RADIX_TREE_MAP_SHIFT; /*向上移一层,基数偏移减少*/
       slot = rcu_dereference(slot->slots[i]);
       if (slot == NULL)
           goto out;

    }

    /* 返回找到的多个slot*/
    for (i = index & RADIX_TREE_MAP_MASK; i < RADIX_TREE_MAP_SIZE; i++) {
        struct radix_tree_node *node;
        index++;
        node = slot->slots[i];
        if (node) {
            results[nr_found++] = rcu_dereference(node);
           if (nr_found == max_items)
               goto out;
        }
    }
    out:
    *next_index = index;
    return nr_found;
}


  • 插入条目


函数radix_tree_insert找到索引键值对应的结点,将item加到该结点的slot指针上。其列出如下:

int radix_tree_insert(struct radix_tree_root *root, unsigned long index, void *item)
{
    struct radix_tree_node *node = NULL, *slot;
    unsigned int height, shift;
    int offset;
    int error;

    BUG_ON(radix_tree_is_indirect_ptr(item));

    /* 如果树的高度不够,就扩展树。函数radix_tree_maxindex计算树高容纳的最大索引*/
    if (index > radix_tree_maxindex(root->height)) {
        error = radix_tree_extend(root, index);
        if (error)
            return error;
    }

    slot = radix_tree_indirect_to_ptr(root->rnode);

    height = root->height;
    shift = (height-1) * RADIX_TREE_MAP_SHIFT; /*计算偏移基数*/

    offset = 0; 
    while (height > 0) {
        if (slot == NULL) { /*如果slot为空,则需要加入孩子结点*/
            /* 分配slot */
            if (!(slot = radix_tree_node_alloc(root)))
                return -ENOMEM;
            slot->height = height;
            if (node) {/*添加slot*/
                rcu_assign_pointer(node->slots[offset], slot);
             node->count++;
         } else
             rcu_assign_pointer(root->rnode,radix_tree_ptr_to_indirect(slot));
        }

        /* 进入上一层*/
        offset = (index >> shift) & RADIX_TREE_MAP_MASK;
        node = slot;
        slot = node->slots[offset];
        shift -= RADIX_TREE_MAP_SHIFT;
        height--;
    }
    /*如果index对应的slot已有映射页面,返回-EEXIST*/
    if (slot != NULL)
        return -EEXIST;

    if (node) {
        node->count++; /*增加子结点的计数*/
        rcu_assign_pointer(node->slots[offset], item);
        BUG_ON(tag_get(node, 0, offset));
        BUG_ON(tag_get(node, 1, offset));
    } else { /*为顶层结点*/
        rcu_assign_pointer(root->rnode, item);
        BUG_ON(root_tag_get(root, 0));
        BUG_ON(root_tag_get(root, 1));
    }

    return 0;
}


  • 扩展树的高度


如果当前树高度不足以存放index,就需要扩展树,扩展方法是在旧树顶上加新的根结点,并将原根结点的tag信息移到新根结点的第1个slot。函数radix_tree_extend 列出如下:
static int radix_tree_extend(struct radix_tree_root *root, unsigned long index)
{
    struct radix_tree_node *node;
    unsigned int height;
    int tag;

    /* 计算扩展的深度*/
    height = root->height + 1;
    /*如果index超过树高所容纳的最大索引值,树高递增*/
    while (index > radix_tree_maxindex(height)) 
        height++;

    /*到这里已计算好合适的高度height*/ 
    if (root->rnode == NULL) { /*只有根结点,设置好树高度就可返回*/
        root->height = height;
        goto out;
    }

    /*将当前树扩展到高度height*/
    do {
        unsigned int newheight;
        if (!(node = radix_tree_node_alloc(root))) /*分配一个结点*/
            return -ENOMEM;

        /* 增加树高,在树顶点之上增加一个结点*/
        node->slots[0] = radix_tree_indirect_to_ptr(root->rnode);

        /*传播tag信息到新根结点*/
        /*以前的根结点现成为新顶结点的第1个插槽。 如果以前的根结点打上了tag,就将新增结点的第1个插槽对应的子节点打上相应的tag*/
        for (tag = 0; tag < RADIX_TREE_MAX_TAGS; tag++) {
            if (root_tag_get(root, tag))
               tag_set(node, tag, 0);
        }

        newheight = root->height+1;
        node->height = newheight;
        node->count = 1;
        node = radix_tree_ptr_to_indirect(node);
        rcu_assign_pointer(root->rnode, node);
        root->height = newheight;
    } while (height > root->height);
out:
    return 0;
}


  • 删除条目


函数radix_tree_delete删除index对应的条目,并返回删除条目的地址。其列出如下:
void *radix_tree_delete(struct radix_tree_root *root, unsigned long index)
{
    /*数组path用于保存路径上的结点及索引偏移值*/
    struct radix_tree_path path[RADIX_TREE_MAX_PATH + 1], *pathp = path;
    struct radix_tree_node *slot = NULL;
    struct radix_tree_node *to_free;
    unsigned int height, shift;
    int tag;
    int offset;

    height = root->height;
    if (index > radix_tree_maxindex(height)) /*index不能超过树的最大索引值*/
        goto out;

    slot = root->rnode;
    if (height == 0) { /*只有根结点*/
        root_tag_clear_all(root);
        root->rnode = NULL;
        goto out;
    }
    slot = radix_tree_indirect_to_ptr(slot);

    shift = (height - 1) * RADIX_TREE_MAP_SHIFT;
    pathp->node = NULL;
    /*获取删除条目的路径*/
    /*将index从根到叶子的路径所经过的结点及相应索引偏移值存放在数组pathp中*/
    do {
        if (slot == NULL)
            goto out;

        pathp++;
        offset = (index >> shift) & RADIX_TREE_MAP_MASK;
        pathp->offset = offset;
        pathp->node = slot;
        slot = slot->slots[offset];
        shift -= RADIX_TREE_MAP_SHIFT;
        height--;
    } while (height > 0);

    if (slot == NULL)
        goto out;

    /*清除与删除条目相关的所有标签*/
    for (tag = 0; tag < RADIX_TREE_MAX_TAGS; tag++) {
        if (tag_get(pathp->node, tag, pathp->offset))
            radix_tree_tag_clear(root, index, tag);
    }

    to_free = NULL;
    /*释放不再需要的结点 */
    while (pathp->node) { /*删除
        pathp->node->slots[pathp->offset] = NULL; /*清除slot*/
        pathp->node->count--; /*孩子数量减1*/
        /*调用RCU机制的函数call_rcu在最后一个引用结束时延迟释放结点to_free */
        if (to_free)
            radix_tree_node_free(to_free);

        if (pathp->node->count) { /*还有孩子存在*/
           if (pathp->node == radix_tree_indirect_to_ptr(root->rnode)) /*为根结点的孩子*/
                radix_tree_shrink(root); /*树缩小*/
           goto out;
        }

        /*释放有0个slot的结点 */
        to_free = pathp->node;
        pathp--;

    }
    /*运行到这里,说明是根结点*/
    root_tag_clear_all(root);
    root->height = 0;
    root->rnode = NULL;
    if (to_free)
        radix_tree_node_free(to_free);

out:
    return slot;
}


  • 缩小树的高度


函数radix_tree_shrink缩小树的高度到最小。其列出如下:

static inline void radix_tree_shrink(struct radix_tree_root *root)
{
    /* 尝试缩小树的高度*/
    while (root->height > 0) {
        struct radix_tree_node *to_free = root->rnode;
        void *newptr;

        BUG_ON(!radix_tree_is_indirect_ptr(to_free));
        to_free = radix_tree_indirect_to_ptr(to_free);

        /*候选结点多于一个孩子或孩子不在最左边slot,不能缩小树的高度,跳出循环*/
        if (to_free->count != 1)
            break;
        if (!to_free->slots[0])
            break;

        /*不需要调用rcu_assign_pointer(),因为仅从树的一部分到另一部分移动结点。如果释放旧指针的引用to_free->slots[0]是安全的,那么释放新指针的引用root->rnode也是安全的*/ 
       newptr = to_free->slots[0];
       if (root->height > 1)
           newptr = radix_tree_ptr_to_indirect(newptr);
       root->rnode = newptr;
       root->height--; 
       /* 仅释放0结点*/
       tag_clear(to_free, 0, 0);
       tag_clear(to_free, 1, 0);
       to_free->slots[0] = NULL;
       to_free->count = 0;
       radix_tree_node_free(to_free);
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
idr下载csdn是指使用idr下载工具来下载csdn的资源。CSDN是中国最大的IT技术社区和全球最大的中文IT社区,拥有海量的IT技术文章和资源。而idr下载工具是一种网络资源下载工具,可以帮助用户快速、高效地下载各种网络资源。 使用idr下载csdn可以带来以下几个好处。首先,idr下载工具可以提供更快的下载速度。由于CSDN是一个庞大的IT社区,网站上的资源非常多,常常会出现下载缓慢或者下载中断的情况。而idr下载工具可以通过多线程下载的方式,充分利用网络带宽,提高下载速度,节省用户的时间。其次,idr下载工具支持断点续传功能。如果下载过程中出现网络中断或者其他原因导致下载失败,用户可以重新开始下载而无需重新从头开始,节省了用户的流量和时间成本。另外,idr下载工具还支持批量下载功能,可以同时下载多个文件,提高工作效率。 要使用idr下载csdn,首先需要在电脑上安装idr下载工具。安装完成后,用户可以通过输入CSDN的下载链接,选择下载的文件类型和保存路径,然后启动下载任务。idr下载工具会自动检测资源大小并进行分段下载,完成后自动合并文件。用户可以随时暂停、取消或者重新开始下载任务。 总之,idr下载csdn是一种方便快捷的下载方式,可以帮助用户高效地获取CSDN平台上的各种资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值