背景
最近在學習 Linux的i2c子系統,看到代碼中有關於IDR的調用。了解了一下有關的文檔,發現是用來管理指針(對象實例)。
//based on linux V3.14 source code
reference:
- https://blog.csdn.net/morphad/article/details/9051261
- https://blog.csdn.net/midion9/article/details/50923095
概述
系統許多資源都用整數ID來標識,如進程ID、文件描述符ID、IPC ID等;資源信息通常存放在對應的數據結構中(如進程信息存放在task_struct中、ipc信息存放在ipc_perm中),id與數據結構的關聯機制有不同的實現,idr機制是其中的一種。
idr,id radix的縮寫。idr主要用於建立id與指針(指向對應的數據結構)之間的對應關系。idr用類基數樹結構來構造一個稀疏數組,以id為索引找到對應數組元素,進而找到對應的數據結構指針。
IDR機制在Linux內核中指的是整數ID管理機制。實質上來講,這就是一種將一個整數ID號和一個指針關聯在一起的機制。這個機制最早在03年2月加入內核,當時作為POSIX定時器的一個補丁。現在,內核中很多地方都可以找到它的身影。
用到idr機制的主要有:IPC id(消息隊列id、信號量id、共享內存id等),磁盤分區id(sda中數字部分)等。
IDR機制產生的背景原理:
IDR機制適用在那些需要把某個整數和特定指針關聯在一起的地方。例如,在IIC總線中,每個設備都有自己的地址,要想在總線上找到特定的設備,就必須要先發送設備的地址。當適配器要訪問總線上的IIC設備時,首先要知道它們的ID號,同時要在內核中建立一個用於描述該設備的結構體,和驅動程序。將ID號和設備結構體結合起來,如果使用數組進行索引,一旦ID號很大,則用數組索引會占據大量內存空間。這顯然不可能。或者用鏈表,但是,如果總線中實際存在的設備很多,則鏈表的查詢效率會很低。此時,IDR機制應運而生,可以很方便的將整數和指針關聯起來,並且具有很高的搜索效率(內部采用radix樹實現)。
相關結構體
struct idr {
struct idr_layer __rcu *hint; //最近一個存儲指針數據的的idr_layer結構
struct idr_layer __rcu *top; //idr的idr_layer樹頂層,樹的根
struct idr_layer *id_free; //指向idr_layer的空閑鏈表
int layers; //idr樹中的idr_layer層數量
int id_free_cnt; //idr_layer空閑鏈表中剩余的idr_layer個數
int cur; //current pos for cyclic allocation
spinlock_t lock;
};
struct idr_layer {
int prefix; //the ID prefix of this idr_layer
DECLARE_BITMAP(bitmap, IDR_SIZE); //標記位圖,標記該idr_layer的ary數組使用情況
//該數組用於保存具體的指針數據或者指向子idr_layer結構,大小為1<<8=256項
struct idr_layer __rcu *ary[1<<IDR_BITS];
int count; //ary數組使用計數
int layer; //層號
struct rcu_head rcu_head;
};
idr初始化
在start_kernel函數中調用idr_init_cache()對idr進行相應的初始化。創建一個slab cache,為后邊分配idr_layer結構。
static struct kmem_cache *idr_layer_cache;
void __init idr_init_cache(void)
{
idr_layer_cache = kmem_cache_create("idr_layer_cache",sizeof(struct idr_layer), 0, SLAB_PANIC, NULL);
}
idr的使用
1.idr的初始化
(1)宏定義並且初始化一個名為name的idr:
#define DEFINE_IDR(name) struct idr name = IDR_INIT(name)
#define IDR_INIT(name) \
{ \
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
}
(2)動態初始化idr:
void idr_init(struct idr *idp)
{
memset(idp, 0, sizeof(struct idr));
spin_lock_init(&idp->lock);
}
2.分配idr的空閑idr_layer鏈表
static inline int __deprecated idr_pre_get(struct idr *idp, gfp_t gfp_mask)
{
return __idr_pre_get(idp, gfp_mask);
}
//注意該函數會導致睡眠,因此不應該用鎖保護,函數實現如下
#define MAX_IDR_SHIFT (sizeof(int) * 8 - 1)
#define MAX_IDR_LEVEL ((MAX_IDR_SHIFT + IDR_BITS - 1) / IDR_BITS)
#define MAX_IDR_FREE (MAX_IDR_LEVEL * 2)
//32位系統下,MAX_IDR_SHIFT=31,則MAX_IDR_LEVEL=(31+8-1)/8=4,則MAX_IDR_FREE=4*2=8
int __idr_pre_get(struct idr *idp, gfp_t gfp_mask)
{
//32位系統下,MAX_IDR_FREE=8,所以idr有最多8個處於free狀態的idr_layer內存空間
while (idp->id_free_cnt < MAX_IDR_FREE) {
struct idr_layer *new;
//通過slab高速緩存分配idr_layer內存空間
new = kmem_cache_zalloc(idr_layer_cache, gfp_mask);
if (new == NULL)
return (0);
//將idr_layer結構鏈入idr空閑可用鏈表中
move_to_free_list(idp, new);
}
return 1;
}
static void move_to_free_list(struct idr *idp, struct idr_layer *p)
{
unsigned long flags;
spin_lock_irqsave(&idp->lock, flags);
__move_to_free_list(idp, p);
spin_unlock_irqrestore(&idp->lock, flags);
}
static void __move_to_free_list(struct idr *idp, struct idr_layer *p)
{
//p代指新創建的id_free成員idr_layer結構。
//當idp->id_free = NULL時(剛初始化),p->ary[0] = idp->id_free = NULL。
//當idp->id_free不為NULL的時候,就表示新創建的idr_layer的ary[0]指向之前的idp->id_free指向的成員,然后再將idp->id_free指向新的成員。最終8個idr_layer都鏈入鏈表,結構如下:
/*
idp->id_free -> p8
p8->ary[0] -> p7
p7->ary[0] -> p6
...
...
p1->ary[0] -> NULL
*/
p->ary[0] = idp->id_free;
idp->id_free = p;
idp->id_free_cnt++;
}
3.分配id號並將id號和指針關聯
idr有一種比較簡單的理解方式,因為之前的IDR_BITS=5,現在在3.14內核中IDR_BITS=8,所以現在它就是一種256進制的數,滿256,向前進一位。
假設當前我們是兩層結構,top指向256叉樹的根,top下面管理256個葉子層的idr_layer。葉子層idr_layer的ary數組元素是用來指向目標obj的。那么兩層總共可以管理256256=65536個obj。同樣道理三層可以最多管理256256*256=16M個obj。
static inline int idr_get_new(struct idr *idp, void *ptr, int *id)
{
return __idr_get_new_above(idp, ptr, 0, id);
}
//參數idp是之前通過idr_init()初始化的idr指針,或者DEFINE_IDR宏定義的指針。
//參數ptr是和ID號相關聯的指針。
//參數id由內核自動分配的ID號,輸出參數。
//參數start_id是起始ID號。
int __idr_get_new_above(struct idr *idp, void *ptr, int starting_id, int *id)
{
struct idr_layer *pa[MAX_IDR_LEVEL + 1];
int rv;
//在該idr的idr_layer樹中分配一個合適的id,並且分配的idr_layer路徑記錄在pa數組中
rv = idr_get_empty_slot(idp, starting_id, pa, 0, idp);
if (rv < 0)
return rv == -ENOMEM ? -EAGAIN : rv;
//關聯ptr和id
idr_fill_slot(idp, ptr, rv, pa);
*id = rv;
return 0;
}
static int idr_get_empty_slot(struct idr *idp, int starting_id,
struct idr_layer **pa, gfp_t gfp_mask,
struct idr *layer_idr)
{
struct idr_layer *p, *new;
int layers, v, id;
unsigned long flags;
id = starting_id;//starting_id=0
build_up:
//第一次申請id號時,根top指向的idr_layer為NULL
p = idp->top;
//第一次申請id號時,layers層數量idp->layers為0
layers = idp->layers;
//若top指針為NULL,則先設置top指針
if (unlikely(!p)) {
//從idr空閑idr_layer鏈表中獲取最后一個鏈入鏈表的idr_layer結構,一般為idr_layer8
//沒有的話,則重新分配一個idr_layer結構
if (!(p = idr_layer_alloc(gfp_mask, layer_idr)))
return -ENOMEM;
p->layer = 0;//指定該idr_layer層號為0
layers = 1; //layers層數量設為1,此時只有根idr_layer,即idr_layer8
}
//如果起始的id號超過該idr中設定的idr_layer層數所能設置的id號最大值,則增加idr中的idr_layer樹
while (id > idr_max(layers)) {
layers++;//idr層數加1
//count為0,表示該idr_layer結構沒有子節點???
if (!p->count) {
/* special case: if the tree is currently empty,
* then we grow the tree by moving the top node upwards.
*/
p->layer++;
WARN_ON_ONCE(p->prefix);
continue;
}
//從layer_idr的空閑鏈表中分配一個idr_layer結構,或者從內存中分配一個idr_layer結構
if (!(new = idr_layer_alloc(gfp_mask, layer_idr))) {
//若分配失敗,top指針指向的idr_layer結構全部要重新初始化,並移到idr的free鏈表中,返回錯誤碼
spin_lock_irqsave(&idp->lock, flags);
for (new = p; p && p != idp->top; new = p) {
p = p->ary[0];
new->ary[0] = NULL;
new->count = 0;
bitmap_clear(new->bitmap, 0, IDR_SIZE);
__move_to_free_list(idp, new);
}
spin_unlock_irqrestore(&idp->lock, flags);
return -ENOMEM;
}
//新分配的new節點鏈入top所指向的idr_layer鏈表中,變成p的父節點
new->ary[0] = p;
//count設為1表示有一個子節點,即ary數組的使用計數
new->count = 1;
//設置層號
new->layer = layers-1;
new->prefix = id & idr_layer_prefix_mask(new->layer);
//如果p的位圖滿,則設置p的父節點new的位圖第0位為1,因為new的ary數組0項指向p
if (bitmap_full(p->bitmap, IDR_SIZE))
__set_bit(0, new->bitmap);
//設置p指向新加入的idr_layer節點
p = new;
}
//設置根top指針
rcu_assign_pointer(idp->top, p);
//設置更新idr->layers層數量
idp->layers = layers;
//從idr的top指針指向的idr_layer樹中獲得id號,分配路徑記錄在pa數組中
v = sub_alloc(idp, &id, pa, gfp_mask, layer_idr);
if (v == -EAGAIN)
goto build_up;
return(v);
}
static struct idr_layer *idr_layer_alloc(gfp_t gfp_mask, struct idr *layer_idr)
{
struct idr_layer *new;
//從idr空閑idr_layer鏈表中獲取第一個idr_layer
if (layer_idr)
return get_from_free_list(layer_idr);
//如果idr空閑idr_layer鏈表中已經沒有idr_layer結構,則通過slab高速緩存分配一個idr_layer結構返回
new = kmem_cache_zalloc(idr_layer_cache, gfp_mask | __GFP_NOWARN);
if (new)
return new;
//如果上邊內存分配失敗,則從idr_preload_head數組中分配一個可用的idr_layer結構,參考idr_preload()
if (!in_interrupt()) {//不能在中斷上下文中,要在進程上下文中
//禁止內核強占
preempt_disable();
//從idr_preload_head數組分配一個idr_layer結構
new = __this_cpu_read(idr_preload_head);
if (new) {
//將idr_preload_head指向下一個idr_layer結構
__this_cpu_write(idr_preload_head, new->ary[0]);
//遞減計數
__this_cpu_dec(idr_preload_cnt);
//將new從鏈表中刪除
new->ary[0] = NULL;
}
//使能內核搶占
preempt_enable();
if (new)
return new;
}
//若上邊分配均失敗,則再次嘗試從slab高速緩存分配idr_layer結構
return kmem_cache_zalloc(idr_layer_cache, gfp_mask);
}
static struct idr_layer *get_from_free_list(struct idr *idp)
{
struct idr_layer *p;
unsigned long flags;
spin_lock_irqsave(&idp->lock, flags);
//從idr的free鏈表獲取一個空閑idr_layer
if ((p = idp->id_free)) {
idp->id_free = p->ary[0];//idr空閑鏈表指針指向第二個idr_layer
idp->id_free_cnt--;//idr的空閑idr_layer個數減1
p->ary[0] = NULL;//將該idr_layer從id_free鏈表中刪除
}
spin_unlock_irqrestore(&idp->lock, flags);
return(p);
}
static int idr_max(int layers)
{
//取layers*8和31的小值
//layers小於4層時,id取值可達到2^(layers*8)-1
//layers大於等於4層時,id取值最大取值設置為2^31-1
int bits = min_t(int, layers * IDR_BITS, MAX_IDR_SHIFT);
return (1 << bits) - 1;
}
static int sub_alloc(struct idr *idp, int *starting_id, struct idr_layer **pa,
gfp_t gfp_mask, struct idr *layer_idr)
{
int n, m, sh;
struct idr_layer *p, *new;
int l, id, oid;
id = *starting_id;//起始id號為0
restart:
//找到idr的根top指向的idr_layer
p = idp->top;
//idr中的layers層數量
l = idp->layers;
pa[l--] = NULL;
while (1) {
//n=(id>>8*l) & 0xFF,計算對應的n值,為該layer中的哪個位置。
//若idr中只有1層idr_layer的話,則n值范圍為0~255
n = (id >> (IDR_BITS*l)) & IDR_MASK;
//從位圖中的第n位開始,查找第一個不為0的位,表示該位可用,為1的位表示已經被使用
m = find_next_zero_bit(p->bitmap, IDR_SIZE, n);
//如果找到的空閑位置m等於IDR_SIZE(即256),表示該idr_layer的位圖已經滿了,
//如果該idr_layer有子節點,並且對應該子節點的bit也為1了,表示該子節點的位圖也滿了,
//則需要為該idr增加idr_layer結構
if (m == IDR_SIZE) {
l++;//層數遞加
oid = id;
//重新計算id,該id為被增長之后的新值,即新值根據層數右移8*l位
id = (id | ((1 << (IDR_BITS * l)) - 1)) + 1;
//如果重新計算過的id值,大於目前idr中的idr_layer層數所能設置的最大id值
//則說明該idr不能分配id值了,需要增加idr中的idr_layer層數,出錯返回
if (id >= 1 << (idp->layers * IDR_BITS)) {
*starting_id = id;
return -EAGAIN;
}
p = pa[l];
BUG_ON(!p);
//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;
}
//期望的n值被占用,但可找到可用的m值,重新計算id值
//示例:如果id=0x0A01,則0x0A=10代表第一級的idr_layer的ary數組的索引,0x01代表下一級的ary數組索引,最終ptr數據指針就保存在下一級的ary[0x01]處。
if (m != n) {
sh = IDR_BITS*l;
id = ((id >> sh) ^ n ^ m) << sh;
}
//id超過所能分配的最大值(1 << 31)或者小於0,則出錯返回
if ((id >= MAX_IDR_BIT) || (id < 0))
return -ENOSPC;
//一層層循環計算直到到達葉子節點處l才為0,然后才跳出循環
if (l == 0)
break;
//p的葉子節點m為空
if (!p->ary[m]) {
//從idr空閑鏈表取出一個idr_layer結構,沒有則重新分配一個idr_layer結構
new = idr_layer_alloc(gfp_mask, layer_idr);
if (!new)
return -ENOMEM;
new->layer = l-1;//設置新節點的所在層數
new->prefix = id & idr_layer_prefix_mask(new->layer);
rcu_assign_pointer(p->ary[m], new);//父節點p的葉子m指向new
p->count++;//父節點p的使用計數加1,即表示有多少個字節點
}
pa[l--] = p;//將中間的節點存入pa對應的數組中
p = p->ary[m];//p指向下一個葉子節點
}
//執行到這里,l=0。p指向最終的葉子節點。pa數組記錄id存放在idr的idr_layer樹路徑,最終要存放id的idr_layer葉子節點存放在pa[0]中。
//p為最終要存放數據指針ptr的idr_layer,存入pa[0]數組中,后邊會在該idr_layer的[id & IDR_MASK]處存放數據指針ptr
pa[l] = p;
return id;
}
static void idr_fill_slot(struct idr *idr, void *ptr, int id,struct idr_layer **pa)
{
//pa數組記錄id存放在idr的idr_layer樹路徑,最終要存放id的idr_layer葉子節點存放在pa[0]中。
//將pa[0]存儲的idr_layer結構存入hint域,用於下次快速查找,相當與cache。
rcu_assign_pointer(idr->hint, pa[0]);
//將數據指針地址存入查找到的idr_layer葉子節點的ary數組的[id&IDR_MASK]處
rcu_assign_pointer(pa[0]->ary[id & IDR_MASK], (struct idr_layer *)ptr);
//該idr_layer結構的使用計數加1
pa[0]->count++;
//標志該節點已被使用的bitmap位
idr_mark_full(pa, id);
}
static void idr_mark_full(struct idr_layer **pa, int id)
{
struct idr_layer *p = pa[0];
int l = 0;
//根據id設置該idr_layer的位圖,在該位圖的第id位設為1
__set_bit(id & IDR_MASK, p->bitmap);
//若該idr_layer的整個位圖為滿,則標志該idr_layer的父節點對應的位為1
while (bitmap_full(p->bitmap, IDR_SIZE)) {
//找到該idr_layer的父節點
if (!(p = pa[++l]))
break;
//因為是該idr_layer的父節點,所以id對應的父節點應該右移8位,設置位圖
id = id >> IDR_BITS;
__set_bit((id & IDR_MASK), p->bitmap);
}
}
4.查找id對應的指針
要想找到obj的指針,必須根據id,一路尋找到葉子層。這里假設為2層的話,若id=266,則266/256 = 1,所以從top---->top->ary[1],我們就找到了葉子節點C。266&IDR_MASK = 10,所以C的ary[10]指向管理的obj。
(1)用前面的256進制方法理解就是266 = 1*256+10,所以,top->ary[1]->ary[10]指向obj。
(2)同樣我們可以求id=27對應的obj,27=0*256+27,所以top->ary[0]->ary[27]指向obj。
static inline void *idr_find(struct idr *idr, int id)
{
//hint保留上次操作過的idr_layer指針
struct idr_layer *hint = rcu_dereference_raw(idr->hint);
//比較檢查是否為當前id對應的idr_layer,是的話直接從該idr_layer的ary數組返回數據
if (hint && (id & ~IDR_MASK) == hint->prefix)
return rcu_dereference_raw(hint->ary[id & IDR_MASK]);
//否則從idr樹中查找
return idr_find_slowpath(idr, id);
}
void *idr_find_slowpath(struct idr *idp, int id)
{
int n;
struct idr_layer *p;
if (id < 0)
return NULL;
//找到該idr的top指針
p = rcu_dereference_raw(idp->top);
if (!p)
return NULL;
//top指向的idr_layer的層號加1,就是整個idr的idr_layer樹的層數
n = (p->layer+1) * IDR_BITS;
//如果id號超過該idr中設定的idr_layer層數所能設置的id號最大值,則返回NULL
if (id > idr_max(p->layer + 1))
return NULL;
BUG_ON(n == 0);
//從樹頂部top,往樹葉查找,取出id對應的數組中的數據指針
//假設為兩層,則id值高8位保存上一級的idr_layer的ary數組索引,低8位保存下一級的idr_layer的ary數組索引
while (n > 0 && p) {
n -= IDR_BITS;
BUG_ON(n != p->layer*IDR_BITS);
p = rcu_dereference_raw(p->ary[(id >> n) & IDR_MASK]);
}
return((void *)p);
}
5.idr_replace替換id
void *idr_replace(struct idr *idp, void *ptr, int id)
{
int n;
struct idr_layer *p, *old_p;
if (id < 0)
return ERR_PTR(-EINVAL);
//找到該idr的top指針
p = idp->top;
if (!p)
return ERR_PTR(-EINVAL);
//根據idr層數,設置對應的位數
n = (p->layer+1) * IDR_BITS;
if (id >= (1 << n))
return ERR_PTR(-EINVAL);
//從樹頂部top,往樹葉查找,取出id對應的數組中的數據指針
n -= IDR_BITS;
while ((n > 0) && p) {
p = p->ary[(id >> n) & IDR_MASK];
n -= IDR_BITS;
}
n = id & IDR_MASK;
if (unlikely(p == NULL || !test_bit(n, p->bitmap)))
return ERR_PTR(-ENOENT);
//對應id的ary數組,指針替換
old_p = p->ary[n];
rcu_assign_pointer(p->ary[n], ptr);
return old_p;
}
6.idr_remove/idr_remove_all移除分配的id
void idr_remove(struct idr *idp, int id)
{
struct idr_layer *p;
struct idr_layer *to_free;
if (id < 0)
return;
//釋放id對應的idr_layer路徑的空間
sub_remove(idp, (idp->layers - 1) * IDR_BITS, id);
if (idp->top && idp->top->count == 1 && (idp->layers > 1) && idp->top->ary[0]) {
/*
* Single child at leftmost slot: we can shrink the tree.
* This level is not needed anymore since when layers are
* inserted, they are inserted at the top of the existing
* tree.
*/
to_free = idp->top;
p = idp->top->ary[0];
rcu_assign_pointer(idp->top, p);
--idp->layers;
to_free->count = 0;
bitmap_clear(to_free->bitmap, 0, IDR_SIZE);
free_layer(idp, to_free);
}
while (idp->id_free_cnt >= MAX_IDR_FREE) {
p = get_from_free_list(idp);
/*
* Note: we don't call the rcu callback here, since the only
* layers that fall into the freelist are those that have been
* preallocated.
*/
kmem_cache_free(idr_layer_cache, p);
}
return;
}
static void sub_remove(struct idr *idp, int shift, int id)
{
struct idr_layer *p = idp->top;
struct idr_layer **pa[MAX_IDR_LEVEL + 1];
struct idr_layer ***paa = &pa[0];
struct idr_layer *to_free;
int n;
*paa = NULL;
*++paa = &idp->top;
//循環將存儲id的idr_layer樹路徑保存在數組paa中
while ((shift > 0) && p) {
n = (id >> shift) & IDR_MASK;
__clear_bit(n, p->bitmap);
*++paa = &p->ary[n];
p = p->ary[n];
shift -= IDR_BITS;
}
//遍歷paa數組,將數組中的idr_layer結構都釋放掉
n = id & IDR_MASK;
if (likely(p != NULL && test_bit(n, p->bitmap))) {
__clear_bit(n, p->bitmap);
rcu_assign_pointer(p->ary[n], NULL);
to_free = NULL;
while(*paa && ! --((**paa)->count)){
if (to_free)
free_layer(idp, to_free);
to_free = **paa;
**paa-- = NULL;
}
if (!*paa)
idp->layers = 0;
if (to_free)
free_layer(idp, to_free);
} else
idr_remove_warning(id);
}
7.idr_destroy銷毀空閑idr_layer鏈表
void idr_destroy(struct idr *idp)
{
__idr_remove_all(idp);
//遍歷idr的free鏈表中的idr_layer結構,依次取出並釋放內存空間
while (idp->id_free_cnt) {
struct idr_layer *p = get_from_free_list(idp);
kmem_cache_free(idr_layer_cache, p);
}
}
void __idr_remove_all(struct idr *idp)
{
int n, id, max;
int bt_mask;
struct idr_layer *p;
struct idr_layer *pa[MAX_IDR_LEVEL + 1];
struct idr_layer **paa = &pa[0];
n = idp->layers * IDR_BITS;
//取出idr的top指針
p = idp->top;
//top指針置為NULL
rcu_assign_pointer(idp->top, NULL);
//計算該idr中設定的idr_layer層數所能設置的id號最大值
max = idr_max(idp->layers);
id = 0;
while (id >= 0 && id <= max) {
while (n > IDR_BITS && p) {
n -= IDR_BITS;
*paa++ = p;
p = p->ary[(id >> n) & IDR_MASK];
}
bt_mask = id;
id += 1 << n;
/* Get the highest bit that the above add changed from 0->1. */
while (n < fls(id ^ bt_mask)) {
if (p)
free_layer(idp, p);
n += IDR_BITS;
p = *--paa;
}
}
idp->layers = 0;
}
結構圖如下:


