linux核心IDR機制詳解【轉】

yooooooo發表於2019-03-15

這幾天在看Linux核心的IPC名稱空間時候看到關於IDR的一些管理性質的東西,剛開始看有些迷茫,深入看下去豁然開朗的感覺,把一些心得輸出共勉。

我們來看一下什麼是IDR?IDR的作用是什麼呢?

先來看下IDR的作用:IDR主要實現ID與資料結構的繫結。剛開始看的時候感覺到有點懵,什麼叫“ID與資料結構的繫結”?舉一個例子大家就會明白了:在IPC通訊的時候先要動態獲取一個key值或者使用現有的key值進行通訊,那麼系統怎麼知道這個key值是否使用了呢?這個就需要IDR來進行判斷了。以上就是IDR的一些淺顯的概念,IDR本質上就是通過對於ID一些有效的管理進而管理和這些ID有關的資料結構----不限於IPC通訊的key值。

IDR怎麼對於資料ID管理呢?傳統上我們對於未使用的ID進行管理的時候可以使用點陣圖進行管理,也可以使用陣列進行管理,也可以使用連結串列進行ID管理,三個個各有優缺點:

  1. 使用點陣圖進行管理的時候優點是使用空間少,但是對於點陣圖對應的資料結構支援不太友好。
  2. 使用陣列進行管理的時候定址快速,但是隻能管理比較少量的ID數目。
  3. 使用連結串列進行管理的時候雖然可以支援大量的資料ID,但是通過連結串列的指標定址比較慢。

所以引入了以上三者的優點進行IDR管理。

IDR管理的核心

IDR把每一個ID分級資料進行管理,每一級維護著ID的5位資料,這樣就可以把IDR分為7級進行管理(5*7=35,維護的資料大於32位),如下所示:

31 30 | 29 28 27 26 25 | 24 23 22 21 20 | 19 18 17 16 15 | 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0

例如資料ID為0B 10 11111 10011 00111 11001 100001 00001,定址如下:

1. 第一級定址 ary1[0b10]得到第二級地址ary2[]
 2. ary3 = ary2[0b11111]
 3. ary4 = ary3[ob10011]
 4. ary5 = ary4[0b00111]
 5. ary6 = ary5[0b11001]
 6. ary7 = ary6[0b100001]
 7. ary8 = ary7[0b00001]

ary8即為要定址到的ID對應的IDR指標。

如下圖:
image

上圖中每一個分級中的IDR陣列中的值不為空代表相應位有效的ID位,但是使用陣列下標標示有效的ID位還是有點慢----需要通過陣列下標以及陣列內容判斷有效的ID位,所以對於每一個IDR引入了有效的ID點陣圖來表示,每一個點陣圖為32位剛好給出了相應的有效的ID位。方便查詢。

上圖中只是使用了IDR的32個陣列表示,並沒有給出IDR的點陣圖以及層數標誌,下面給出相應的資料結構:

IDR 資料結構:

struct idr_layer {
    //點陣圖,ary陣列結構哪個有效
        unsigned long            bitmap; /* A zero bit means "space here" */
        //IDR陣列
        struct idr_layer __rcu  *ary[1<<IDR_BITS];
        標示
        int                      count;  /* When zero, we can release it */
        //層數,代表所在的ID位
        int                      layer;  /* distance from leaf */
        struct rcu_head          rcu_head;
};

struct idr {
    //IDR層數頭,實際上就是32叉樹
        struct idr_layer __rcu *top;
    //尚未使用的IDR
        struct idr_layer *id_free;
        //層數
        int               layers; /* only valid without concurrent changes */
        //id_free未用的個數;
        int               id_free_cnt;
        spinlock_t        lock;
};

下面討論一下IDR的初始化以及增刪改查ID問題:

  1. IDR的初始化
  2. IDR的增加
  3. IDR的查詢

IDR的初始化:

IDR的初始化相對來說比較簡單,使用IDR_INIT可以初始化一個IDR,原型如下:

#define IDR_INIT(name)                                          \
{                                                               \
        .top            = NULL,                                 \
        .id_free        = NULL,                                 \
        .layers         = 0,                                    \
        .id_free_cnt    = 0,                                    \
        .lock           = __SPIN_LOCK_UNLOCKED(name.lock),      \
}

可以看到IDR只是把各個資料值為零,原子鎖初始化下。

IDR的增加:

IDR增加比較複雜,在C中程式設計大部分情況可以分為如下兩點討論:

1.idr.top為NULL的情況;
 2.idr.top不為NULL的情況;
 以上考慮問題也是可以的,但是沒有考慮到如下問題:
 每一個idr_layer結構體有一個layer標示,我們每每增加一層,就要遍歷整個idr的32叉樹。無形中增加了系統負擔。

idr設計者在考慮問題時候恰恰相反,沒增加一個idr_layer層,就把要增加的idr_layer->ary[0]指向舊的idr_layer樹的根,把要增加idr_layer->layer賦予舊的根部的idr_layer->layer + 1值,這樣就不會考慮到idr->top為NULL的情況了。ps:只需要判斷在增加第一個idr_layer時候判斷一下,並且把idr_layer->layer值賦為0.

IDR的查詢:

在查詢IDR時侯會先查詢IDR根節點,然後根據ID位所在的層的值遍歷IDR樹,如果查詢到某一段樹為NULL,則會返回NULL。

以下是IDR查詢的過程:

void *idr_find(struct idr *idp, int id)                         
{       
        int n;
        struct idr_layer *p;                                    
        //獲取根IDR
        p = rcu_dereference_raw(idp->top);
        if (!p)
                return NULL;
        /**
        根據IDR的層數獲取要遍歷的個數;
    **/
        n = (p->layer+1) * IDR_BITS;

        /* 去除我們不需要查詢的位數. */
        id &= MAX_ID_MASK;
    /***如果ID值大於n, 1<<n為根據層數換算過來的ID的最大值**/
        if (id >= (1 << n))
                return NULL;
        BUG_ON(n == 0);
    /***
        遍歷順序:28---->0,每次減少5位,可以遍歷完全IDR的32叉樹
    ***/
        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);
}

相關文章