Linux 核心list head 學習

2021-06-28 09:42:09 字數 3134 閱讀 8318

在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這個結構看起來怪怪的,它竟沒有資料域!所以看到這個結構的人第一反應就是我們怎麼訪問資料?

其實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 核心中,提供了乙個用來建立雙向迴圈鍊錶的結構 list head 雖然linux 核心是用 c語言寫的,但是 list head 的引入,使得核心資料結構也可以擁有物件導向的特性,通過使用操作 list head 的通用介面很容易實現 的重...

Linux核心list head學習(二)

前一篇文章討論了list head 結構的基本結構和實現原理,本文主要介紹一下例項 自己如果想在應用程式中使用list head 的相應操作 當然應該沒人使用了,c stl提供了list 用起來貌似更方便 在應用程式中需要包含自己的 list.h 標頭檔案 注 這個list.h 是為了配合示例程式而...