一、映射概述
- 一个映射,也称为关联数组
- 是一个由唯一键组成的集合,而每个键必然关联一个特定的值。这种键到值的关联关系称为映射
散列表
散列表是实现映射的一种数据结构
自平衡二叉搜索树
- 虽然可以用散列表实现映射,但映射也可以通过自平衡二叉搜索树存储数据
- 虽然散列表能提供更好的平均的渐进复杂度,但是二叉搜索树在最坏情况下能有更好的表现(即对数复杂性相比线型复杂性)
- 二叉搜索树同时满足顺序保证,这给用户的按序遍历带来很好的性能
- 二叉搜索树的最后一个优势是它不需要散列函数,需要的键类型只要可以定义<=操作算子便可以
- 比如C++的STL容器std::map就是采用自平衡二叉搜索树实现的,它能提供按序遍历的能力
二、Linux内核映射的实现
- Linux内核提供了简单、有效的映射数据结构。但是它并非一个通用的映射。因为它的目标是 :映射一个唯一的标识数(UID)到一个指针
- 除了提供三个标准的映射操作外,Linux还在add操作基础上实现了allocate操作。这个allocate操作不但向map中加入了键值对,而且还可产生UID
- 映射的定义代码定义在<include/linux/idr.h>文件中,实现定义在<lib/idr.c>文件中
struct idr数据结构
- 该结构用于映射用户空间的UID,比如将inodify_watch的描述符或者POSIX的定时器ID映射到内核中相关联的数据结构上,如in otify_watch或者k_itimer结构体
- 其命名仍然沿袭了内核中有些含混不清的命名体系,这个映射被命名为idr
- 以下代码来自于Linux 2.6.22/include/linux/idr.h
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 */ }; struct idr { struct idr_layer *top; struct idr_layer *id_free; int layers; int id_free_cnt; spinlock_t lock; };
三、初始化一个idr
- 建立一个idr简单,首先你需要静态定义或者动态分配一个idr数据结构。然后调用idr_init();
idr_init()
/** * idr_init - initialize idr handle * @idp: idr handle * * This function is use to set up the handle (@idp) that you will pass * to the rest of the functions. */ void idr_init(struct idr *idp) { init_id_cache(); memset(idp, 0, sizeof(struct idr)); spin_lock_init(&idp->lock); } EXPORT_SYMBOL(idr_init);
演示案例
struct idr id_huh; //静态定义idr结构 idr_init(&id_huh); //初始化idr结构
四、分配一个新的UID
- 通过上面建立了一个idr之后,接下来就可以分配新的UID了。这个过程分为两步完成:
- 第一步:告 诉idr你需要分配新的UID,允许其在必要时调整后备树的大小
- 第二步:此步骤才是真正请求新的UID
- 之所以需要这两个组合动作是因为要允许调整初始大小——这中间涉及在无锁情况下分配内存的场景
调整后备树的大小(idr_pre_get)
- 第一个调整后备树大小的方法是idr_pre_get()
- 该函数将在需要时进行UID的分配工作:调整由idp指向的idr的大小。如果真的需要调整大小,则内存分配例程使用gfp标识:gfp_mask,你不需要对并发访问该方法进行同步保护。
- idr_pre_get成功时返回1,失败时返回0
/** * idr_pre_get - reserver resources for idr allocation * @idp: idr handle * @gfp_mask: memory allocation flags * * This function should be called prior to locking and calling the * following function. It preallocates enough memory to satisfy * the worst possible allocation. * * If the system is REALLY out of memory this function returns 0, * otherwise 1. */ 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_alloc(idr_layer_cache, gfp_mask); if (new == NULL) return (0); free_layer(idp, new); } return 1; } EXPORT_SYMBOL(idr_pre_get);
获取新的UID(idr_get_new)
- 执行获取新的UID,并将其加到idr
- 该方法使用idp所指的idr去分配一个新的UID,并且将其关联到指针ptr上
- 成功时,该方法返回0,并且匠心的UID存于starting_id。错误时,返回非0的错误码,错误码是-EAGAIN说明你需要(再次)调用idr_pre_get();如果idr已满,错误码为-ENOSPC
static int idr_get_new_above_int(struct idr *idp, void *ptr, int starting_id) { 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 = alloc_layer(idp))) return -1; 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) continue; if (!(new = alloc_layer(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; __free_layer(idp, new); } spin_unlock_irqrestore(&idp->lock, flags); return -1; } new->ary[0] = p; new->count = 1; if (p->bitmap == IDR_FULL) __set_bit(0, &new->bitmap); p = new; } idp->top = p; idp->layers = layers; v = sub_alloc(idp, ptr, &id); if (v == -2) goto build_up; return(v); }
- 演示例如:如果西面的代码执行成功将获得一个新的UID,将其存储在整形变量id中,而且将UID映射到ptr(我们没有在代码片中定义它)
获得最小的UID(idr_get_new_above)
- 该函数和idr_get_new()相同,除了它确保新的UID大于等于starting_id外。使用这个变种方法允许idr的使用者确保UID不会被重用,允许其值不但在当前分配的ID中不唯一,而且还保证在系统整个运行期间唯一
/** * idr_get_new_above - allocate new idr entry above or equal to a start id * @idp: idr handle * @ptr: pointer you want associated with the ide * @start_id: id to start search at * @id: pointer to the allocated handle * * This is the allocate id function. It should be called with any * required locks. * * If memory is required, it will return -EAGAIN, you should unlock * and go back to the idr_pre_get() call. If the idr is full, it will * return -ENOSPC. * * @id returns a value in the range 0 ... 0x7fffffff */ 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) { if (rv == -1) return -EAGAIN; else /* Will be -3 */ return -ENOSPC; } *id = rv; return 0; } EXPORT_SYMBOL(idr_get_new_above);
- 演示例如:下面的代码和前面的演示案例类似,不过我们明确要求增加UID的值
五、查找UID(idr_find)
- 当我们在一个idr中已经分配了一些UID时,我们自然就需要查找它们:调用者要给出UID,idr将返回对应的指针。查找步骤显然要比分配一个新的UID简单
- 该函数如果调用成功,返回id关联的指针;如果出错,返回空指针
- 注意:如果你使用idr_get_new或者idr_get_new_above()将空指针映射给UID,那么该函数在成功时也返回NULL,这样你就无法区分是成功还是失败,所以最好不要将UID映射到空指针上
/**
* idr_find - return pointer for given id
* @idp: idr handle
* @id: lookup key
*
* Return the pointer given the id it has been registered with. A %NULL
* return indicates that @id is not valid or you passed %NULL in
* idr_get_new().
*
* The caller must serialize idr_find() vs idr_get_new() and idr_remove().
*/
void *idr_find(struct idr *idp, int id)
{
int n;
struct idr_layer *p;
n = idp->layers * IDR_BITS;
p = idp->top;
/* Mask off upper bits we don't use for the search. */
id &= MAX_ID_MASK;
if (id >= (1 << n))
return NULL;
while (n > 0 && p) {
n -= IDR_BITS;
p = p->ary[(id >> n) & IDR_MASK];
}
return((void *)p);
}
EXPORT_SYMBOL(idr_find);
- 演示案例:
六、删除UID(idr_remove)
- 从idr中删除UID
- 如果成功,则将id关联的指针一起从映射中删除。遗憾的是,idr_remove()并没有办法提示任何错误(比如,如果id不在idp中)
/**
* idr_remove - remove the given id and free it's slot
* @idp: idr handle
* @id: unique key
*/
void idr_remove(struct idr *idp, int id)
{
struct idr_layer *p;
/* 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]) { // We can drop a layer
p = idp->top->ary[0];
idp->top->bitmap = idp->top->count = 0;
free_layer(idp, idp->top);
idp->top = p;
--idp->layers;
}
while (idp->id_free_cnt >= IDR_FREE_MAX) {
p = alloc_layer(idp);
kmem_cache_free(idr_layer_cache, p);
return;
}
}
EXPORT_SYMBOL(idr_remove);
七、撤销idr(idr_destroy)
- 撤销一个idr
- 如果成功,则只释放idr中未使用的内存。它并不释放当前分配给UID使用的任何内存。通常,内核代码不会撤销idr,除非关闭或者卸载,而且只用在没有其他用户(也就没有更多的UID)时才能删除
/**
* idr_destroy - release all cached layers within an idr tree
* idp: idr handle
*/
void idr_destroy(struct idr *idp)
{
while (idp->id_free_cnt) {
struct idr_layer *p = alloc_layer(idp);
kmem_cache_free(idr_layer_cache, p);
}
}
EXPORT_SYMBOL(idr_destroy);