Linux(内核剖析):16---内核数据结构之映射(struct idr)

一、映射概述

  • 一个映射,也称为关联数组
  • 是一个由唯一键组成的集合,而每个键必然关联一个特定的值。这种键到值的关联关系称为映射

散列表

  • 散列表是实现映射的一种数据结构

自平衡二叉搜索树

  • 虽然可以用散列表实现映射,但映射也可以通过自平衡二叉搜索树存储数据
  • 虽然散列表能提供更好的平均的渐进复杂度,但是二叉搜索树在最坏情况下能有更好的表现(即对数复杂性相比线型复杂性)
  • 二叉搜索树同时满足顺序保证,这给用户的按序遍历带来很好的性能
  • 二叉搜索树的最后一个优势是它不需要散列函数,需要的键类型只要可以定义<=操作算子便可以
  • 比如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);
发布了1300 篇原创文章 · 获赞 827 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/103843445
今日推荐