我们一般称键值对数组为映射,也称为哈希。一般查找的时间复杂度可以低至常数级别。在高频次的查询场景中,有较为优异的性能。关于映射的性质,我们不过多讨论,本文主要看看 Linux 中映射的实现,本文以 Linux 2.6.12 版本为例。
Linux 实现的映射,并不是一个通用的映射。他的目标是:映射一个唯一数(UID)到一个指针。
一、实现结构
Linux 提供了结构体 idr 用来完成这一目标。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct idr_layer { unsigned long bitmap; struct idr_layer *ary[1<<IDR_BITS]; int count; };
struct idr { struct idr_layer *top; struct idr_layer *id_free; int layers; int id_free_cnt; spinlock_t lock; };
|
使用一些接口来构造一个 idr 数据结构,并进行初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| static int init_id_cache(void) { if (!idr_layer_cache) idr_layer_cache = kmem_cache_create("idr_layer_cache", sizeof(struct idr_layer), 0, 0, idr_cache_ctor, NULL); return 0; }
void idr_init(struct idr *idp) { init_id_cache(); memset(idp, 0, sizeof(struct idr)); spin_lock_init(&idp->lock); }
|
其中 idr_layer_cache 是一个全局变量,在初始化 idr 的时候,会尝试对 idr_layer_cache 进行初始化一次。暂时不用理 kmem_cache_create 方法的含义,只需要知道他是为 idr_layer_cache 获取到了内存空间即可。
然后将用户传入的 idr 结构体指针的空间,全部置为 0,并初始化结构体中的锁。
二、分配一个 UID
构造好 idr 之后,就可以分配新的 UID 了。这个过程分为两步。
- 告诉 idr,我们需要分配新的 UID,允许其在必要时可以调整后备树的大小
- 请求新的 UID
对于第一步,我们可以使用 idr_pre_get
这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| static void free_layer(struct idr *idp, struct idr_layer *p) {
spin_lock(&idp->lock); p->ary[0] = idp->id_free; idp->id_free = p; idp->id_free_cnt++; spin_unlock(&idp->lock); }
int idr_pre_get(struct idr *idp, unsigned 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; }
|
IDR_FREE_MAX 这个值经过计算,在 64 位机器上是 12。感兴趣可以去计算一下。