(linux)idr(integer ID management)机制
最近研究进程间通信,遇到了idr相关的函数,为了扫清障碍,先研究了linux的idr机制。
IDR(integer ID management)的要完成的任务是给要管理的对象分配一个唯一的ID,于是可以通过这个数字找到要管理的对象。
应用IDR机制时要包含头文件<linux/idr.h>。
struct idr {
struct idr_layer *top; //idr的top层,可以方便的理解为根节点。
struct idr_layer *id_free; //id_free为首的形成一个链表,这个是预备队,
//并没有参与到top为根的节点中去
int layers; //当前的层数。
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 */
};
IDR_BITS 在32位操作系统是5 ,64位操作系统是6,我们以32位操作系统为例。
本文的介绍以两层的为例。layers = 2.
idr中的top指向的是当前正在工作的最高层的idr_layer,即图中的A,top的ary是个指针数组,指向
低一层的idr_layer。top层ary指针数组不一定都指向已经分配了的低一层idr_layer。也可能某个指针指
向NULL。如下图的ary[1]就指向NULL。
最后一层idr_layer 叶子层 例如B,他的指针数组ary中的元素,如果分配出去了那么指向某个结构体的地址,这个地址指向要管理的数据结构。如果没有分配出去,指针指向NULL。对于叶子层而言,判断指针数组某个元素是否指向有意义的数据结构,用位图bitmap。bitmap对应的位 是1,表示ary数组的对应元素指向某有意义的数据结构。
最后一层的bitmap的含义已经介绍,但是top层(或者层数大于2的时候,中间某层)bitmap的含义是什么呢?以两层为例,如果图中B的bitmap是0xFFFFFFFF,即每一个指针都分配出去了,那么A的bitmap的第0位置1.同样如果A的bitmap的第2位是1,表示ary[2]指向的C的bitmap是0xFFFFFFFF,即C也ary数组也分配完毕。
这部分是函数idr_mark_full来实现:
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);// 叶子层数字id对应的位 置1.
/*
* 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])) // pa[++l]记录的上一层idr_layer。
break;
id = id >> IDR_BITS;
__set_bit((id & IDR_MASK), &p->bitmap); //如果由于本层满了,则上一层对应位置1.
} //循环检测。
}
介绍完负责工作的部分,下面介绍预备役。所谓预备役就是id_free指向的空闲的idr_layer。所谓空闲是指,这些idr_layer并没有投入。如果需要分配一个idr_layer,首先将id_free指向的idr_layer取出来使用,同时id_free指向下一个。即如下图所示,如果需要分配,D被取出来使用,同时id_free指针指向E,同时id_freecnt减一。
将预备役投入使用是函数alloc_layer完成的:
static struct idr_layer *alloc_layer(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]; // id_free 指向D的下一位 E
idp->id_free_cnt--; // 预备役的个数减1
p->ary[0] = NULL; //D要被使用了,第0个指针不再指向E,初始化为NULL
}
spin_unlock_irqrestore(&idp->lock, flags);
return(p); // 返回D
}
有个问题是预备役是怎么来的?如果预备役分配光了怎么办。分配光了也没有关系,还好我们有idr_pre_get函数。
#if BITS_PER_LONG == 32
#define IDR_BITS 5
#define MAX_ID_SHIFT (sizeof(int)*8 - 1) //31
#define MAX_LEVEL (MAX_ID_SHIFT + IDR_BITS - 1) / IDR_BITS //7
#define IDR_FREE_MAX MAX_LEVEL + MAX_LEVEL //14
坦白说,MAX_LEVEL的含义是什么,我并不清楚。为什么一次分配14个idr_layer充当预备役我并不清楚。请清楚的兄弟不吝赐教。
这个函数的含义就是我要分配14个idr_layer,充当预备役。如果中间分配失败,那么能分配几个算几个。投入预备役的函数是free_layer。比较好懂我就不解释了。
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;
}
static void free_layer(struct idr *idp, struct idr_layer *p)
{
unsigned long flags;
/*
* Depends on the return element being zeroed.
*/
spin_lock_irqsave(&idp->lock, flags);
__free_layer(idp, p);
spin_unlock_irqrestore(&idp->lock, flags);
}
static void __free_layer(struct idr *idp, struct idr_layer *p)
{
p->ary[0] = idp->id_free;
idp->id_free = p;
idp->id_free_cnt++;
}
从预备役机制上看,我们可以得到使用idr编程流程应该是这样的。
首先调用idr_pre_get,来分配可用的idr_layer,投入预备役,接下来调用idr_get_new,
给要管理的对象target分配一个数字id,这个过程中可能会调用alloc_layer,将预备役中的
idr_layer投入使用,用在top为根管理结构中。
终有一天,预备役也被打光了idr_get_new
函数返回-EAGAIN,告诉我们,预备役全部阵亡,于是,我们从-EAGAIN的遗言中,知道,我们需要调用
idr_pre_get来充实预备役了。
again: if (idr_pre_get(&my_idr, GFP_KERNEL) == 0) {
/* No memory, give up entirely */
}
spin_lock(&my_lock);
result = idr_get_new(&my_idr, &target, &id);
if (result == -EAGAIN) {
sigh();
spin_unlock(&my_lock);
goto again;
}
下面:讲述如何给要管理的对象分配一个小数字作为id。
首先看知道obj的ID,如果查找obj ,即指向obj的指针。也就是说先看我们想要达到的效果,在来分析如何实现给对象分配ID。
根据ID ,来查找obj。函数idr_find实现查找功能
假如下图中C 的ary[2]指向一个管理的obj。我们来看下如何通过数字66来查找到obj。
我们以top为根的树其实是一个32叉树。如果只有一层,那么top本身指向叶子层,那么最多理32个obj,即ary数组的每个元素,指向一个obj。 但是假如说我们管理的对象超过了32个,我们就不能用一层来管理这个需要有两层结构。就像我们的示意图。
其实idr有一种比较简单的理解方式,就是它是一种32进制的数,满32,向前进一位。
我们还是从示意图讲起。我们寻找66指向的obj。首先判断66是否超过了当前层数所能管理最多obj。
当前我们是两层结构,top指向32叉树的根,top下面管理32个叶子层的idr_layer。上面一讲提到了,叶子层idr_layer的ary数组元素是用来指向目标obj的。那么两层总共可以管理3232=1024个obj。同样道理三层可以最多管理3232*32=32K 个obj。
要想找到obj的指针,必须根据ID,一路寻找的叶子层。66/32 = 2,所以从top--->top->ary[2],
我们就找到了叶子节点C。66|IDR_MASK = 2,所以C的ary[2]指向管理的obj。
用前面的32进制方法理解就是66 = 232+2,所以,top->ary[2]->ary[2]指向obj。
同样我们可以求ID是27对应的obj 27=032+27,所以top->ary[0]->ary[27]指向obj。
小结:通过上面的描述,我们也看到了,我们就是要建立一个32叉树,来管理obj。通过ID,可以一层层定位到叶子层,叶子层的指针指向的就是我们要管理的obj。 需要指出的是32叉树,不一定每个分支都分配好了idr_layer,用到了再分配,防止浪费,比如示意图中,并没有用到32~63,我们看到top->ary[1]为NULL。如有需要分配34了,那没办法,会在分配过程中分配个idr_layer,top->ary[1]指向分配的idr_layer。
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);
}
下面分析如果给一个obj分配个ID。
提供两个函数给obj分配ID
int idr_get_new(struct idr *idp, void *ptr, int *id)
int idr_get_new_above(struct idr *idp, void *ptr, int starting_id, int *id)
参数说明:
idp---不说了,管理结构idr的指针,对应示意图中最左面的那个结构。
ptr---指向要管理的结构的指针,我们的任务就是给它分配个小数字,作为他的身份证。成功之后,我们可以拿着这个ID,直接找到ptr。
id----输出参数,将分配的数字存入id。
这两个函数其中idr_get_new比较乖,比较好说话,随便给他分配一个没人用的id就可以,他他不挑不捡。第二个函数idr_get_new_above有点难说话,要求挺多,他有个参数starting_id,要求分配不小于starting_id的一个数字作为id。
两个函数都是调用了idr_get_new_above_int,区别是idr_get_new将starting_id填成了0.表示随便给分配个大于0的没被别人用的id就行。
-EAGAIN的意思上面一讲提到过,这个是预备役全体阵亡的遗言,没有空闲的idr_layer用来分配了,所以失败了,如果用户非常需要给ptr分配个id,那么请先分配点预备役,即调用idr_pre_get。
-ENOSPC的含义是你小子要的id太大了,超过了MAX_ID_BIT,即2^31,idr说,我是管理小数字的结构,拜托不要那这么大的数字骚扰我。
if ((id >= MAX_ID_BIT) || (id < 0))
return -3; // sub_alloc函数中的语句
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) {
if (rv == -1)
return -EAGAIN;
else /* Will be -3 */
return -ENOSPC;
}
*id = rv;
return 0;
}
酝酿了半天,可以聊聊idr_get_new_above_int这个了。
idr_get_empty_slot函数是分配个大于starting_id的数字作为ptr的ID。如果分配成功,id>=0,将叶子节点id对应的ary数组的元素赋值为 ptr。同时将叶子层的count++,表示又分配出去一个。将叶子层的位图bitmap对应槽位置1的工作是idr_mark_full完成。如果叶子层全满了,则通知叶子层的父亲对应槽位置1,依次传递。
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.
*/
pa[0]->ary[id & IDR_MASK] = (struct idr_layer *)ptr;
pa[0]->count++;
idr_mark_full(pa, id);
}
return id;
}
OK,到了idr_get_empty_slot。这个函数是干重活的函数。需要仔细研读代码。这个函数不举例子很难描述清楚,举例子又显得特别琐碎,很头疼。建议读者从0开始分配一直分配到32需要分层,就可以理解代码的含义。
先讲初始化:
#define IDR_INIT(name) \
{ \
.top = NULL, \
.id_free = NULL, \
.layers = 0, \
.id_free_cnt = 0, \
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
}
top等于NULL 表示我的32叉树还没建立起来,id_free =NULL,id_free_cnt=0表示不好意思,我的预备役也为空,没法为您分配idr_layer。这是最初的状态,32叉树连个根都没有,整个idr处于一穷二白的状态。
p = idp->top;
layers = idp->layers;
if (unlikely(!p)) {
if (!(p = alloc_layer(idp)))
return -1;
layers = 1;
}
idr_get_empty_slot这个部分,表示如果idr的32叉树连个根都没有,我需要分配一个idr_layer来当根。如果alloc_layer失败,表示预备役空了,惨了,只能返回失败,告诉调用者,预备役没了,请填充预备役。一般是可以分配的。
这个循环体的含义是,用户这个搞得这个starting_id太大了,或者低的id分配出去了,只能给用户分配个大的id。如果这个id大于了当前层数所能管理的最高ID,我们需要加一层了。
以上面的示意图为例,我们当前有两层结构,最多能管理32*32=1K个,我们能分配的最大id就是1023,如果用户要求我们分配大于等于1500的id,那么我们目前的两层结构是无法满足需要的,所以我们需要加一层。首先将layer++,表示我们的32叉树升级了,多了一层,从预备役分配出一个idr_layer,让新分配的new当根。p指针指向根。
如果分配的id不够大,不需要分层,那么这个while就不执行了,直接跳到sub_alloc函数。
while ((layers < (MAX_LEVEL - 1)) && (id >= (1 << (layers*IDR_BITS)))) {
layers++;
if (!p->count)//这个地方是应对特殊情况,比如0~31都没有分配,第一层还没有,用户
//上来要分配32或46这样明显是两层才能完成的结构
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, &id, pa);
if (v == -2)
goto build_up;
sub_alloc函数。
还是以示意图为例讲述。我们是两层的结构,p是32叉树的根节点top
如果用户要分配大于等于66的id,66=2*32+2,首先找到了我们要找的66是位于top->ary[2],我们需要确认 根的ary[2]这个分支是否还能分配。如果p->ary[2]对应的idr_layer 所有的槽位都分配出去了,客满,新的顾客无法入住,我们就不必白费劲去ary[2]这个分支去分配了。判断的办法就是m = find_next_bit(&bm, IDR_SIZE, n);这个函数很可爱,就是说我要找大于2 的所有分支,寻找第一个没有客满的分支。通过top层或者中间层bitmap的含义,如果某个分支全部客满,则在对应bitmap位置1 ,表示,不要去这个分支找了,找也白找。
然后一层层往下找,知道找到叶子层,在叶子层查找大于等于2的id。
各种情况我就不分析了,大家可以自己尝试分配一下:
- 从0开始,分配,累加到33,差不多就可以理解idr_get_new这种情况的分配流程
- 不按常理出牌,乱分配,假如我第一个就要分配 大于37的,第二次就要分配大于1500的,之类的,
在走一遍流程,就可以理解相关的代码。
while (1) {
/*
* We run around this while until we reach the leaf node...
*/
n = (id >> (IDR_BITS*l)) & IDR_MASK;
bm = ~p->bitmap;
m = find_next_bit(&bm, IDR_SIZE, n);
if (m == IDR_SIZE) {
/* no space available go back to previous layer. */
l++;
oid = id;
id = (id | ((1 << (IDR_BITS * l)) - 1)) + 1;
/* if already at the top layer, we need to grow */
if (!(p = pa[l])) {
*starting_id = id;
return -2;
}
/* If we need to go up one layer, continue the
* loop; otherwise, restart from the top.
*/
sh = IDR_BITS * (l + 1);
if (oid >> sh == id >> sh)
continue;
else
goto restart;
}
if (m != n) {
sh = IDR_BITS*l;
id = ((id >> sh) ^ n ^ m) << sh;
}
if ((id >= MAX_ID_BIT) || (id < 0))
return -3;
if (l == 0)
break;
/*
* Create the layer below if it is missing.
*/
if (!p->ary[m]) {
if (!(new = alloc_layer(idp)))
return -1;
p->ary[m] = new;
p->count++;
}
pa[l--] = p;
p = p->ary[m];
}
参考文献 :IDR-integer ID management
(linux)idr(integer ID management)机制的更多相关文章
- Linux IDR机制【转】
转自:https://blog.csdn.net/av_geek/article/details/49640433 IDR机制在Linux内核中指的是整数ID管理机制. 实质上来讲,这就是一种将一个整 ...
- Linux系统的快速启动机制(内核切换) 【转】
转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26807463&id=4187846 原文地址:Linux系统的 ...
- 20155301 滕树晨linux基础——linux进程间通信(IPC)机制总结
20155301 滕树晨linux基础--linux进程间通信(IPC)机制总结 共享内存 共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,由IPC为进程创建的一个特殊地址范围,它将出现在 ...
- linux 内核与用户空间通信机制netlink初探
1.Linux进程概述 Linux中的进程间通信机制源自于Unix平台上的进程通信机制.Unix的两大分支AT&T Unix和BSD Unix在进程通信实现机制上各有所不同,前者形成了运行 ...
- 【转载】Linux设备树(Device Tree)机制
转:Linux设备树(Device Tree)机制 目录 1. 设备树(Device Tree)基本概念及作用2. 设备树的组成和使用 2.1. DTS和DTSI 2.2. DTC 2.3. DT ...
- 【转】linux 设置用户id 设置组id
linux 设置用户id 设置组id 转自 linux 设置用户id 设置组id 最近看apue,简单记录对设置用户id理解(设置组id同理). 1. 相关的id好像很多,共有哪些? 文件2个 ...
- Linux mips64r2 PCI中断路由机制分析
Linux mips64r2 PCI中断路由机制分析 本文主要分析mips64r2 PCI设备中断路由原理和irq号分配实现方法,并尝试回答如下问题: PCI设备驱动中断注册(request_irq) ...
- Linux的原子操作与同步机制
Linux的原子操作与同步机制 .进程1执行完“mov eax, [count]”后,寄存器eax内保存了count的值0.此时,进程2被调度执行,抢占了进程1的CPU的控制权.进程2执行“cou ...
- 转:Linux内部的时钟处理机制全面剖析
Linux内部的时钟处理机制全面剖析 在 Linux 操作系统中,很多活动都和时间有关,例如:进程调度和网络处理等等.所以说,了解 Linux 操作系统中的时钟处理机制有助于更好地了解 Linux 操 ...
随机推荐
- CSS-文本(中,英)
1.缩进文本:text-indent 2.水平对齐:text-align: left/center/right/justify(实现两端对齐文本效果) 3.字间隔:word-spacing(可以改变 ...
- 浅谈HookSSDT和和Resume(恢复)SSDT
最近在学HookSSDT和针对Hook的ResumeSSDT,避免自己理解有所偏差,把它们写出来,希望大家不吝赐教.(虽然已经是过时了的技术,但是最起码了解其中的原理,嘿嘿嘿.) 转载注明出处:ht ...
- 会话跟踪技术Cookieless
会话跟踪技术Cookieless 在Web应用中,通常使用Cookie记录用户的状态,如用户名.访问时间等信息.当进行HTTP请求的时候,会自动发送Cookie信息给服务器.服务器接收到,就可以判 ...
- 【UTR #2】题目交流通道
题目描述 定好了难度,雄心勃勃的吉米多出题斯基开始寻找智慧的神犇星球的居民出题. 然而吉米多出题斯基没有料到,神犇星球的居民告诉吉米多出题斯基:"今年神犇星球经济不景气,大家都想宅在家里,哪 ...
- ie下li标签中span加float:right不换行问题解决方案
在IE6,IE7下使用标签时,在加入右浮动样式(float:right)后,会换行的bug解决方案:bug案例:新闻列表中,为使时间右对齐,加右浮动产生换行 <ul> <li> ...
- Play框架连接Mysql遇到的一些问题
最近,在基于Play框架的项目中需要连接Mysql数据库.在这个过程中遇到了一些问题.在此,把它记录下来. 首先,Play框架和Mysql连接有两种方式,这两种方式都是在application.con ...
- 朱子奇- 精算师,Tailorwoods创始人 | 到「在行」来约见我
朱子奇- 精算师,Tailorwoods创始人 | 到「在行」来约见我 Tailorwoods
- 【转载】Websocket学习
首先是在Tomcat里面看到Websocket的演示.很有意思. http://localhost:8080/examples/websocket/index.xhtml 里面有: Echo exam ...
- Dubbo简介及实例
节点角色说明: Ø Provider: 暴露服务的服务提供方. Ø Consumer: 调用远程服务的服务消费方. Ø Registry: 服务注册与发现的注册中心. Ø Monitor: 统 ...
- myBatis-plus异常提示For input string: "{0=null}"
异常信息 org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.Per ...