linux
核心list_head
學習(一)
在linux
核心中,提供了乙個用來建立雙向迴圈鍊錶的結構
list_head
。雖然linux
核心是用
c語言寫的,但是
list_head
的引入,使得核心資料結構也可以擁有物件導向的特性,通過使用操作
list_head
的通用介面很容易實現**的重用,有點類似於
c++的繼承機制(希望有機會寫篇文章研究一下
c語言的物件導向機制)。下面就是
kernel
中的list_head
結構定義:
struct list_head ;
#define list_head_init(name)
需要注意的一點是,頭結點
head
是不使用的,這點需要注意。
使用list_head
組織的鍊錶的結構如下圖所示:
這個結構看起來怪怪的,它竟沒有資料域!所以看到這個結構的人第一反應就是我們怎麼訪問資料?
其實list_head
不是拿來單獨用的,它一般被嵌到其它結構中,如:
struct file_node;
此時list_head
就作為它的父結構中的乙個成員了,當我們知道
list_head
的位址(指標)時,我們可以通過
list.c
提供的巨集
list_entry
來獲得它的父結構的位址。下面我們來看看
list_entry
的實現:
#define list_entry(ptr,type,member)\
container_of(ptr,type,member)
#define offsetof(type,member) ((size_t)&((type *)0)->member)
#define container_of(ptr,type,member) ( )
這裡涉及到三個巨集,還是有點複雜的,我們乙個乙個來看:
#define offsetof(type,member) ( (size_t)& ((type *)0
)-> member )
我們知道
0 位址內容是不能訪問的,但
0位址的位址我們還是可以訪問的,
這裡用到乙個取址運算子
(type *)0
它表示將
0位址強制轉換為
type
型別,((type *)0
)-> member
也就是從
0址址找到
type
的成員member
。我們結合上面的結構來看
struct file_node;
將實參代入
offset( struct file_node, node )
;最終將變成這樣:
( (size_t) & ((struct file_node*)0
)-> node )
;這樣看的還是不很清楚,我們再變變:
struct file_node *p = null;
& p->node;
這樣應該比較清楚了,即求
p 的成員
node
的位址,只不過p 為
0位址,從
0位址開始算成員
node
的位址,也就是
成員node
在結構體
struct file_node
中的偏移量。
offset
巨集就是算
member
在type
中的偏移量的。
我們再看第二個巨集
#define container_of(ptr,type,member) ( )
這個巨集是由兩個語句組成,最後
container_of
返回的結果就是第二個表示式的值。這裡
__mptr
為中間變數,這就是
list_head
指標型別,它被初始化為
ptr的值,而
ptr就是當前所求的結構體中
list_head
節點的位址。為什麼要用中間變數,這是考慮到安全性因素,如果傳進來乙個
ptr++
,所有ptr++
放在乙個表示式中會有***,像
(p++)+(p++)
之類。(char*)__mptr
之所以要強制型別轉化為
char
是因為位址是以位元組為單位的,而
char
的長度就是乙個位元組。
container_of
的值是兩個位址相減,
剛說了__mptr
是結構體中
list_head
節點的位址,
offset
巨集求的是
list_head
節點member
在結構體
type
中的偏移量,那麼
__mptr
減去它所在結構體中的偏移量,就是結構體的位址。
所以list_entry(ptr,type,member)
巨集的功能就是,由結構體成員位址求結構體位址。其中
ptr
是所求結構體中
list_head
成員指標,
type
是所求結構體型別,
member
是結構體
list_head
成員名。通過下圖來總結一下:
繼續列舉一些雙鏈表的常用操作:
雙向鍊錶的遍歷
——list_for_each
//注:這裡
prefetch
是gcc
的乙個優化選項,也可以不要
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)
生成雙向鍊錶的頭結點
——list_head()
list_head() --
生成乙個名為
name
的雙向煉表頭節點
#define list_head(name) \
struct list_head name = list_head_init(name)
static inline void init_list_head(struct list_head *list)
雙向鍊錶的插入操作
--list_add()
將new
所代表的結構體插入
head
所管理的雙向鍊錶的頭節點
head
之後:
(即插入表頭)
static inline void list_add(struct list_head *new, struct list_head *head)
static inline void __list_add( struct list_head *new, struct list_head *prev, struct list_head *next) 從
list
中刪除結點
——list_del()
static inline void list_del(struct list_head *entry)
static inline void __list_del(struct list_head * prev, struct list_head * next)
判斷鍊錶是否為空(如果雙向鍊錶
head
為空則返回真,否則為假)
——list_empty()
static inline int list_empty(const struct list_head *head)
Linux 核心list head 學習
在linux核心中,提供了乙個用來建立雙向迴圈鍊錶的結構 list head。雖然linux核心是用c語言寫的,但是list head的引入,使得核心資料結構也可以擁有物件導向的特性,通過使用操作list head 的通用介面很容易實現 的重用,有點類似於c 的繼承機制 希望有機會寫篇文章研究一下c...
Linux 核心list head 學習
在linux核心中,提供了乙個用來建立雙向迴圈鍊錶的結構 list head。雖然linux核心是用c語言寫的,但是list head的引入,使得核心資料結構也可以擁有物件導向的特性,通過使用操作list head 的通用介面很容易實現 的重用,有點類似於c 的繼承機制 希望有機會寫篇文章研究一下c...
Linux核心list head學習(二)
前一篇文章討論了list head 結構的基本結構和實現原理,本文主要介紹一下例項 自己如果想在應用程式中使用list head 的相應操作 當然應該沒人使用了,c stl提供了list 用起來貌似更方便 在應用程式中需要包含自己的 list.h 標頭檔案 注 這個list.h 是為了配合示例程式而...