Linux基本数据结构

在Linux内核中经常用到的数据结构有链表、队列、映射和二叉树。这里介绍的链表包括双向链表和哈希链表,队列是内核提供kfifo。映射是idr,所谓idr就是根据整数查询指针。

链表

双向链表

#include <linux/list.h>
struct list_head {
    struct list_head *next, *prev;
};

双向链表的结构只有两个指针,那么如何保存数据呢,和其他嵌入式数据结构一样,双向链表嵌入到其他数据结构中,可以根据二者在内存中的位置关系利用链表地址反向计算数据地址。计算的函数如下。

#define list_entry(ptr, type, member) container_of(ptr, type, member)
#define container_of(ptr, type, member)                         \
    (type *)((char *)(ptr) - (char *) &((type *)0)->member)

根据提供的链表指针ptr,包含的数据类型type,以及链表在type的成员名字member,就可以反向计算出type的地址。

生命周期

// 将链表初始化,也就是prev和next都指向自身
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \               // 静态定义一个双向链表
struct list_head name = LIST_HEAD_INIT(name)
// 和LIST_HEAD_INIT功能一样,这里传递的是指针,而前面传递的是对象
void INIT_LIST_HEAD(struct list_head *list);

基本操作

读者不妨思考一下下面这些函数为什么这么定义,以及如何实现这些函数。

// 为了简便,entry和head均表示struct list_head *
void list_add(entry, head);             // add entry after head
void list_add_tail(entry, head);        // add entry before head
void list_del(entry);                   // delete entry
void list_del_init(entry);              // delete and init entry
void list_replace(old_entry, new_entry);
void list_replace_init(old_entry, new_entry); // init old_entry
void list_move(entry, head);            // move entry after head
void list_move_tail(entry, head);       // move entry before head
bool list_empty(head);                  // check if empty by next
int list_empty_careful(head);           // check if empty by next & prev
int list_is_last(entry, head);          // check if last entry
int list_is_singular(head);             // check if only on entry
void list_rotate_left(head);            // move first to last

高级操作

// 为了简便,entry和head均表示struct list_head *
void list_cut_position(list, head, entry); // list = [head, entry]
                                           // head = [entry->next, tail]
void list_splice(list, head);           // insert list after head
                                        // list self is head, so not insert
void list_splice_tail(list, head);      // insert list before head
void list_splice_init(list, head);      // insert after head, init list
void list_splice_tail_init(list, head); // insert before head, init list

链表遍历

在使用双向链表的时候要遵循一个规则,那就是链表头不能嵌入到数据中,也就是说链表头和普通的链表节点是不一样的。所以不能对链表头调用list_entry(),而必须使用下面的宏。

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

另外比较常用的操作是迭代链表,主要可以用如下一些宏。

#define __container_of(ptr, sample, member)                     \
    (void *)container_of((ptr), typeof(*(sample)), member)

这个宏的特殊之处在于我们不是去指定链表保存的数据类型,而是直接传递数据指针,由typeof去帮我们提取数据类型。

#define list_for_each_entry(pos, head, member)                  \
    for (pos = __container_of((head)->next, pos, member);       \
         &pos->member != (head);                                \
         pos = __container_of(pos->member.next, pos, member))
#define list_for_each_entry_safe(pos, tmp, head, member);

pos是我们想要得到的数据指针,head是链表头,所以必须从next开始获取,结束条件是我们拿到了head的数据,显然这个数据是无效的。一旦我们有了pos,往后面迭代的时候就不需要使用head,而直接可以使用pos->member了。当然,如果我们在迭代过程中将当前节点删除,还不会有什么问题,但是一旦我们删除后有对当前节点初始化,那么迭代就会终止。更可怕的是如果我们将当前节点移动到了别的地方,那么就会产生致命错误,极可能系统崩溃。安全版本可以删除当前元素,因为已经用tmp记录了下一个元素的位置。

此外还可以进行反向迭代。

#define list_for_each_entry_reverse(pos, head, member);
#define list_for_each_entry_safe_reverse(pos, tmp, head, member);

哈希链表

哈希链表本质上还是双向链表,当next为NULL的时候就是链表尾部,但是又可以通过pprev来改变前一个节点。

#include <linux/list.h>
struct hlist_head {
    struct hlist_node *first;
};
struct hlist_node {
    struct hlist_node *next, **pprev;
};

哈希表的本质是hlist_head的一个数组,既然是数组,长度就是固定的。每当要向表中一个位置添加节点时,就将hlist_node加入到hlist_head所指定的链表中。这里和双向链表的设计思路完全一样,链表头不用来存放数据,仅仅作为重要的参照标志。为了节省空间,链表头只包含一个指针。

这里出现了一个pprev指针,它指向上一个hlist_node的next指针的地址,如果前一个节点是hlist_head,那么就是hlist_head的first的地址。之所以要用这么一个指针,而不是是像链表一样直接用prev,是存在技术上的困难,因为prev可能是hlist_head也可以是hlist_node,但是如果将其含义修改为上一节点的next指针地址,就可以用统一的hlist_node二级指针表示。

生命周期

#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
#define HLIST_HEAD_INIT { .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
static inline void INIT_HLIST_NODE(struct hlist_node *h)
{
    h->next = NULL;
    h->pprev = NULL;
}

基本操作

// 为了简便,node表示struct hlist_node *,head表示struct hlist_head *
#define hlist_entry(ptr, type, member); // ref list_entry()
int hlist_unhashed(node);               // check if inserted hash table
int hlist_empty(head);                  // check if empty
void hlist_del(node);                   // delete node
void hlist_del_init(node);              // delete and init node
void hlist_add_head(node, head);        // insert to head->first
void hlist_add_before(node, pos);       // insert node before pos
void hlist_add_behind(node, pos);       // insert node behind pos
void hlist_move_list(head, new_head);   // move list to new_head
                                        // head->first will be NULL

哈希链表的遍历和双向链表工作原理一样,不过实现上要复杂一点,并且没有反向迭代的版本。

#define hlist_for_each_entry(pos, head, member);
#define hlist_for_each_entry_safe(pos, tmp, head, member);

队列 - kfifo

FIFO就是先进先出的意思,一般用队列表示,Linux Kernel实现了一个通用的FIFO,称之为KFIFO。

本文参照linux-kernel-3.19,由于代码相对于老的接口有一些变动,所以对于用户来说需要作出如下一些更变。

  • 将类型声明由struct kfifo *变为struct kfifo
  • 使用kfifo_alloc()kfifo_init()初始化
  • kfifo_in/kfifo_out替代__kfifo_put/__kfifo_get表示免锁算法
  • kfifo_in_spinlocked/kfifo_out_spinlocked替代kfifo_put/kfifo_get表示要加锁的算法
  • __kfifo_*函数被更名为kfifo_*

如果只有一个写入者,一个读取者,是不需要锁的。对于多个写入者,一个读取者,只需要对写入者上锁。反之,如果有多个读取者,一个写入者,只需要对读取者上锁。

另外在实现上用到了比较特殊的技巧,内核实现了两类KFIFO,一种就是struct kfifo,也就是动态KFIFO,data段动态分配,这里称之为动态KFIFO。一种是匿名KFIFO,buf段长度不为0,data段指向buf,静态分配,这里称之为静态KFIFO。这也是为什么几乎所有的方法都用宏定义,这样可以避免类型检查。

基本用法

一般用法是在结构体中嵌入一个kfifo结构,可以是静态KFIFO也可以是动态KFIFO。

// all fifo is a pointer
int kfifo_alloc(fifo, unsigned int size, gfp_t gfp_mask);
void kfifo_free(fifo);
unsigned int kfifo_size(fifo);          // mask + 1
unsigned int kfifo_len(fifo);           // in - out
int kfifo_is_empty(fifo);               // in == out
int kfifo_is_full(fifo);                // len > mask
unsigned int kfifo_avail(fifo);
unsigned int kfifo_reset(fifo);         // in = out = 0
void kfifo_reset_out(fifo);             // out = in
unsigned int kfifo_unused(fifo);        // len - (in - out)
unsigned int kfifo_in(fifo, void *buf, unsigned int num);
unsigned int kfifo_out(fifo, void *buf, unsigned int num);

源码分析

数据结构

#include <linux/kfifo.h>
struct __kfifo {
    unsigned int    in;                 // point to head
    unsigned int    out;                // point to tail
    unsigned int    mask;               // FIFO total size
    unsigned int    esize;              // element size
    void            *data;              // data buffer
};

环形队列如下图所示,读取者将out向右推,写入者将in向右推。

ringbuffer.png

但是到目前为止,还没有看到struct kfifo的定义,实际上它是由一组宏来定义的。

#define __STRUCT_KFIFO_COMMON(datatype, recsize, ptrtype)       \
    union {                                                     \
        struct __kfifo  kfifo;                                  \
        datatype        *type;                                  \
        const datatype  *const_type;                            \
        char            (*rectype)[recsize];                    \
        ptrtype         *ptr;                                   \
        ptrtype const   *ptr_const;                             \
    }
#define __STRUCT_KFIFO_PTR(type, recsize, ptrtype)      \
    {                                                   \
        __STRUCT_KFIFO_COMMON(type, recsize, ptrtype);  \
        type            buf[0];                         \
    }
struct kfifo __STRUCT_KFIFO_PTR(unsigned char, 0, void);
type
实际就是队列元素的类型,kfifo以unsigned char作为元素类型
recsize
全称叫record size,很显然kfifo的record size为0. 因此下文不再讨论recsize不为0的情况。

这段代码设计非常精巧,我们知道kfifo中并不包含元素数据类型,元素指针类型,那么如何利用kfifo来确定相关数据类型呢?共同体为我们保存了这些信息,例如我们要想知道fifo的元素类型,就可以通过typeof(*fifo->type)获取。也就是除了buf是直接用于存储数据以外,其它的字段是为了获取类型。也就是说共同体内部处理fifo之外,我们并不会去关注其它变量,即便用其它变量也不过是利用它们获取类型,而绝不会使用它们的值。

另外有个宏用于判断指针是否为kfifo。

#define __is_kfifo_ptr(fifo)    (sizeof(*fifo) == sizeof(struct __kfifo))

这个宏在很多地方都有用到,这也是设计者考虑问题比较复杂所致,如果是kfifo,就意味着fifo->buf数组长度为0,data部分单独分配。如果是其它fifo,fifo->buf长度不为0,data就会指向这部分。这里说其它fifo是指匿名结构体,它和kfifo唯一不同之处是buf的长度不为0。

静态定义

kfifo的实现中运用了大量的宏,并且比较复杂,因此先找一个比较简单的切入口,逐步分析。 DECLARE_KFIFO用于静态声明一个kfifo对象。

#define __STRUCT_KFIFO(type, size, recsize, ptrtype)                    \
    {                                                                   \
        __STRUCT_KFIFO_COMMON(type, recsize, ptrtype);                  \
        type buf[((size < 2) || (size & (size - 1))) ? -1 : size];      \
    }
#define STRUCT_KFIFO(type, size)                \
    struct __STRUCT_KFIFO(type, size, 0, type)
#define DECLARE_KFIFO(fifo, type, size) STRUCT_KFIFO(type, size) fifo
fifo
要定义的kfifo的名字
type
元素的类型
size
kfifo可容纳的元素个数,必须是2的幂
buf
用于存放元素,size & (size - 1)用于检测大小是否为2的幂

如果你仔细看就会发现DECLARE_KFIFO是一个匿名结构体,它和kfifo不是一回事!这也是为什么很多地方都要用到__is_kfifo_ptr的原因。

DECLARE_KFIFO只是声明一个变量,初始化用如下宏,注意看下面就应该知道,之所以用typeof而不是直接用struct kfifo *就是因为两者不是一回事。

#define INIT_KFIFO(fifo)                                                \
    (void)({                                                            \
            typeof(&(fifo)) __tmp = &(fifo);                            \
            struct __kfifo *__kfifo = &__tmp->kfifo;                    \
            __kfifo->in = 0;                                            \
            __kfifo->out = 0;                                           \
            __kfifo->mask = __is_kfifo_ptr(__tmp) ? 0 :                 \
                ARRAY_SIZE(__tmp->buf) - 1;                             \
            __kfifo->esize = sizeof(*__tmp->buf);                       \
            __kfifo->data = __is_kfifo_ptr(__tmp) ? NULL : __tmp->buf;  \
        })

动态分配

静态分配的情况其实比较少见,更多的是动态的分配。

1: #define kfifo_alloc(fifo, size, gfp_mask)                               \
2:     __kfifo_int_must_check_helper(({                                    \
3:         typeof((fifo) + 1) __tmp = (fifo);                              \
4:         struct __kfifo *__kfifo = &__tmp->kfifo;                        \
5:         __is_kfifo_ptr(__tmp) ?                                         \
6:             __kfifo_alloc(__kfifo, size, sizeof(*__tmp->type), gfp_mask) : \
7:             -EINVAL;                                                    \
8:     }))
4
typeof((fifo) + 1)这里为什么要加1呢,主要的好处是帮助确定传递的参数类型是否正确,如果传递的是结构体会产生编译错误。如果传递的是数组名,如int fifo[4]typeof(fifo)的结果为int [4],而使用typeof(fifo + 1)结果为int *。所以可以认为主要是为了转换数组类型为指针类型。
6
给fifo->data分配空间

入队

static void kfifo_copy_in(struct __kfifo *fifo, const void *src,
                          unsigned int len, unsigned int off)
{
    unsigned int size = fifo->mask + 1;
    unsigned int esize = fifo->esize;
    unsigned int l;

    off &= fifo->mask;
    if (esize != 1) {
        off *= esize; size *= esize; len *= esize;
    }
    l = min(len, size - off);

    memcpy(fifo->data + off, src, l);
    memcpy(fifo->data, src + l, len - l);
    smp_wmb(); // update data before increase fifo->in
}
unsigned int __kfifo_in(struct __kfifo *fifo,
                        const void *buf, unsigned int len)
{
    unsigned int l;

    l = kfifo_unused(fifo);
    if (len > l)
        len = l;

    kfifo_copy_in(fifo, buf, len, fifo->in);
    fifo->in += len;
    return len;
}
#define kfifo_in(fifo, buf, n)                          \
    ({                                                  \
        typeof((fifo) + 1) __tmp = (fifo);              \
        typeof(__tmp->ptr_const) __buf = (buf);         \
        unsigned long __n = (n);                        \
        struct __kfifo *__kfifo = &__tmp->kfifo;        \
        __kfifo_in(__kfifo, __buf, __n);                \
    })
#define kfifo_in_spinlocked(fifo, buf, n, lock) \
    ({                                          \
        unsigned long __flags;                  \
        unsigned int __ret;                     \
        spin_lock_irqsave(lock, __flags);       \
        __ret = kfifo_in(fifo, buf, n);         \
        spin_unlock_irqrestore(lock, __flags);  \
        __ret;                                  \
    })

出队

static void kfifo_copy_out(struct __kfifo *fifo, void *dst,
                           unsigned int len, unsigned int off)
{
    unsigned int size = fifo->mask + 1;
    unsigned int esize = fifo->esize;
    unsigned int l;

    off &= fifo->mask;
    if (esize != 1) {
        off *= esize; size *= esize; len *= esize;
    }
    l = min(len, size - off);

    memcpy(dst, fifo->data + off, l);
    memcpy(dst + l, fifo->data, len - l);
    smp_wmb(); // copy data before increase fifo->out
}
unsigned int __kfifo_out_peek(struct __kfifo *fifo,
                              void *buf, unsigned int len)
{
    unsigned int l;

    l = fifo->in - fifo->out;
    if (len > l)
        len = l;

    kfifo_copy_out(fifo, buf, len, fifo->out);
    return len;
}
unsigned int __kfifo_out(struct __kfifo *fifo,
                         void *buf, unsigned int len)
{
    len = __kfifo_out_peek(fifo, buf, len);
    fifo->out += len;
    return len;
}
#define kfifo_out(fifo, buf, n)                         \
    __kfifo_uint_must_check_helper(({                   \
        typeof((fifo) + 1) __tmp = (fifo);              \
        typeof(__tmp->ptr) __buf = (buf);               \
        unsigned long __n = (n);                        \
        struct __kfifo *__kfifo = &__tmp->kfifo;        \
        __kfifo_out(__kfifo, __buf, __n);               \
    }))

#define kfifo_out_spinlocked(fifo, buf, n, lock) \
    __kfifo_uint_must_check_helper(({            \
        unsigned long __flags;                   \
        unsigned int __ret;                      \
        spin_lock_irqsave(lock, __flags);        \
        __ret = kfifo_out(fifo, buf, n);         \
        spin_unlock_irqrestore(lock, __flags);   \
        __ret;                                   \
    }))

映射 - idr

在Linux中,IDR是一个Small id to pointer translation service,用于管理整数ID,将整数和指针映射。使用的时候首先为一个数据结构的指针分配一个整数ID,接下来通过ID可以快速查找对应的指针。

数组和链表也能用于这样的转换,但是数组不能用于查询范围很大的情况,链表的迭代效率很低,因此不能用于映射量很大的情况。某些情况下可以用hash表来替代IDR,但是IDR相比于hash表来说不必预分配一个很大的数组,并且最坏情况要比hash表好。平衡二叉树能更好的控制最坏情况,但是IDR处理的情况比较特殊,只需要管理整数和指针,所以可以实现出比平衡二叉树更优的算法,不论在存储上还是在查询上都表现更好。 IDR也是一种radix tree,每个节点有256个分支,通过一些技巧性的位运算可以得到很高的查询效率。

基本用法

创建与销毁

静态初始化接口如下所示。

#define IDR_INIT(name) {.lock = __SPIN_LOCK_UNLOCKED(name.lock),}
#define DEFINE_IDR(name) struct idr name = IDR_INIT(name)

动态初始化接口如下所示。

void idr_init(struct idr *idp)
{
    memset(idp, 0, sizeof(struct idr));
    spin_lock_init(&idp->lock);
}

释放接口如下:

void idr_remove(struct idr *idp, int id);
void idr_destroy(struct idr *idp);
idr_remove
删除id并释放相关数据。
idr_destroy
释放所有的id映射和层。

分配ID

void idr_preload(gfp_t gfp_mask);
int idr_alloc(struct idr *idp, void *ptr, int start, int end, gfp_t gfp_mask);
void idr_preload_end(void);
idr_preload
调用idr_alloc之前需要调用idr_preload,用于载入percpu层缓冲,并且只能在进程上下文使用。当然idr_preload还做了一件重要的事情就是禁止抢占。
idr_alloc
用于分配一个ID和指针关联,分配的值区间为[start, end)。当我们指定end的数值小于等于0的时候,默认就视为INT_MAX。
idr_preload_end
启用内核抢占。

基本上使用IDR的流程如下所示。

// alloc idr...
idr_init(idr);

idr_preload(GFP_KERNEL);
spin_lock(lock);

id = idr_alloc(idr, ptr, start, end, GFP_NOWAIT);

spin_unlock(lock);
idr_preload_end();

if (id < 0)
    error;

idr_remove(idr, id);
idr_destroy(idr);

查询迭代

static inline void *idr_find(struct idr *idp, int id);
void *idr_replace(struct idr *idp, void *ptr, int id);
int idr_for_each(struct idr *idp,
                 int (*fn)(int id, void *p, void *data),
                 void *data);
bool idr_is_empty(struct idr *idp);
idr_find
根据指定的ID查找指针。
idr_replace
相当于更新id对应的指针。
idr_for_each
fn函数的参数p将由迭代时的idr_layer指针代入, fn函数的data即是idr_for_each的最后一个参数,这里设计要求fn返回0表示成功,返回非0表示失败,最终idr_for_each如果全部执行成功返回0,否则就返回非0。
idr_is_empty
该函数设计基于idr_for_each实现,只要idr中有一个元素,那么迭代就会调用fn,我们只需要设计一个fn始终返回1的函数,并调用idr_for_each即可。因为idr_for_each()在调用fn时发现返回非0,就会停止迭代并返回该值。

源码分析

假设我们要查找0x515,0x515/0x256 = 2,表示位于第1层的编号2, 0x515%0x256 = 3,表示位于第0层的编号3,如下图所示。

idr-tree.png

数据结构

#define IDR_BITS 8
#define IDR_SIZE (1 << IDR_BITS)
#define IDR_MASK ((1 << IDR_BITS)-1)
struct idr_layer {
    int                     prefix;   // 前缀,即高位,用于判断大范围
    int                     layer;    // 深度,到叶节点距离
    struct idr_layer __rcu  *ary[1<<IDR_BITS]; // 指向子节点
    int                     count;    // 引用计数,子节点个数
    union {
        DECLARE_BITMAP(bitmap, IDR_SIZE); // ary使用情况
        struct rcu_head         rcu_head;
    };
};
struct idr {
    struct idr_layer __rcu  *hint;      // 最近使用的层
    struct idr_layer __rcu  *top;       // 树根,深度最大的层
    int                     layers;     // 树高,最大层号加1
    int                     cur;        // 用于循环分配的当前位置
    spinlock_t              lock;
    int                     id_free_cnt; // 空闲链表的idr_layer数
    struct idr_layer        *id_free;    // 空闲链表头
};

idr_alloc

 1: int idr_alloc(struct idr *idr, void *ptr, int start, int end, gfp_t gfp_mask)
 2: {
 3:     int max = end > 0 ? end - 1 : INT_MAX;
 4:     struct idr_layer *pa[MAX_IDR_LEVEL + 1];
 5:     int id;
 6: 
 7:     might_sleep_if(gfp_mask & __GFP_WAIT);
 8: 
 9:     if (WARN_ON_ONCE(start < 0))
10:         return -EINVAL;
11:     if (unlikely(max < start))
12:         return -ENOSPC;
13: 
14:     id = idr_get_empty_slot(idr, start, pa, gfp_mask, NULL);
15:     if (unlikely(id < 0))
16:         return id;
17:     if (unlikely(id > max))
18:         return -ENOSPC;
19: 
20:     idr_fill_slot(idr, ptr, id, pa);
21:     return id;
22: }
3
确定整数区间,注意范围是[start, end)
4
MAX_IDR_LEVEL具体有多大这里就不关心了,32位机上应该为4, \(2^{8\times4}\) 正好是32位机能表示的最大长度。
7
用于调试
14
分配ID,注意这里并没有指定最后一个参数layer_idr,因此不会调用get_from_free_list()从空闲链表分配
20
填充

idr_get_empty_slot

 1: static int idr_get_empty_slot(struct idr *idp, int starting_id,
 2:                               struct idr_layer **pa, gfp_t gfp_mask,
 3:                               struct idr *layer_idr)
 4: {
 5:     struct idr_layer *p, *new;
 6:     int layers, v, id;
 7:     unsigned long flags;
 8: 
 9:     id = starting_id;
10: build_up:
11:     p = idp->top;
12:     layers = idp->layers;
13:     if (unlikely(!p)) {
14:         if (!(p = idr_layer_alloc(gfp_mask, layer_idr)))
15:             return -ENOMEM;
16:         p->layer = 0;
17:         layers = 1;
18:     }
19: 
20:     while (id > idr_max(layers)) {
21:         layers++;
22:         if (!p->count) {
23:             p->layer++;
24:             WARN_ON_ONCE(p->prefix);
25:             continue;
26:         }
27:         if (!(new = idr_layer_alloc(gfp_mask, layer_idr))) {
28:             spin_lock_irqsave(&idp->lock, flags);
29:             for (new = p; p && p != idp->top; new = p) {
30:                 p = p->ary[0];
31:                 new->ary[0] = NULL;
32:                 new->count = 0;
33:                 bitmap_clear(new->bitmap, 0, IDR_SIZE);
34:                 __move_to_free_list(idp, new);
35:             }
36:             spin_unlock_irqrestore(&idp->lock, flags);
37:             return -ENOMEM;
38:         }
39:         new->ary[0] = p;
40:         new->count = 1;
41:         new->layer = layers-1;
42:         new->prefix = id & idr_layer_prefix_mask(new->layer);
43:         if (bitmap_full(p->bitmap, IDR_SIZE))
44:             __set_bit(0, new->bitmap);
45:         p = new;
46:     }
47:     rcu_assign_pointer(idp->top, p);
48:     idp->layers = layers;
49:     v = sub_alloc(idp, &id, pa, gfp_mask, layer_idr);
50:     if (v == -EAGAIN)
51:         goto build_up;
52:     return(v);
53: }
layer_idr
该参数表示从指定位置分配,也就是利用空闲链表。
13
如果没有根节点就会分配一个根节点层。
16-17
p->layer是索引,很显然层数始终比索引要大1,如果一开始一层都没有,那么首次分配的层号为0。
20
当id超过当前idr所能表示的最大值时,就需要分配新的层,也就是增加树的高度。计算idr_max()也比较简单。 \(256^{layers} - 1\) ,这里为什么要减1呢,因为idr_max表示的是最大编号,而不是可表示的编号个数。
23
如果当前层无人使用,我们可以直接增大当前层的深度。很显然只有当idr中一层都没有的时候才会出现此种情况,否则我们一定是从叶节点成长起来的,也就是说只有刚刚建立的top才有机会执行这样的高效算法。
27
分配一个新的层
28-38
处理分配出错的情况,如果分配失败, top指向的idr_layer要全部重新初始化,并移到idp的空闲链表中。
39-45
初始化新分配的层。
39
将new设为p的父节点,这也是前面讲idr是从叶节点长起来的原因。
40
既然是从儿子长起来,就必然有人引用,引用计数要设置为1。
41
最大层号始终比层数小1。
42
设置prefix,这也是一个精妙的算法, idr_layer_prefix_mask的算法很简单,就是~idr_max(layer + 1),注意这里的layer是层号,因此调用idr_max()时要加1,如第0层mask为~0xFF,第1层mask为~0xFFFF,依次类推。很显然,prefix就是该层所能表示的范围之外的部分,之所以和id求与,是为了减少不必要的分支。
43-44
这也是一个精妙的设计,如果子节点的位图已经填满,那么表示new->ary[0]下起始已经没有可用的空间了,因此需要把父节点的对应位图填1,如果我们从上往下搜索,发现某个节点bitmap对应位为1,就表示该位对应以下的所有节点都被用光,极大的节省搜索时间。
45
将p指向new,也就是新的top,如此循环下去,直到树的高度满足要求,就停止循环。
47
用p去替换top,只要理解新分配的作为父节点,就能理解这里为什么直接替换top了。
49
从top指向的idr_layer树中获取ID号,分配路径记录到pa中,这个函数不会去增加树的高度,但是会在必要的时候分配新的层, idr_get_empty_slot()是从下往上长,而sub_alloc()则是生长必要的枝叶。
50-51
这是Linux内核中比较常见的一种做法,这里特别定义了-EAGAIN的语义,首先要想一下为什么会出现这种情况,比较明显的一个例子是,树的高度虽然够了,能够足够表示starting_id,但是所有的ID已经被别人用光了,这种情况就不得不增加树的高度。 sub_alloc()返回-EAGAIN正是遇到了这种情况。

idr_layer_alloc

 1: static struct idr_layer *idr_layer_alloc(gfp_t gfp_mask, struct idr *layer_idr)
 2: {
 3:     struct idr_layer *new;
 4: 
 5:     if (layer_idr)
 6:         return get_from_free_list(layer_idr);
 7: 
 8:     new = kmem_cache_zalloc(idr_layer_cache, gfp_mask | __GFP_NOWARN);
 9:     if (new)
10:         return new;
11: 
12:     if (!in_interrupt()) {
13:         preempt_disable();
14:         new = __this_cpu_read(idr_preload_head);
15:         if (new) {
16:             __this_cpu_write(idr_preload_head, new->ary[0]);
17:             __this_cpu_dec(idr_preload_cnt);
18:             new->ary[0] = NULL;
19:         }
20:         preempt_enable();
21:         if (new)
22:             return new;
23:     }
24: 
25:     return kmem_cache_zalloc(idr_layer_cache, gfp_mask);
26: }
5
如果指定了layer_id,就从layer_id获取,也就是合理利用空闲链表。
8
从slab cache分配一个idr_layer,这个idr_layer_cache实际上是在start_kernel的时候创建的。正常情况下这里成功分配就应该返回了。
12
如果从slab分配失败,就尝试从PERCPU获取。
25
如果所有分配方式都失败了,就加上警告选项再从slab分配一次,如果这次失败,会体现出相应的错误信息。

sub_alloc

static int sub_alloc(struct idr *idp, int *starting_id, struct idr_layer **pa,
                     gfp_t gfp_mask, struct idr *layer_idr)
{
    int n, m, sh;
    struct idr_layer *p, *new;
    int l, id, oid;

    id = *starting_id;
restart:
    p = idp->top;
    l = idp->layers;
    pa[l--] = NULL;
    while (1) {
        n = (id >> (IDR_BITS*l)) & IDR_MASK;
        m = find_next_zero_bit(p->bitmap, IDR_SIZE, n);
        if (m == IDR_SIZE) {
            l++;
            oid = id;
            id = (id | ((1 << (IDR_BITS * l)) - 1)) + 1;

            if (id > idr_max(idp->layers)) {
                *starting_id = id;
                return -EAGAIN;
            }
            p = pa[l];
            BUG_ON(!p);

            sh = IDR_BITS * (l + 1);
            if (oid >> sh == id >> sh)
                continue;
            else
                goto restart;
        }
        if (m != n) {
            sh = IDR_BITS*l;
            id = ((id >> sh) ^ n ^ m) << sh;
        }
        if ((id >= MAX_IDR_BIT) || (id < 0))
            return -ENOSPC;
        if (l == 0)
            break;

        if (!p->ary[m]) {
            new = idr_layer_alloc(gfp_mask, layer_idr);
            if (!new)
                return -ENOMEM;
            new->layer = l-1;
            new->prefix = id & idr_layer_prefix_mask(new->layer);
            rcu_assign_pointer(p->ary[m], new);
            p->count++;
        }
        pa[l--] = p;
        p = p->ary[m];
    }

    pa[l] = p;
    return id;
}
12
将l减1,就得到对应的层号。
14
该行很好理解,就是id在l层对应的编号。
15
从n开始找位图中为0的位置,前面已经提到,如果某个位图为1,就表该位置以下所有节点被用光了。
16-32
处理特殊情况:该层大于starting_id的位置都被占用,已经没有空闲位置可用。
17
层加层编号,向更高层进军,因为当前层已经被用光了。
19
增加id编号,让其进入其兄长区间,或者父亲区间。
21-23
如果新的id超过当前idr所能表示的范围,就更新starting_id,并返回-EAGAIN,这样调用该函数的idr_get_empty_slot()就会去增加树高。
28-32
如果没有注释,恐怕没几个人能看得明白这是要干什么,这也是作者在这里秀技巧,如果相等,说明需要进入到上一层,这个时候只需要继续循环即可。如果不相等,说明不用进入上一层,这种情况需要重头开始。
34-36
这里也是在秀技巧,完全看不懂要干嘛。
38-41
检查有效性,如果l=0,那么就意味着已经抵达叶节点,可以退出循环。
43-53
如果ary[m]没有子节点,就需要分配一个层,并初始化。
52
保存当前层,减小层号。
53
进入子节点。
56
保存当前层。
57
返回实际找到的ID。

idr_fill_slot

static void idr_fill_slot(struct idr *idr, void *ptr, int id,
                          struct idr_layer **pa)
{
    rcu_assign_pointer(idr->hint, pa[0]);

    rcu_assign_pointer(pa[0]->ary[id & IDR_MASK], (struct idr_layer *)ptr);
    pa[0]->count++;
    idr_mark_full(pa, id);
}
hint
用于保存最近使用的层。
pa[0]->ary[id & IDR_MASK]
用于保存所指定的指针。
static void idr_mark_full(struct idr_layer **pa, int id)
{
    struct idr_layer *p = pa[0];
    int l = 0;

    __set_bit(id & IDR_MASK, p->bitmap);
    /*
     * If this layer is full mark the bit in the layer above to
     * show that this part of the radix tree is full.  This may
     * complete the layer above and require walking up the radix
     * tree.
     */
    while (bitmap_full(p->bitmap, IDR_SIZE)) {
        if (!(p = pa[++l]))
            break;
        id = id >> IDR_BITS;
        __set_bit((id & IDR_MASK), p->bitmap);
    }
}
idr_mark_full
向上追溯标记占用情况,也就是字节点占用满了就标记其父节点对应的位,如果父节点也全部占用满了就标记爷爷对应的位,依次类推。