遍歷Linux kernel的鏈表時刪除元素的方法

<!--@page { margin: 2cm }PRE.cjk { font-family: "DejaVu Sans", monospace }P { margin-bottom: 0.21cm }A:link { so-language: zxx }-->

         內核的鏈表list_head設計相當巧妙。今天我說一下對list_head鏈表的遍歷時如何刪除元素。

         鏈表遍歷時,如果刪除當前元素,一般都是會出錯的。在所有語言的各種庫中的鏈表都是如此。list_head也一樣。

 

<!--@page { margin: 2cm }P { margin-bottom: 0.21cm }A:link { so-language: zxx }-->

       如,在java的遍歷中刪除當前元素,會拋出java.util.ConcurrentModificationException異常。

見:《Java中如何刪除一個集合中的多個元素http://blog.csdn.net/shendl/archive/2007/12/28/1999907.aspx 一文。

 

 

       使用list_for_each遍歷鏈表,如果使當前元素脫鏈,那麼系統就會毫不留情的crash掉。什麼提示信息都沒有。因此這類bug非常難以定位。

 

list_for_each源碼:

/**
 * list_for_each        -       iterate over a list
 * @pos:        the &struct list_head to use as a loop cursor.
 * @head:       the head for your list.
 */
#define list_for_each(pos, head) /
        for (pos = (head)->next; prefetch(pos->next), pos != (head); /
                pos = pos->next)

 

          list_del脫鏈元素後,會把nextprev分別賦值爲:

/*
 * These are non-NULL pointers that will result in page faults
 * under normal circumstances, used to verify that nobody uses
 * non-initialized list entries.
 */
#define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA

       list_del_init脫鏈元素後,會把nextprev都設置爲自己。

 

          因此,在list_for_each中刪除當前元素後,就無法正確找到鏈表的下一個元素。

 

       如果要在遍歷list_head鏈表時,刪除當前元素,那麼就必須使用list_for_each_safe函數而不能使用list_for_each函數。

 

list_for_each_safe源碼:

/**
 * list_for_each_safe - iterate over a list safe against removal of list entry
 * @pos:        the &struct list_head to use as a loop cursor.
 * @n:          another &struct list_head to use as temporary storage
 * @head:       the head for your list.
 */
#define list_for_each_safe(pos, n, head) /
        for (pos = (head)->next, n = pos->next; pos != (head); /
                pos = n, n = pos->next)

          這個函數比list_for_each函數多了一個n參數。這個參數也是list_head類型的。

          它保存下一個元素,這樣就可以安全的刪除當前元素,不會造成找不到後續元素的情況發生。

          在循環結束時,pos指向n元素,而不是指向posnext元素。因爲pos脫鏈後,pos元素的next可能已經是空指針,或者是LIST_POISON1 這個無意義的值了。

         如果list是空的,那麼pos=n後,仍然等於head,遍歷就此結束了!

	 因此,使用lisf_for_each_safe函數遍歷list_head鏈表,就可以安全地刪除當前元素了。

 

發佈了185 篇原創文章 · 獲贊 136 · 訪問量 214萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章