list操作

在 [net/core/netfilter.c] 的 nf_register_sockopt() 函數中有這麼一段話: 

…… struct list_head *i; …… list_for_each(i, &nf_sockopts) { struct nf_sockopt_ops *ops = (struct nf_sockopt_ops *)i; …… } ……


函數首先定義一個 (struct list_head *) 指針變量i,然後調用 list_for_each(i,&nf_sockopts) 進行遍歷。在 [include/linux/list.h] 中, list_for_each() 宏是這麼定義的: 

#define list_for_each(pos, head) / for (pos = (head)->next, prefetch(pos->next); pos != (head); / pos = pos->next, prefetch(pos->next))

它實際上是一個 for 循環,利用傳入的 pos 作爲循環變量,從表頭 head 開始,逐項向後(next 方向)移動 pos,直至又回到 head(prefetch() 可以不考慮,用於預取以提高遍歷速度 )。 

那麼在 nf_register_sockopt() 中實際上就是遍歷 nf_sockopts 鏈表。爲什麼能直接將獲得的 list_head 成員變量地址當成 struct nf_sockopt_ops 數據項變量的地址呢?我們注意到在 struct nf_sockopt_ops 結構中,list是其中的第一項成員,因此,它的地址也就是結構變量的地址。更規範的獲得數據變量地址的用法應該是: 

struct nf_sockopt_ops *ops = list_entry(i, struct nf_sockopt_ops, list);


大多數情況下,遍歷鏈表的時候都需要獲得鏈表節點數據項,也就是說 list_for_each()和list_entry() 總是同時使用。對此 Linux 給出了一個 list_for_each_entry() 宏: 

#define list_for_each_entry(pos, head, member) ……


與 list_for_each() 不同,這裏的pos是數據項結構指針類型,而不是 (struct list_head *)。nf_register_sockopt() 函數可以利用這個宏而設計得更簡單: 

…… struct nf_sockopt_ops *ops; list_for_each_entry(ops,&nf_sockopts,list){ …… } ……


某些應用需要反向遍歷鏈表,Linux 提供了 list_for_each_prev() 和 list_for_each_entry_reverse() 來完成這一操作,使用方法和上面介紹的 list_for_each()、list_for_each_entry() 完全相同。 

如果遍歷不是從鏈表頭開始,而是從已 知的某個節點 pos 開始,則可以使用 list_for_each_entry_continue(pos,head,member)。有時還會出現這種需求,即經過一系列計算後,如果 pos 有值,則從 pos 開始遍歷,如果沒有,則從鏈表頭開始,爲此,Linux 專門提供了一個 list_prepare_entry(pos,head,member) 宏,將它的返回值作爲 list_for_each_entry_continue() 的 pos 參數,就可以滿足這一要求。 

4. 安全性考慮 
在併發執行的環境下,鏈表操作通常都應該考慮同步安全性問題,爲了方便,Linux 將這一操作留給應用自己處理。Linux 鏈表自己考慮的安全性主要有兩個方面: 

a) list_empty() 判斷 

基本的 list_empty() 僅以頭指針的 next 是否指向自己來判斷鏈表是否爲空,Linux 鏈表另行提供了一個 list_empty_careful() 宏,它同時判斷頭指針的 next 和 prev,僅當兩者都指向自己時才返回真。這主要是爲了應付另一個 cpu 正在處理同一個鏈表而造成 next、prev 不一致的情況。但代碼註釋也承認,這一安全保障能力有限:除非其他 cpu 的鏈表操作只有 list_del_init(),否則仍然不能保證安全,也就是說,還是需要加鎖保護。 

b) 遍歷時節點刪除 

前面介紹了用於鏈表遍歷的幾個宏,它們都是通過移動 pos 指針來達到遍歷的目的。但如果遍歷的操作中包含刪除 pos 指針所指向的節點,pos 指針的移動就會被中斷,因爲 list_del(pos) 將把 pos 的 next、prev 置成 LIST_POSITION2 和 LIST_POSITION1 的特殊值。 

當然,調用者完全可以自己緩存 next 指針使遍歷操作能夠連貫起來,但爲了編程的一致性,Linux 鏈表仍然提供了兩個對應於基本遍歷操作的 "_safe" 接口:list_for_each_safe(pos, n, head)、list_for_each_entry_safe(pos, n, head, member),它們要求調用者另外提供一個與 pos 同類型的指針n,在 for 循環中暫存 pos 下一個節點的地址,避免因 pos 節點被釋放而造成的斷鏈。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章