1. 程式人生 > >linux 核心hash_list學習筆記

linux 核心hash_list學習筆記

linux核心為了方便進行連結串列的操作,定義了一系列很方便的巨集定義,在平時的專案開發中,應用巨集定義能使得專案事半功倍,下面是核心實現原始碼的實現,僅重定義了名字,正在學習,就暫時記錄下來吧:

結構定義:

typedef struct hlist_node     
{
    struct hlist_node     *next;    // 指向下一個結點的指標
    struct hlist_node    **pprev;// 指向上一個結點的next指標的地址 
}HLIST_NODE_S;

typedef struct hlist_head    
{
    struct hlist_head *first;    // 指向每一個hash桶的第一個結點的指標
}HLIST_HEAD_S;

初始化工作:

// 初始化hash桶的頭結點 
#define    INIT_HLIST_HEAD(ptr)   ((ptr)->first = NULL)

// 初始化hash桶的普通結點 
static  inline  void  INIT_HLIST_NODE(HLIST_NODE_S *node)
{
    node->next    = NULL;
    node->pprev    = NULL;
}

雙鏈表基本操作

<1> 新增到頭節點處

/**
 * HLIST_Add_Head
 * @n: the element to add to the hash list.
 * @h: the list to add to.
 */
static    inline    void    HLIST_Add_Head(HLIST_NODE_S *node, HLIST_HEAD_S *head)    
{
    HLIST_NODE_S *first = head->first;
    node->next = first;
    
    if (NULL != first)
    {
        first->pprev = &node->next;
    }
    
    head->first = node;
    node->pprev = &head->first;
}

<2>新增到指定節點前

/* next must be != NULL */
/* node:要新增的新的節點。
 * next:在next節點之前新增node。
 * 在next節點的前面新增一個新的節點n,在使用這個函式中要特別注意,next不能為NULL。
 */
static    inline    void    HLIST_Add_Before(HLIST_NODE_S *node, HLIST_NODE_S *next)
{
    node->pprev    = next->pprev;
    node->next     = next;
    next->pprev    = &node->next;
    *(node->pprev) = node;
}

 <3>新增到指定節點後

/* next must be != NULL */
/* node:要新增的新的節點。
 * next:表示在next節點之後新增node。
 * 在next 節點的後面新增一個新的節點node,這裡也要求next不能為NULL
 */
static inline void HLIST_Add_After(HLIST_NODE_S *node, HLIST_NODE_S *next)
{
    next->next = node->next;
    node->next = next;
    next->pprev = &node->next;

    if(NULL != next->next)
    {
         next->next->pprev  = &next->next;
    }
}

<4>刪除指定節點

/* node:要刪除的節點。
 * 對於刪除操作的話,要注意node是不是末尾節點,如果是末尾節點的話,next就是NULL?
 * 所以就沒有指向的pprev,就更不能進行相應的修改了,否則進行修改。
 */
static inline void __hlist_del(HLIST_NODE_S *node)
{
    HLIST_NODE_S *next = node->next;
    HLIST_NODE_S **pprev = node->pprev;
    *pprev = next;
    if (NULL != next)
    {
        next->pprev = pprev;
    }
}


/* node:要刪除的節點。
 * 在這個函式中首先刪除了node節點,之後將n節點的兩個指標指向了NULL,表示不可使用的地方
 */
static inline void HLIST_Del(HLIST_NODE_S *node)
{
    __hlist_del(node);
    node->next     = NULL;
    node->pprev    = NULL;
}

<5>判斷連結串列是否為NULL或節點是否在hash內

/*
 * 判斷一個結點是否已經存在於hash桶中 
 * 判斷h->prev是不是為空,如果pprev的指向是空的話,表示這個節點沒有新增到這個連結串列當中來,
 * 如果是空,返回true,否則返回false
 */
static inline int HLIST_UnHashed(const HLIST_NODE_S *node)
{
    return !node->pprev;
}

// 判斷一個hash桶是否為空 
/* head:struct hlist_head節點指標(hlist連結串列的頭節點)。
 * 判斷hlist連結串列是不是空連結串列,如果是,返回true,否則返回false。
 */
static inline int HLIST_IsEmpty(const HLIST_HEAD_S *head)
{
    return (NULL != head->first);
}

遍歷hash_list

<1>兩個方便的巨集定義

/***********************************************************
*       遍歷hash連結串列,需要用到兩個地址偏移的巨集定義                            *
************************************************************/

//獲取結構體成員相對於結構體的偏移
#define offsetof(TYPE,MEMBER) ((int) &((TYPE *)0)->MEMBER)

//通過獲取結構體中的某個成員,反推該結構體的指標 
#define container_of(ptr, type , member) ({ \
	const typeof(((type *)0)->member) *__mptr = (ptr) ; \
	(type *)((char *)__mptr - offsetof(type,member)) ;})

<2>基本遍歷,用於查詢

/* pos:struct hlist_node型別的一個指標;
* head:struct hlist_head型別的一個指標,表示hlist連結串列的頭結點。
* 這個實際上就是一個for迴圈,從頭到尾遍歷連結串列。
*/
/* 普通遍歷,遍歷過程中不能刪除節點,否則可能會出現後續節點的訪問錯誤 */
#define HLIST_FOR_EACH(pos, head) \
    for (pos = (head)->first; pos != NULL ; 1; }); \
     pos = pos->next)

<3>安全遍歷,用於遍歷中刪除

/* 這個實際上就是一個for迴圈,從頭到尾遍歷連結串列。這個和前面的不同的是多了一個n,
 * 這麼做是為了遍歷過程中防止斷鏈的發生。刪除時用這個。
 * pos:struct hlist_node型別的一個指標;
 * next:struct hlist_node型別的一個指標,這裡為區分,寫作next;
 * head:struct hlist_head型別的一個指標,表示hlist連結串列的頭結點。
 這是安全遍歷,實際上就是預讀取,提前指向下一個節點,遍歷過程中可以刪除節點
 */ 
#define HLIST_FOR_EACH_SAFE(pos, next, head) \
        for (pos = (head)->first; pos && ({ next = pos->next; 1; }); \
         pos = next)

<4>訪問元素基本遍歷:訪問連結串列所在結構體其他成員

/* ptype:用來存放遍歷到的資料結構的地址,型別是type *;
 * pos:struct hlist_node型別的一個指標;
 * head:hlist連結串列的頭結點;
 * member:struct hlist_node在type結構體中的變數的名稱。
 * 通過hlist_entry 這個巨集,我們可以訪問hash連結串列所在結構體的其他成員,很多時候需要這樣訪問。
   本遍歷巨集並非安全定義,遍歷過程中不能刪除節點
 */
/**
 * HLIST_FOR_EACH_ENTRY    - iterate over list of given type
 * @ptype:    the type * to use as a loop cursor.
 * @pos:    the &HLIST_NODE_S to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the hlist_node within the struct.
 */
#define HLIST_FOR_EACH_ENTRY(ptype, pos, head, member)             \
            for (pos = (head)->first;                     \
             (pos != NULL) &&             \
                ({ ptype = hlist_entry(pos, typeof(*ptype), member); 1;}); \
             pos = pos->next)

<5>訪問元素安全遍歷,用於遍歷過程中刪除節點

/* ptype:用來存放遍歷到的資料結構的地址,型別是type *;
* pos:struct hlist_node型別的一個指標;
* n:struct hlist_node型別的一個指標;
* head:hlist連結串列的頭結點;
* member:struct hlist_node在type結構體中的變數的名稱。
* 在迴圈中,我們就可以使用tops來指向type
* 型別結構體的任何一個變量了。這個巨集函式也是為了防止在遍歷的時候刪除節點而引入的。
*/
/**
* HLIST_FOR_EACH_ENTRY_SAFE - iterate over list of given type safe against 
removal of list entry
* @tpos:    the type * to use as a loop cursor.
* @pos:    the &HLIST_NODE_S to use as a loop cursor.
* @n:        another &HLIST_NODE_S to use as temporary storage
* @head:    the head for your list.
* @member:    the name of the hlist_node within the struct.
*/
#define HLIST_FOR_EACH_ENTRY_SAFE(ptype, pos, next, head, memsber)          \
                for (pos = (head)->first;                     \
                 pos && ({ next = pos->next; 1; }) &&                  \
                    ({ ptype = hlist_entry(pos, typeof(*ptype), member); 1;}); \
                 pos = next)

參考文件: