idr机制

一 idr的初始化

1 静态

#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),	\
}

2 动态

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

二 主要数据结构

struct idr {
	struct idr_layer __rcu *top;标识使用的idr_layer,类似于根结点
	struct idr_layer *id_free;连接,还未使用的idr_layer,id_free为首的形成一个链表,这个是预备队,并没有参与到top为根的结点中去
	int		  layers; /* only valid without concurrent changes */基数树层数
	int		  id_free_cnt;空闲的idr_layer计数
	spinlock_t	  lock;
};
struct idr_layer {
	unsigned long		 bitmap; /* A zero bit means "space here" */表示id在数组中的位置
	struct idr_layer __rcu	*ary[1<<IDR_BITS];根或树干结点时存储子结点指针,叶结点时存储指针数据
	int			 count;	 /* When zero, we can release it */ary中有效数据个数,为0时可以释放
	int			 layer;	 /* distance from leaf */距离叶子的距离,如果是叶结点layer=0
	struct rcu_head		 rcu_head;
};

三 主要宏

#if BITS_PER_LONG == 32
# define IDR_BITS 5//1层idr_layer最多可以表示1<<5=32个id
# define IDR_FULL 0xfffffffful//可以使用的最大的id号
要表示出0xffffffff = 1<<32-1最大id号时,需要的MAX_IDR_LEVEL是7;就是因为IDR_BITS为5,1层idr_layer最多可以表示1<<5=32个id,2层最多可以表示1<<10,7层才可以表示1<<35。
# define TOP_LEVEL_FULL (IDR_FULL >> 30)
#elif BITS_PER_LONG == 64
# define IDR_BITS 6
# define IDR_FULL 0xfffffffffffffffful
# define TOP_LEVEL_FULL (IDR_FULL >> 62)
#else
# error "BITS_PER_LONG is not 32 or 64"
#endif

#define IDR_SIZE (1 << IDR_BITS) //0x20
#define IDR_MASK ((1 << IDR_BITS)-1)//0x1f

#define MAX_IDR_SHIFT (sizeof(int)*8 - 1) //31
#define MAX_IDR_BIT (1U << MAX_IDR_SHIFT)//0x80000000
#define MAX_IDR_MASK (MAX_IDR_BIT - 1)//0x7fffffff

/* Leave the possibility of an incomplete final layer */
#define MAX_IDR_LEVEL ((MAX_IDR_SHIFT + IDR_BITS - 1) / IDR_BITS)//7

/* Number of id_layer structs to leave in free list */
#define MAX_IDR_FREE (MAX_IDR_LEVEL * 2)//14,7层就够用了,为什么弄个14呢?因为在分配id过程中可能需要对树进行扩容,如果出现结点为空还要分配节点,比如空树情况,分配一个值为0x7fffffff的id,则首先要扩容MAX_LEVEL个结点,再添加MAX_LEVEL-1个结点后才能正确分配id。

四 主要函数接口

1 分配存放ID号的内存

int idr_pre_get(struct idr *idp, gfp_t gfp_mask)
该函数执行过后,共分配了 MAX_IDR_FREE==14个 (0~13)idr_layer,它们链成了一个链表,其实没有层的概念,但可以理解成是各层的。第0层是叶子层在最底下;idr_layer13_0->ary[0]=NULL(第一个分配的是第13层的),idr_layer12_0->ary[0]=idr_layer11_0,以此类推,idr_layer1_0->ary[0]=idr_layer0_0;idr->top =NULL;idr->id_free=idr_layer0_0,idr->id_free_cnt=14。其中idr_layer0_0表示第0层第0个idr_layer。第13层有1<<5个idr_layer,第12层有1<<10个idr_layer,第7层有1<<32个idr_layer。如果要从idr->id_free的链表上卸下一个,那么卸下的会是idr_layer0_0。
idr->id_free_cnt未使用的idr_layer,没有为0,最大为14;
idr->layers基数树层数,没有为0;有1层为1,最大为14;
idr_layer->count ary中有效数据个数,没有为0,最大32;
idr_layer->layers 标记idr_layer位于第几层,叶结点layer=0;最大为13。

2 分配一个新的idr entry,返回值存储在id中

int idr_get_new(struct idr *idp, void *ptr, int *id)
  static int idr_get_new_above_int(struct idr *idp, void *ptr, int starting_id)
    static int idr_get_empty_slot(struct idr *idp, int starting_id, struct idr_layer **pa)

idr_get_empty_slot

1 如果p==NULL,说明是第一次调用,基数树还没有根结点;从id_free上卸下一个idr_layer。卸下来的p = idp->top为idr_layer0_0是第0层的,之后,id_free=idr_layer1_0,id_free_cnt=13,idr_layer1_0->ary[0]=NULL,断开和后面idr_layer的联系,如果不需要扩容,这个就是叶子结点,存储的是关联指针;如果需要扩容,top根结点会变成更底层的结点;此时就是叶子结点;new的结点会荣升为根结点。p->layer=0。
2 如果id太大了,当前的层数,分配不了那么大的id(比如第一调用时,id=1024),那就需要对基数树进行扩容;layers++;先临时修改树的总层数,但并未赋给idr。
(1) 如果!p->count当前树是空树,则直接修改根结点的层级p->layer++就可以了(比如1024,要到根结点的层级变为p->layer=2),这种情况是需要扩容的,是在sub_alloc()中实现的。
(2) 否则,将原树的根结点作为新树根结点的第一个元素,循环添加根结点直到满足扩容要求;在扩容过程中如果失败,则会删除前面添加的结点。
3 idp->layers = layers;修改树的总层数。
4 从基数树中分配空闲id,如果树需要扩容,则根据id大小对树进行扩容。分配id过程如下:
(1) 查找starting_id开始的id空间,如果没有可用空间,则需要对基数树进行扩容;
(2) 根据starting_id递归子结点,如果子结点无空间,则回退到上级或顶级并根据增大后的starting_id继续查找空闲id。 
注:递归过程中,需要父结点bitmap中标识子结点有空闲空间,但是子结点的空闲区间可能会出现在id之前,id之后无空闲空间,所以会出现回退上级或顶级的情况。
注:一个结点有32或64个区间,分别对应ary的每个元素。 如果该结点没有空闲id,则回退到上层,并向后偏移一个区间继续查找空闲ID;如果父区间是该结点的最后一个区间,则回退到根结点重新开始查找。 回退上层与根的两种情况如下:
layer = 6 5 4 3 2 1 0 id = 00 00000 00000 00000 11111 00001 00000时 a 在查找第0层第0(00000)区间时,由于该区间没有空闲id,则回退上级,并向后偏移一个区间即第2(00001+1)区间;由于上层两个区间属于同一节点,则可以继续查找。 b 在查找第1层第1(00001)区间时,由于该区间没有空闲id,则回退上级,并向后偏移一个区间即第0(11111+1)区间;由于上层两个区间不属于同一节点,则回到根结点继续查找。
(3) 递归子结点过程中,如果结点不存在,则添加结点;
(4) 递归到叶结点,则循环结束,并返回叶结点的空闲id。
注:id = (id | ((1 << (IDR_BITS * l)) - 1)) + 1;表示回退到上层,并偏移一个区间;sh = IDR_BITS * (l + 1);用来判断偏移后的区间与原区间是否在同一结点sh = IDR_BITS*l; id = ((id >> sh) ^ n ^ m) << sh;将id中代表l级的域值由n修改成m,可以看成id=id>>(sh+IDR_BITS)<<(sh+IDR_BITS)+n^n^m<<sh=id>>(sh+IDR_BITS)<<(sh+IDR_BITS)+m<<sh
回退到上层或顶层情况如下图:
idr机制_第1张图片
A结点bitmap=0x0000FFFF,B结点bitmap=0x0000FFFF,D结点bitmap=0x00000000当从977开始查找空闲id时,会查找到A结点,但A结点没有空闲id,会回退到上层C.ary[30],并编移一个区间C.ary[31],由于C.ary[30]与C.ary[31]在同一结点即E.ary[0]=E.ary[0];继续从C.ary[31]开始查找,会在B结点找到空闲id=992当从1009开始查找空闲id时,会查找到B结点,但B结点没有空闲id,会回退到上层C.ary[31],并统称一个区间D.ary[0],由于C.ary[31]与D.ary[0]不在同一结点即E.ary[0]!=E.ary[1];则回到顶层结点E,并从id=1024重新再查找空闲ID,则会在D结点找到空闲id=1024。
问题:每次扩容都是直接从id_free上拿下来一个,有人称为预备役的东西;可是我们子分配了14个啊,拿完了怎么办?从预备役机制上看,我们可以得到使用idr编程流程应该是这样的:
首先调用idr_pre_get,来分配可用的idr_layer,投入预备役,接下来调用idr_get_new,
给要管理的对象target分配一个数字id,这个过程中可能会调用get_from_free_list,将预备役中的idr_layer投入使用,用在top为根管理结构中。终有一天,预备役也被打光了idr_get_new函数返回-EAGAIN,告诉我们,预备役全部阵亡,于是,我们从-EAGAIN的遗言中,知道,我们需要调用idr_pre_get来充实预备役了。
int idr_get_new(struct idr *idp, void *ptr, int *id)
  static int idr_get_new_above_int(struct idr *idp, void *ptr, int starting_id)
    static void idr_mark_full(struct idr_layer **pa, int id)

idr_mark_full

注: rcu_assign_pointer(pa[0]->ary[id & IDR_MASK],
(struct idr_layer *)ptr);
之前会调用这样一句,是根据id的低5位,找到pa[0]->ary[id & IDR_MASK],void *ptr,是一个需要与id关联的指针,它一定存放在叶子结点的ary[]里,idr_get_empty_slot执行完会根据基数树从根到叶子得到pa,比如,pa[2]、pa[1]、pa[0];pa[0]就是一个叶子结点。
1 设置叶子层id对应的bitmap。
2 如果叶子层全满了,则通知叶子层父亲bitmap的对应位置1,依次传递。

3 idr查找id对应指针

id的查找即根据id值找到叶子结点中存储的指针值。
void *idr_find(struct idr *idp, int id)
1 找到idp->top。
2 1<<n为该基数树所有结点能表示的最大id。
3 id &= MAX_IDR_MASK;屏蔽搜索不到的。
4 从树的根开始找,每次移动IDR_BITS=5bit,一直找到叶子结点;找到叶子结点中的p->ary[(id >> n) & IDR_MASK]里就是存进去的关联指针ptr了。
比如:id = 67 = 0x43=0b01000011
当n == 2*IDR_BITS时 p->ary[0]
当n == 1*IDR_BITS时 p->ary[2]这里找到了数据不为0的结点,第1层;
当n == 0*IDR_BITS时 p->ary[3]至此就找到了叶子结点,第0层。
其实id = 32*2+3就是这样来的。

4 idr移除删除ID号并释放指针

void idr_remove(struct idr *idp, int id)
1 static void sub_remove(struct idr *idp, int shift, int id)
(1)  将id所表示的基数树路径上结点对应的位图置0,从树的根清到叶子结点,paa保存从根到叶子结点的idr_layer。
(2) while循环的结束条件,如果是p == NULL或者了p->bitmap的第n位没有set,就没有叶子结点了啊,那这个id分配出了问题。如果是shift<=0,那么p可能指向叶子结点的上一个结点,所以跳出来while后,判断p != NULL与test_bit(n, &p->bitmap)设置了p->bitmap的第n位。先清除改bit。
(3)  接下来的while,paa是从叶子结点开始一直到根,如果路径上结点为空结点,同时删除结点。while的结束条件,如果是paa不为NULL,--((**paa)->count)不为0,说明该结点不是空结点,但是它的上一个,更靠近叶子结点的结点会是空的,所以最后还要if (to_free) free_layer(to_free);一下下,to_free的free是要到下一个循环的时候,确定上一个结点为NULL了,才会free。如果是paa为NULL,后面的条件就不管了,就是到了根了;if (!*paa) idp->layers = 0;整个树都被free了。
2 如果top根结点只使用了0区间,则删除top根结点,将0区间表示的结点作为top根结点。
3 释放idr缓存中多余的结点,还会留MAX_IDR_FREE=14个。
void idr_remove_all(struct idr *idp)
1 1<<n是能表示出的最大id。
2 p = idp->top;暂存top,idp->top=NULL。
3 max就是最大id了。
4 最外层的while,id从0开始,每个循环增加 1 << IDR_BITS。
5 里层第一个while,paa依次保存树的根到叶子结点。
6 里层第二个while,paa拿出来的时候,是从叶子结点到根的顺序;n的起始值是5。while (n < fls(id ^ bt_mask))表示最后一个为1的bit的位置,fls(32)=6。
7 从叶子结点到根的顺序随着id的增大一层一层free layer。

5 销毁idr树

void idr_destroy(struct idr *idp)
{
	while (idp->id_free_cnt) {
		struct idr_layer *p = get_from_free_list(idp);
		kmem_cache_free(idr_layer_cache, p);
	}
}

在调用idr_destroy()之前,要先在该idr上调用idr_remove_all(),确保所有的idr内存被释放。

你可能感兴趣的:(linux驱动)