一種SDN路由轉發流表實現方法

 

1.        背景與需求

1.1    背景

傳統上,路由器採用Radix樹進行IP路由存儲,但是到了SDN中,流表的key不一定是IP地址,所以就需要對路由表的存儲更換一種算法,可以達到快速增、刪、改、查的效果。由於KEY的類型不確定,設置無法比較大小,所以Hash表是一種不錯的選擇。

一般的HASH表都是通過數組(哈希桶)和鏈表的組合而成,如下圖所示。數據元素的key通過給定的hash算法計算出所在數組的索引號,然後再將數據元素插入對應的鏈中。這種HASH表的特點是結構簡單,便於管理,但是由於鏈表是順序訪問且節點是臨時分配,無法達到路由表的使用性能,所以就需要一種增強型路由表來用於路由器的流表管理。同時,軟路由類型的路由器一般都是基於多核進行開發的,所以路由表需要在多核間進行共享,並且同時需要保證路由表在多核間的一致性。這種增強型的HASH表叫做EHASH(Enhanced Hash Table)


圖 1  傳統HASH表

 

1.2需求

EHASH路由流表需求如下:

a.      可以快速分配出路由元素

b.      在衝突鏈上快速查找

c.      可以對HASH進行統計計數

d.      多個轉發核間共享路由流表數據

e.      多個轉發核間以最高速度進行互斥訪問

 

1.3設計思路

目前SDN路由器採用虛擬化的方案,會有多臺負責數據轉發的虛擬機,每個虛擬機中都會有一個或多個轉發核。爲了在所有的轉發核間共享路由轉發流表,需要在Hypervisor層上管理一個很大的內存,這塊內存可以在各個虛擬機中訪問到。具體Hypervisor的內存管理方式不是本文描述的重點,只要知道路由轉發流表所用的內存都是從這塊共享內存中分配出來的即可。

爲了滿足快速查找需求,採用HASH作爲主體算法,通過HASH來進行快速查找;爲了滿足快速分配和快速訪問的需求,這裏採用利用數組預分配表項資源,通過數組索引來快速的訪問內存,因爲是預分配所以需要提前消耗比較大的共享內存,是一種典型的空間換時間的算法。

 

EHASH的算法結構如下圖所示:


圖 2 EHASH算法結構

 

圖 3 Pool區域初始化爲數組鏈表

 

HASH桶數組用來完成HASH,經過HASH函數計算後的key就是HASH桶數組的索引。HASH桶數組元素就是衝突鏈的表頭。通過NextIndex指向該HASH值的衝突鏈。如果NextIndex爲全F,那麼就表示該HASH值對應的HASH元素上沒有衝突鏈。HASH桶數組叫做主表,即MainTable,其中的數組元素都從MAIN_TABLE內存區分配出來。

         如上面圖所示,Pool內存區會整個初始化爲一個大數組,通過NextIndex會將這個大數組連接爲一個大的鏈表。NextIndex存儲的內容是一個元素在POOL_TABLE內存區數組中的索引下標。所有的衝突鏈上的元素都是從POOL_TABLE內存區上分配出來,當衝突節點被釋放收,會修改釋放節點的NextIndex,使其連接到Pool內存數組的下一個空閒元素上。

         當多個插入的數據對應了同一個HASH KEY的時候,就會在同一個HASH KEY上形成衝突鏈。衝突鏈叫做PoolTable,所有的衝突鏈上的元素都是從POOL_TABLE內存區上分配出來。衝突鏈的第一個數據存儲在HASH桶數組元素中,然後通過NextIndex指向衝突鏈的下一個元素;同理,下一個元素通過NextIndex指向再下一個衝突鏈元素,如此類推,直到最後一個衝突鏈元素,最後一個衝突鏈元素的NextIndex爲0xFFFFFFFF,表示鏈表沒有更多的元素了。通過NextIndex指向下一個元素在Pool內存數組中的索引下標,將預分配好的離散的數組元素組合成爲一個鏈表。

         爲了滿足多轉發核間對路由表的互斥訪問,所以需要通過鎖來進行保護。同時因爲路由流表的讀操作多餘寫操作,所以通過讀寫鎖對內存進行保護。如前文所述,目前採用虛擬化方案,會存取多個OS,所以鎖使用內存也是在Hypervisor維護的共享內存上。這樣所有的虛擬機都可以正常訪問到所,以便互斥地訪問路由流表資源。

         同時對EHASH增加了統計表,如下圖所示。統計表同HASH桶的元素一一對應,也就是說和HASH桶的大小相同。統計表中的每個元素記錄了衝突鏈上衝突元素的數量,以及歷史上該HASH KEY衝突的最大數量。通過觀察統計數量,可以發現衝突最多的HASH KEY,從而調整HASH KEY的算法,使其對數據散列的更加平衡。


圖 4 EHASH統計表

 

 

2.        主要數據結構

 

2.1共享內存佈局

如前文所述,目前採用虛擬化方案,會存取多個負責轉發的虛擬機,爲了讓多個虛擬機之間共享路由轉發流表等信息,需要在Hypervisor層上管理一塊內存,這塊內存可以在各個虛擬機中訪問到。共享內存佈局如下所示


圖 5 共享內存佈局

 

  • MAIN_TABLE 內存區:EHASH的HASH桶的元素的內存。Addr爲內存區其實地址,Len爲內存區長度。
  • POOL_TABLE 內存區:EHASH的衝突鏈元素的內存。Addr爲內存區其實地址,Len爲內存區長度。
  • STATISTICS_TABLE 內存區:EHASH統計表元素的內存。Addr爲內存區其實地址,Len爲內存區長度。
  • SPINLOCK_TABLE 內存區:EHASH表自旋鎖內存。Addr爲內存區其實地址,Len爲內存區長度。
  • WRLOCK_TABLE 內存區: EHASH表讀寫鎖內存。Addr爲內存區其實地址,Len爲內存區長度。

 

每個內存區都被視爲是數組,而且內存區是相鄰的,所以通過數組元素的地址,可以元素下標索引值,也可以通過元素下標索引值計算出元素對應的地址。數組元素索引和元素地址轉換通過如下兩個宏可以完成:

  • EHASH_NODE_ENTRY:通過內存區基地址和元素索引,計算得到元素的地址;
  • EHASH_NODE_INDEX: 通過內存區基地址和元素地址,計算得到元素在數組中的索引下標

相關代碼如下:

#define EHASH_NODE_ENTRY(base,index,entrysize,type)  \
    (((index) == EHASH_INDEX_INVALID) ? NULL :((type *)((u32)(base) + (index) * (entrysize))))
 
/*得到指定爲值off處元素的Index值*/
#define EHASH_NODE_INDEX(base,off,entrysize)  \
        (((u32)(off) -(u32)(base))/(entrysize))

 

 

2.2EHASH管理結構


圖 6 EHASH關鍵管理結構

 

EHASH_S結構是EHASH表的表管理結構,主要成員如下:

  • pHashMainTbl:指向MAIN_TABLE內存區,將該內存區視爲數組,每個數組元素爲HASH表的節點數據。
  • pHashPool: 指向POOL_TABLE內存區,將該內存區視爲數組,每個數組元素爲HASH表的節點數據。當有HASH節點從POOL_TABLE內存區中分配出去,該指針指向POOL_TABLE內存區中下一個空閒的數組元素。
  • pHashStatTbl:指向STATISTICS_TABLE內存區,將該內存區視爲數組,每個數組元素爲HASH表統計數據結構。
  • pHashMainRwLockTbl:指向WRLOCK_TABLE內存區,將該內存區視爲數組,每個數組元素爲HASH表的讀寫鎖結構。
  • pHashGlbResSpinlock:指向SPINLOCK_TABLE內存區,將該內存區視爲數組,每個數組元素爲HASH表的自旋鎖結構。
  • pfEntryDataInitFunc(): HASH表節點數據初始化接口,把外部拷貝的數據填充到HASH表的節點中。
  • pfEntryDataClear():HASH表節點數據清除接口
  • pfStatEntryAdd(): HASH統計表中統計節點計數增加接口。
  • pfStatEntryDel(): HASH統計表中統計節點計數減少接口。
  • pfHashWriteLock():對HASH表上讀鎖。
  • pfHashWriteUnLock():對HASH表解除讀鎖。

EHASH_NODE_S結構是HASH節點的管理結構,在EHASH_NODE_S結構後緊接着的是具體的業務數據,HASH節點的KEY來自於具體的業務數據。EHASH_NODE_S結構和其後跟着的業務數據一起構成了HASH表的節點數據。主要成員如下:

  • ucValid:標明該節點的數據是否有效,當衝POOL_TABLE內存區分配出來的時候,ucValid會被標記爲1,釋放到POOL_TABLE中的時候,ucValid被設定爲0。修改節點數據前ucValid設爲0,然後修改數據,修改後再將ucValid設定爲1.
  • ulNextIndex:下一個元素在POOL_TABLE內存數組中的數組下標。POOL_TABLE內存數組通過ulNextIndex將一個個離散或相鄰的數組元素串聯成爲一個鏈表。鏈表最後一個元素的ulNextIndex爲0xFFFFFFFF

 

EHASH_STAT_S結構是EHASH統計表的元素,EHASH統計表的每個元素對應一個EHASH表的HASH KEY,統計表同HASH桶的元素一一對應。統計表中的每個元素記錄了衝突鏈上衝突元素的數量,以及歷史上該HASH KEY衝突的最大數量。主要成員有:

  • usHistoryMaxNum:歷史上該HASH KEY衝突的最大數量
  • usCurMaxNum:該HASH KEY上當前衝突元素的數量

 

相關核心代碼如下:

typedef struct tagEHASH_NODE
{
    volatile u8 ucValid;
    u8 ucRsvd1;
    u16 usRsvd2;
    volatile u32 ulNextIndex;
} EHASH_NODE_S;
 
typedef struct tagEHASH_STAT
{
    u16 usHistoryMaxNum;
    u16 usCurMaxNum;
} EHASH_STAT_S;
 
typedef EHASH_NODE_S EHASH_BACKET_S;
 
typedef struct tagEHASH
{
    EHASH_BACKET_S *pHashMainTbl;
    EHASH_NODE_S *pHashPool;
    EHASH_STAT_S *pHashStatTbl;
 
    u32 ulSize;
    u32 ulPoolSize;
    u32 ulStatSize;
    u32 ulEntrySize;
    u32 ulStatEntrySize;
 
    u32(*pfEhashKey)();
    s32(*pfEhashCmp)();
    void (*pfEntryDataInitFunc)(void *, void *);
    void (*pfEntryDataClear)(void *, u32);
    void (*pfStatEntryAdd)(void *, u32);
void (*pfStatEntryDel)(void *, u32);
 
    u32 ulCount;
    void *pHashMainRwLockTbl;     /*讀寫鎖基地址*/
    u32 ulRwLockEntrySize;        /*一個讀寫鎖佔用的內存大小*/
    void *pHashGlbResSpinlock;    /*全局資源鎖*/
 
              /*hash鎖操作*/
    void (*pfHashReadLock)(void *, u32);
    void (*pfHashReadUnLock)(void *, u32);
    void (*pfHashWriteLock)(void *, u32);
    void (*pfHashWriteUnLock)(void *, u32);
    void (*pfGlbResSpinLock)();
    void (*pfGlbResSpinUnLock)();
} EHASH_S;
 
 

3.        EHASH關鍵流程

3.1衝突節點分配流程

HASH表衝突節點的從POOL_TABLE內存區中分配得到的,分配流程如下


圖 7 衝突節點分配流程

核心代碼如下:

/*從哈希資源表中分配一個新節點(EHASH模塊的內部函數)*/
EHASH_NODE_S *hashEntryAlloc(EHASH_S *pstEhcb)
{
    EHASH_NODE_S *pstNewNode = NULL;
    EHASH_NODE_S *pstHashPool = NULL;
    u32 ulEntrySize = pstEhcb->ulEntrySize;
 
    (*pstEhcb->pfGlbResSpinLock)(pstEhcb);  /*鎖一把*/
 
    pstHashPool = pstHashPool = pstEhcb->pHashPool;
    if (pstHashPool != NULL)
    {
        pstNewNode = pstHashPool;
        pstHashPool = EHASH_NODE_ENTRY(pstEhcb->pHashMainTbl, pstNewNode->ulNextIndex, ulEntrySize, EHASH_NODE_S);
        pstNewNode->ulNextIndex = EHASH_INDEX_INVALID;
        pstEhcb->pHashPool = pstHashPool;
 
        /*添加成功,記錄添加個數*/
        pstEhcb->ulCount++;
 
        (*pstEhcb->pfGlbResSpinUnLock)(pstEhcb);  /*解鎖*/
        return pstNewNode;
    }
    else
    {
        (*pstEhcb->pfGlbResSpinUnLock)(pstEhcb);  /*解鎖*/
        return NULL;
    }
}



3.2衝突節點釋放流程
 

HASH表衝突節點的需要釋放到POOL_TABLE內存區中,釋放流程如下:

圖 8 衝突節點釋放流程

 

核心代碼如下:

/*向哈希資源表中釋放一個節點(EHASH模塊的內部函數)*/
void hashEntryFree(EHASH_S *pstEhcb, EHASH_NODE_S *pstNode)
{
    EHASH_NODE_S *pstHashPool = NULL;
 
    if (pstEhcb == NULL || pstNode == NULL)
    {
        return;
    }
 
    (*pstEhcb->pfGlbResSpinLock)(pstEhcb);  /*鎖一把*/
 
    pstHashPool = pstEhcb->pHashPool;
    /*衝突表用光的情況下,要判斷pstHashPool == NULL的情況*/
    if (NULL != pstHashPool)
    {
        pstNode->ulNextIndex = EHASH_NODE_INDEX(pstEhcb->pHashMainTbl, pstHashPool, pstEhcb->ulEntrySize);
        pstEhcb->pHashPool = pstNode;
    }
    else
    {
        pstNode->ulNextIndex = EHASH_INDEX_INVALID;
        pstEhcb->pHashPool = pstNode;
    }
 
    pstEhcb->ulCount--;
 
(*pstEhcb->pfGlbResSpinUnLock)(pstEhcb);  /*解鎖*/
 
    return;
}


3.3哈希表資源初始化流程

初始化HASH表所用的所有資源,包括回調函數,共享內存區等,流程如下:

圖 9 哈希表資源初始化流程

 

核心代碼如下:

/*
 *創建哈希表
 *該函數實際上只創建了哈希控制塊,這是因爲使用EHASH模塊所創建的哈希表用於多核
 *共享,所以只需要在主核上初始化哈希表項資源,從核只需初始化好哈希控制塊即可。
 *基於上述原因,EHASH模塊將哈希控制塊的初始化與哈希表項初始化分割開。在主核上
 *需要依次調用EHASH_HashNew()和EHASH_HashTblInit()來完成初始化動作;而在從核
 *上只需調用EHASH_HashNew()函數創建哈希控制塊即可操縱哈希表了。
 */
EHASH_S * EHASH_HashNew(u32(*pfEhashKey)(), s32(*pfEhashCmp)(),
                        void (*pfEntryDataInitFunc)(void *, void *),
                        void*(*pfMallocFunc)(u32), void (*pfEntryDataClear)(void *, u32),
                        void (*pfStatEntryAdd)(void *, u32), void (*pfStatEntryDel)(void *, u32))
{
    EHASH_S *pstEhcb = NULL;
 
    if (pfMallocFunc == NULL || pfEhashKey == NULL || pfEhashCmp == NULL
            || pfEntryDataInitFunc == NULL || pfEntryDataClear == NULL)
    {
        return NULL;
    }
             
    pstEhcb = (EHASH_S *)pfMallocFunc(sizeof(EHASH_S));
    if (pstEhcb == NULL)
    {
        return NULL;
    }
 
    pstEhcb->pfEhashKey = pfEhashKey;
    pstEhcb->pfEhashCmp = pfEhashCmp;
    pstEhcb->pfEntryDataInitFunc = pfEntryDataInitFunc;
    pstEhcb->pfEntryDataClear = pfEntryDataClear;
 
    /*初始化統計函數*/
    if (pfStatEntryAdd == NULL)
    {
        pstEhcb->pfStatEntryAdd = EHASH_HashCommonStatAdd;
    }
    else
    {
        pstEhcb->pfStatEntryAdd = pfStatEntryAdd;
    }
 
    if (pfStatEntryDel == NULL)
    {
        pstEhcb->pfStatEntryDel = EHASH_HashCommonStatDel;
    }
    else
    {
        pstEhcb->pfStatEntryDel = pfStatEntryDel;
    }
 
    /*初始化讀寫鎖和spinlock的接口函數*/
    pstEhcb->pfHashReadLock    = EHASH_HashReadLock;
    pstEhcb->pfHashReadUnLock  = EHASH_HashReadUnLock;
    pstEhcb->pfHashWriteLock   = EHASH_HashWriteLock;
    pstEhcb->pfHashWriteUnLock = EHASH_HashWriteUnLock;
    pstEhcb->pfGlbResSpinLock  = EHASH_GlbResSpinLock;
    pstEhcb->pfGlbResSpinUnLock = EHASH_GlbResSpinUnLock;
    return pstEhcb;
}
 
/*
 *初始化哈希主表和哈希資源表
 *pEhashMainTblAddr指向待初始化的哈希主表的首地址
 *ulSize爲哈希主表的大小,即通常我們所說的哈希表大小
 *pEhashPoolAddr指向待初始化的哈希資源表的首地址,哈希資源表其實就是一個由哈
 *希空閒節點所組成的大鏈表
 *ulPoolSize爲哈希資源表的大小
 *ulEntrySize爲當前初始化的哈希表中每一個哈希節點的實際大小
 */
s32 EHASH_HashTblInit(EHASH_S *pstEhcb,
                      void *pEhashMainTblAddr, u32 ulSize,
                      void *pEhashPoolAddr, u32 ulPoolSize,
                      u32 ulEntrySize, void *pEhashStatTblAddr,
                      u32 ulStatSize, u32 ulStatEntrySize,
                      void *pMainRwLockTblAddr, u32 ulRwLockEntrySize,
                      void *pEhashResSpinlock)
{
    if (pstEhcb == NULL || pEhashMainTblAddr == NULL || pEhashPoolAddr == NULL
            || pEhashStatTblAddr == NULL)
    {
        return ERROR;
    }
 
    if (pMainRwLockTblAddr == NULL || pEhashResSpinlock == NULL)
    {
        return ERROR;
    }
    pstEhcb->pHashMainTbl = pEhashMainTblAddr;
    pstEhcb->pHashPool = pEhashPoolAddr;
    pstEhcb->pHashStatTbl = pEhashStatTblAddr;
 
    pstEhcb->ulSize = ulSize;
    pstEhcb->ulPoolSize = ulPoolSize;
    pstEhcb->ulStatSize = ulStatSize;
    pstEhcb->ulEntrySize = ulEntrySize;
    pstEhcb->ulStatEntrySize = ulStatEntrySize;
 
    /*add by dengjunjun_107 on 2012-4-10 支持ehash的鎖機制,資源鏈用spinlock,ehash鏈用rwlock begin*/
    pstEhcb->pHashMainRwLockTbl = pMainRwLockTblAddr;
    pstEhcb->ulRwLockEntrySize = ulRwLockEntrySize;
    pstEhcb->pHashGlbResSpinlock = pEhashResSpinlock;
    return OK;
}
s32 EHASH_HashTblResInit(EHASH_S *pstEhcb)
{
    u32 ulSize = pstEhcb->ulSize;
    u32 ulPoolSize = pstEhcb->ulPoolSize;
    u32 ulStatSize = pstEhcb->ulStatSize;
    u32 ulEntrySize = pstEhcb->ulEntrySize;
    u32 ulStatEntrySize = pstEhcb->ulStatEntrySize;
    EHASH_NODE_S *pstEhashNode = NULL;
    s32 i = 0;
    cvmx_rwlock_wp_lock_t *pstMainRwLockTbl = NULL;
    cvmx_spinlock_t * pstGlbResSpinlock = NULL;
 
    if (pstEhcb == NULL || pstEhcb->pHashMainTbl == NULL || pstEhcb->pHashPool == NULL
            || pstEhcb->pHashStatTbl == NULL)
    {
        return ERROR;
}
 
    if (pstEhcb->pHashMainRwLockTbl == NULL || pstEhcb->pHashMainRwLockTbl == NULL)
    {
        return ERROR;
    }
 
    bzero(pstEhcb->pHashMainTbl, (ulSize * ulEntrySize));
    for (i = 0, pstEhashNode = pstEhcb->pHashMainTbl; i < ulSize; i++)
    {
        pstEhashNode = EHASH_NODE_ENTRY(pstEhcb->pHashMainTbl, i, ulEntrySize, EHASH_NODE_S);
        pstEhashNode->ulNextIndex = EHASH_INDEX_INVALID;
    }
 
    bzero(pstEhcb->pHashPool, (ulPoolSize * ulEntrySize));
    for (i = 0, pstEhashNode = pstEhcb->pHashPool; i < (ulPoolSize - 1); i++)
    {
        pstEhashNode->ulNextIndex = ulSize + i + 1;
        pstEhashNode = EHASH_NODE_ENTRY(pstEhcb->pHashMainTbl, pstEhashNode->ulNextIndex, ulEntrySize, EHASH_NODE_S);
    }
    pstEhashNode->ulNextIndex = EHASH_INDEX_INVALID;
 
    /*清零 統計表信息,統計*/
    bzero(pstEhcb->pHashStatTbl, (ulStatSize * ulStatEntrySize));
 
    UOS_ASSERT(sizeof(cvmx_rwlock_wp_lock_t) <= pstEhcb->ulRwLockEntrySize);
    /*初始化hash主表的讀寫鎖*/
    pstMainRwLockTbl = (cvmx_rwlock_wp_lock_t *) pstEhcb->pHashMainRwLockTbl;
    for (i = 0; i < ulSize; i++)
    {
        cvmx_rwlock_wp_init(&pstMainRwLockTbl[i]);
    }
 
    /*初始化資源表的全局spinlock*/
    pstGlbResSpinlock = (cvmx_spinlock_t *)pstEhcb->pHashGlbResSpinlock;
    cvmx_spinlock_init(pstGlbResSpinlock);
    return OK;
}



3.4哈希查找流程
 

輸入爲指定要查找的內容,該內容中含所有關鍵字KEY,通過KEY可以計算得到散列值HASH KEY,在EHASH中進行查找,如果找到那麼返回找到的元素,流程如下:

圖 10哈希查找流程

 

核心代碼如下:

/*
 *哈希查找函數,pstEhcb指向哈希表控制塊,pEhashEntryData指向待查找的數據
 *哈希查找函數通常會被控制面和數據面的代碼一起使用,旨在實現無阻塞的哈希
 *查找。
 */
void * EHASH_HashSearch(EHASH_S *pstEhcb, void *pEhashEntryData)
{
    u32 ulKey;
    EHASH_BACKET_S *pstHashBacket = NULL;
    EHASH_NODE_S *mp = NULL;
    u32 ulEntrySize = pstEhcb->ulEntrySize;
    u8 ucFlag = 0;
 
    ulKey = (*pstEhcb->pfEhashKey)(pEhashEntryData);
    (*pstEhcb->pfHashReadLock)(pstEhcb, ulKey);
    pstHashBacket = (EHASH_BACKET_S *)((void *)pstEhcb->pHashMainTbl + ulEntrySize * ulKey);
 
    for (mp = pstHashBacket;
            (mp != NULL && mp->ucValid == 1);
            mp = EHASH_NODE_ENTRY(pstEhcb->pHashMainTbl, mp->ulNextIndex, ulEntrySize, EHASH_NODE_S))
    {
        if ((*pstEhcb->pfEhashCmp)(mp, pEhashEntryData) == 0)
        {
            ucFlag = 1;
            break;
        }
    }
    (*pstEhcb->pfHashReadUnLock)(pstEhcb, ulKey);
    if (ucFlag)
    {
        return mp;
    }
    else
    {
        return NULL;
    }
}


5.5向哈希表中插入一個新節點

輸入爲要插入的內容,該內容中含所有關鍵字KEY,通過KEY可以計算得到散列值HASH KEY。最終函數將帶插入的內容插入到HASH表中。流程如下


圖 11 向哈希表中插入一個新節點

 

核心代碼如下:

/*
 *向哈希表中壓入一個新節點,pstEhcb指向哈希表控制塊,pfInitFunc函數指針用於
 *爲不同數據類型的哈希節點進行初始化,pEhashEntryData用於爲新創建的節點提供
 *數據。
 */
s32 EHASH_HashPush(EHASH_S *pstEhcb, void *pEhashEntryData)
{
    u32 ulKey;
    u8 ucFlag = 0;
    EHASH_BACKET_S *pstHashBacket = NULL;
    EHASH_STAT_S *pstHashStatBacket = NULL;
    EHASH_NODE_S *mp, *pstNewNode = NULL;
    u32 ulEntrySize = pstEhcb->ulEntrySize;
    u32 ulStatEntrySize = pstEhcb->ulStatEntrySize;
 
    ulKey = (*pstEhcb->pfEhashKey)(pEhashEntryData);
 
    (*pstEhcb->pfHashWriteLock)(pstEhcb, ulKey);
 
    pstHashStatBacket = EHASH_NODE_ENTRY(pstEhcb->pHashStatTbl, ulKey, ulStatEntrySize, EHASH_STAT_S);
    pstHashBacket = EHASH_NODE_ENTRY(pstEhcb->pHashMainTbl, ulKey, ulEntrySize, EHASH_BACKET_S);
 
    for (mp = pstHashBacket;
            (mp != NULL && mp->ucValid == 1);
            mp = EHASH_NODE_ENTRY(pstEhcb->pHashMainTbl, mp->ulNextIndex, ulEntrySize, EHASH_NODE_S))
    {
        if ((*pstEhcb->pfEhashCmp)(mp, pEhashEntryData) == 0)
        {
            ucFlag = 1;
            break;
        }
    }
 
    if (ucFlag == 1)
    {
        /*既然是push操作,且找到了相同的節點,那麼直接用新的數據覆蓋舊的數據,
         *在內層實現覆蓋更新操作,是否需要覆蓋更新則可在外部進行判斷,這樣可以
         *更新時如果發現節點存在,不需要將原有節點刪除,只需要將原有節點中需要
         *保存的部分內容以及需要更新的內容push入即可,也保證了更新操作在鎖的保護
         *下*/
        bcopy((void *)(pEhashEntryData + sizeof(EHASH_NODE_S)), (void *)(mp + 1),
              (ulEntrySize - sizeof(EHASH_NODE_S)));
        (*pstEhcb->pfHashWriteUnLock)(pstEhcb, ulKey);
        return OK;
}
 
    if (pstHashBacket->ucValid == 0)    /*在空表中添加節點*/
    {
        (*pstEhcb->pfEntryDataInitFunc)(pstHashBacket, pEhashEntryData);
        pstHashBacket->ucValid = 1;
    }
    else    /*在衝突表中添加節點*/
    {
        pstNewNode = hashEntryAlloc(pstEhcb);
        if (pstNewNode == NULL)
        {
            UOS_ASSERT(0);
            (*pstEhcb->pfHashWriteUnLock)(pstEhcb, ulKey);
            return ERROR;
        }
 
        (*pstEhcb->pfEntryDataInitFunc)(pstNewNode, pEhashEntryData);
        pstNewNode->ucValid = 1;
 
        for (mp = pstHashBacket;
                (mp->ucValid == 1 && mp->ulNextIndex != EHASH_INDEX_INVALID);
                mp = EHASH_NODE_ENTRY(pstEhcb->pHashMainTbl, mp->ulNextIndex, ulEntrySize, EHASH_NODE_S));
 
        pstHashBacket->ucValid = 0;
 
        mp->ucValid = 0;
        mp->ulNextIndex = EHASH_NODE_INDEX(pstEhcb->pHashMainTbl, pstNewNode, ulEntrySize);
 
        mp->ucValid = 1;
        pstHashBacket->ucValid = 1;
    }
 
    /*增加節點,回調統計信息*/
    (*pstEhcb->pfStatEntryAdd)(pstHashStatBacket, ulStatEntrySize);
    (*pstEhcb->pfHashWriteUnLock)(pstEhcb, ulKey);
 
    return OK;
}



3.6從哈希表中彈出(刪除)指定節點
 

輸入爲要刪除的內容,該內容中含所有關鍵字KEY,通過KEY可以計算得到散列值HASH KEY。最終函數將從HASH表中刪除指定內容的節點。流程如下

 

圖 12 從哈希表中彈出(刪除)指定節點

 

核心代碼如下:

/*
 *從哈希表中彈出(刪除)待查找的節點,pstEhcb指向哈希表控制塊,pEhashEntryData
 *指向待查找的數據。
 */
s32 EHASH_HashPull(EHASH_S *pstEhcb, void *pEhashEntryData)
{
    u32 ulKey;
    EHASH_BACKET_S *pstHashBacket = NULL;
    EHASH_STAT_S *pstHashStatBacket = NULL;
    EHASH_NODE_S *mp, *pstPrevNode = NULL;
    u32 ulEntrySize = pstEhcb->ulEntrySize;
    u32 ulStatEntrySize = pstEhcb->ulStatEntrySize;
 
    ulKey = (*pstEhcb->pfEhashKey)(pEhashEntryData);
 
    (*pstEhcb->pfHashWriteLock)(pstEhcb, ulKey);
 
    pstHashStatBacket =  (EHASH_STAT_S *)((void *)pstEhcb->pHashStatTbl  + ulStatEntrySize * ulKey);
    pstHashBacket =  (EHASH_BACKET_S *)((void *)pstEhcb->pHashMainTbl + ulEntrySize * ulKey);
 
    if ((*pstEhcb->pfEhashCmp)(pstHashBacket, pEhashEntryData) == 0)
    {
        if (pstHashBacket->ulNextIndex == EHASH_INDEX_INVALID)    /*Cond.1 無衝突鏈的情況下刪除首節點*/
        {
            pstHashBacket->ucValid = 0;
            /*bzero((void *)pstHashBacket, ulEntrySize);*/
            pstHashBacket->ulNextIndex = EHASH_INDEX_INVALID;
            (*pstEhcb->pfEntryDataClear)((void*)pstHashBacket, pstEhcb->ulEntrySize);
        }
        else    /*Cond.2 存在衝突鏈的情況下刪除首節點*/
        {
            mp = EHASH_NODE_ENTRY(pstEhcb->pHashMainTbl, pstHashBacket->ulNextIndex, ulEntrySize, EHASH_NODE_S);
            pstHashBacket->ucValid = 0;
            /*在刪除主表節點時將數據塊初始化一下,將衝突節點內容拷入到主節點中,*/
            (*pstEhcb->pfEntryDataInitFunc)(pstHashBacket, mp);
            pstHashBacket->ulNextIndex = mp->ulNextIndex;
            pstHashBacket->ucValid = 1;
            mp->ucValid = 0;
            (*pstEhcb->pfEntryDataClear)((void*)mp, pstEhcb->ulEntrySize);
            hashEntryFree(pstEhcb, mp);
        }
    }
    else
    {
        u8 ucFlag = 0;
        for (mp = pstHashBacket, pstPrevNode = NULL;
                (mp != NULL && mp->ucValid == 1);
                mp = EHASH_NODE_ENTRY(pstEhcb->pHashMainTbl, mp->ulNextIndex, ulEntrySize, EHASH_NODE_S))
        {
            if ((*pstEhcb->pfEhashCmp)(mp, pEhashEntryData) == 0)
            {
                ucFlag = 1;
                break;
            }
            pstPrevNode = mp;
        }
 
        if (ucFlag == 0)
        {
            (*pstEhcb->pfHashWriteUnLock)(pstEhcb, ulKey);
            return ERROR;
        }
 
        /*查找到待彈出的節點*/
        if (mp->ulNextIndex == EHASH_INDEX_INVALID)   /*Cond.3 衝突鏈存在的情況下刪除尾節點*/
        {
            pstHashBacket->ucValid = 0;
            mp->ucValid = 0;
            pstPrevNode->ucValid = 0;
            pstPrevNode->ulNextIndex = EHASH_INDEX_INVALID;
            pstPrevNode->ucValid = 1;
            pstHashBacket->ucValid = 1;
            (*pstEhcb->pfEntryDataClear)((void*)mp, pstEhcb->ulEntrySize);
            hashEntryFree(pstEhcb, mp);
        }
        else    /*Cond.4 衝突鏈存在的情況下刪除中間節點*/
        {
            pstHashBacket->ucValid = 0;
            mp->ucValid = 0;
            pstPrevNode->ucValid = 0;
            pstPrevNode->ulNextIndex = mp->ulNextIndex;
            pstPrevNode->ucValid = 1;
            pstHashBacket->ucValid = 1;
            (*pstEhcb->pfEntryDataClear)((void*)mp, pstEhcb->ulEntrySize);
            hashEntryFree(pstEhcb, mp);
        }
    }
 
    (*pstEhcb->pfHashWriteUnLock)(pstEhcb, ulKey);
    /*刪除節點,回調統計信息*/
    (*pstEhcb->pfStatEntryDel)(pstHashStatBacket, ulStatEntrySize);
 
    return OK;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章