Linux IDR机制【转】

转自:https://blog.csdn.net/av_geek/article/details/49640433

IDR机制在Linux内核中指的是整数ID管理机制。

实质上来讲,这就是一种将一个整数ID号和一个指针关联在一起的机制。

这个机制最早在03年2月加入内核,当时作为POSIX定时器的一个补丁。现在,内核中很多地方都可以找到它的身影。

 

IDR机制原理:

IDR机制适用在那些需要把某个整数和特定指针关联在一起的地方。例如,在IIC总线中,每个设备都有自己的地址,要想在总线上找到特定的设备,就必须要先发送设备的地址。当适配器要访问总线上的IIC设备时,首先要知道它们的ID号,同时要在内核中建立一个用于描述该设备的结构体,和驱动程序

将ID号和设备结构体结合起来,如果使用数组进行索引,一旦ID 号很大,则用数组索引会占据大量内存空间。这显然不可能。或者用链表,但是,如果总线中实际存在的设备很多,则链表的查询效率会很低。

此时,IDR机制应运而生。该机制内部采用红黑树实现,可以很方便的将整数和指针关联起来,并且具有很高的搜索效率

 

struct idr {
    struct idr_layer *top;
    struct idr_layer *id_free;
    int          layers; /* only valid without concurrent changes */
    int          id_free_cnt;
    spinlock_t      lock;
};

struct idr_layer {
    unsigned long         bitmap; /* A zero bit means "space here" */
    struct idr_layer    *ary[1<<IDR_BITS];
    int             count;     /* When zero, we can release it */
    int             layer;     /* distance from leaf */
    struct rcu_head         rcu_head;
};

 

宏定义并且初始化一个名为name的IDR:

#define DEFINE_IDR(name)    struct idr name = IDR_INIT(name)

#define IDR_INIT(name)                        \
{                                \
    .top        = NULL,                    \
    .id_free    = NULL,                    \
    .layers     = 0,                    \
    .id_free_cnt    = 0,                    \
    .lock        = __SPIN_LOCK_UNLOCKED(name.lock),    \
}

动态初始化IDR:

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

 

分配存放ID号的内存:

每次通过IDR获得ID号之前 ,需要为ID号先分配内存。分配内存的函数是idr_pre_get().成功返回1,失败放回0

第一个参数是指向IDR的指针,第二个参数是内存分配标志。

int idr_pre_get(struct idr *idp, gfp_t gfp_mask)
{
    while (idp->id_free_cnt < IDR_FREE_MAX) {
        struct idr_layer *new;
        new = kmem_cache_zalloc(idr_layer_cache, gfp_mask);
        if (new == NULL)
            return (0);
        move_to_free_list(idp, new);
    }
    return 1;
}

它调用了 :

static void move_to_free_list(struct idr *idp, struct idr_layer *p)
{
    unsigned long flags;

    /*
     * Depends on the return element being zeroed.
     */
    spin_lock_irqsave(&idp->lock, flags);
    __move_to_free_list(idp, p);
    spin_unlock_irqrestore(&idp->lock, flags);
}

static void __move_to_free_list(struct idr *idp, struct idr_layer *p)
{
    p->ary[0] = idp->id_free;
    idp->id_free = p;
    idp->id_free_cnt++;
}


分配ID号并将ID号和指针关联:

参数idp是之前,通过idr_init()初始化的idr指针,或者DEFINE_IDR宏定义的指针。参数ptr是和ID号相关联 的 指针。参数id由内核自动分配的ID号。参数start_id是起始ID号。

成功返回0,失败返回负值:

int idr_get_new(struct idr *idp, void *ptr, int *id)
{
    int rv;

    rv = idr_get_new_above_int(idp, ptr, 0);
    /*
     * This is a cheap hack until the IDR code can be fixed to
     * return proper error values.
     */
    if (rv < 0)
        return _idr_rc_to_errno(rv);
    *id = rv;
    return 0;
}

int idr_get_new_above(struct idr *idp, void *ptr, int starting_id, int *id)
{
    int rv;

    rv = idr_get_new_above_int(idp, ptr, starting_id);
    /*
     * This is a cheap hack until the IDR code can be fixed to
     * return proper error values.
     */
    if (rv < 0)
        return _idr_rc_to_errno(rv);
    *id = rv;
    return 0;
}

这两个函数唯一的区别是起始ID号不同:

它们都调用了:

static int idr_get_new_above_int(struct idr *idp, void *ptr, int starting_id)
{
    struct idr_layer *pa[MAX_LEVEL];
    int id;

    id = idr_get_empty_slot(idp, starting_id, pa);
    if (id >= 0) {
        /*
         * Successfully found an empty slot.  Install the user
         * pointer and mark the slot full.
         */
        rcu_assign_pointer(pa[0]->ary[id & IDR_MASK],
                (struct idr_layer *)ptr);
        pa[0]->count++;
        idr_mark_full(pa, id);
    }

    return id;
}

它调用了:

static int idr_get_empty_slot(struct idr *idp, int starting_id,
                  struct idr_layer **pa)
{
    struct idr_layer *p, *new;
    int layers, v, id;
    unsigned long flags;

    id = starting_id;
build_up:
    p = idp->top;
    layers = idp->layers;
    if (unlikely(!p)) {
        if (!(p = get_from_free_list(idp)))
            return -1;
        p->layer = 0;
        layers = 1;
    }
    /*
     * Add a new layer to the top of the tree if the requested
     * id is larger than the currently allocated space.
     */
    while ((layers < (MAX_LEVEL - 1)) && (id >= (1 << (layers*IDR_BITS)))) {
        layers++;
        if (!p->count) {
            /* special case: if the tree is currently empty,
             * then we grow the tree by moving the top node
             * upwards.
             */
            p->layer++;
            continue;
        }
        if (!(new = get_from_free_list(idp))) {
            /*
             * The allocation failed.  If we built part of
             * the structure tear it down.
             */
            spin_lock_irqsave(&idp->lock, flags);
            for (new = p; p && p != idp->top; new = p) {
                p = p->ary[0];
                new->ary[0] = NULL;
                new->bitmap = new->count = 0;
                __move_to_free_list(idp, new);
            }
            spin_unlock_irqrestore(&idp->lock, flags);
            return -1;
        }
        new->ary[0] = p;
        new->count = 1;
        new->layer = layers-1;
        if (p->bitmap == IDR_FULL)
            __set_bit(0, &new->bitmap);
        p = new;
    }
    rcu_assign_pointer(idp->top, p);
    idp->layers = layers;
    v = sub_alloc(idp, &id, pa);
    if (v == IDR_NEED_TO_GROW)
        goto build_up;
    return(v);
}

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 (p->bitmap == IDR_FULL) {
        if (!(p = pa[++l]))
            break;
        id = id >> IDR_BITS;
        __set_bit((id & IDR_MASK), &p->bitmap);
    }
}

idr_get_new还调用了:

#define _idr_rc_to_errno(rc) ((rc) == -1 ? -EAGAIN : -ENOSPC)                             //这是一个错误处理的宏

 

通过ID号查找对应的指针:

void *idr_find(struct idr *idp, int id)
{
    int n;
    struct idr_layer *p;

    p = rcu_dereference(idp->top);
    if (!p)
        return NULL;
    n = (p->layer+1) * IDR_BITS;

    /* Mask off upper bits we don't use for the search. */
    id &= MAX_ID_MASK;

    if (id >= (1 << n))
        return NULL;
    BUG_ON(n == 0);

    while (n > 0 && p) {
        n -= IDR_BITS;
        BUG_ON(n != p->layer*IDR_BITS);
        p = rcu_dereference(p->ary[(id >> n) & IDR_MASK]);
    }
    return((void *)p);
}

 

删除ID号:

void idr_remove(struct idr *idp, int id)
{
    struct idr_layer *p;
    struct idr_layer *to_free;

    /* Mask off upper bits we don't use for the search. */
    id &= MAX_ID_MASK;

    sub_remove(idp, (idp->layers - 1) * IDR_BITS, id);
    if (idp->top && idp->top->count == 1 && (idp->layers > 1) &&
        idp->top->ary[0]) {
        /*
         * Single child at leftmost slot: we can shrink the tree.
         * This level is not needed anymore since when layers are
         * inserted, they are inserted at the top of the existing
         * tree.
         */
        to_free = idp->top;
        p = idp->top->ary[0];
        rcu_assign_pointer(idp->top, p);
        --idp->layers;
        to_free->bitmap = to_free->count = 0;
        free_layer(to_free);
    }
    while (idp->id_free_cnt >= IDR_FREE_MAX) {
        p = get_from_free_list(idp);
        /*
         * Note: we don't call the rcu callback here, since the only
         * layers that fall into the freelist are those that have been
         * preallocated.
         */
        kmem_cache_free(idr_layer_cache, p);
    }
    return;
}

它调用了:

static void sub_remove(struct idr *idp, int shift, int id)
{
    struct idr_layer *p = idp->top;
    struct idr_layer **pa[MAX_LEVEL];
    struct idr_layer ***paa = &pa[0];
    struct idr_layer *to_free;
    int n;

    *paa = NULL;
    *++paa = &idp->top;

    while ((shift > 0) && p) {
        n = (id >> shift) & IDR_MASK;
        __clear_bit(n, &p->bitmap);
        *++paa = &p->ary[n];
        p = p->ary[n];
        shift -= IDR_BITS;
    }
    n = id & IDR_MASK;
    if (likely(p != NULL && test_bit(n, &p->bitmap))){
        __clear_bit(n, &p->bitmap);
        rcu_assign_pointer(p->ary[n], NULL);
        to_free = NULL;
        while(*paa && ! --((**paa)->count)){
            if (to_free)
                free_layer(to_free);
            to_free = **paa;
            **paa-- = NULL;
        }
        if (!*paa)
            idp->layers = 0;
        if (to_free)
            free_layer(to_free);
    } else
        idr_remove_warning(id);
}

 

static struct idr_layer *get_from_free_list(struct idr *idp)
{
    struct idr_layer *p;
    unsigned long flags;

    spin_lock_irqsave(&idp->lock, flags);
    if ((p = idp->id_free)) {
        idp->id_free = p->ary[0];
        idp->id_free_cnt--;
        p->ary[0] = NULL;
    }
    spin_unlock_irqrestore(&idp->lock, flags);
    return(p);
}

posted @ 2018-11-14 13:50  Sky&Zhang  阅读(865)  评论(0编辑  收藏  举报