對Linux核心鍊錶的list entry巨集的理解

2021-10-25 15:57:50 字數 2608 閱讀 6590

linux核心方式與眾不同,它不是將資料結構塞入鍊錶,而是將鍊錶節點塞入資料結構!

這一點一定要時刻銘記於心。

鍊錶節點定義:

struct list_head ;

鍊錶的初始化可以由兩個巨集來靜態的實現:

#define list_head_init(name) 

#define list_head(name) \

struct list_head name = list_head_init(name)

當然也有用函式動態實現的形式:

/* init_list_head - initialize a list_head structure

* @list: list_head structure to be initialized.

* initializes the list_head to point to itself. if it is a list header,

* the result is an empty list. */

static inline void init_list_head(struct list_head *list)

【list_entry巨集】

由於是把節點嵌入資料結構,而不是把資料結構嵌入節點,於是引發了乙個問題:

作為乙個節點,它是如何探知外面的世界的?它如何知道自己被怎樣的乙個資料結構包圍著?如何去訪問包圍它的這個資料結構中的其他成員變數?

於是有了list_entry,是乙個巨集,呼叫另乙個巨集,用來獲取包含某個鍊錶節點的結構體:

/* list_entry - get the struct for this entry

* @ptr: the &struct list_head pointer.

* @type: the type of the struct this is embedded in. //該結構體的型別

* @member: the name of the list_head within the struct. */ //該鍊錶節點的名稱

#define list_entry(ptr, type, member) \

container_of(ptr, type, member)

list_entry巨集呼叫了container_of巨集,後者的作用是已知結構體中一成員名(member)和其位址(ptr),求得整個結構體的位址,這個巨集返回指向type結構的指標。ptr為成員位址,type為結構體型別,member為結構體成員名。

這裡的三個引數,ptr是指向list_head型別鍊錶的指標,type為乙個結構體,而member為結構體type中的乙個域,型別為list_head。在核心**中大量引用了這個巨集,因此,搞清楚這個巨集的含義和用法非常重要。

於是我們進一步地,來看container_of巨集的具體實現:

#define container_of(ptr, type, member) ()

首先,typeof()的作用是獲取變數的型別。

於是這裡做的第一步是,把0位址強轉成結構體type型別的指標,然後利用這個type結構體指標去訪問其成員變數member。

然後這個時候再對這個訪問到的member變數作typeof操作,其獲取其變數型別。

然後利用該變數型別建立乙個臨時指標變數__mptr,指向ptr

——也就是說,這麼一長串const typeof(((type *)0)->member) *__mptr,也只是新建了乙個和ptr同樣型別的指標,然後執行ptr,在後面代替ptr來使用。

緊接著,第二行的offsetof又是乙個巨集:

#define offsetof(type, member) ((size_t) &((type *)0)->member)
該巨集的作用是計算某個結構體type中,某個成員member的偏移量。

具體原理是,把0位址強轉成結構體type型別的指標,然後利用這個type結構體指標去訪問其成員變數member。這一步相當於,假想在0位址處有乙個type型別的結構體,然後訪問假想的member成員。然後再對訪問的結果取位址,顯然這就是這個假想的member成員相對於0位址的偏移——即成員變數member在type中的偏移。

知道了offsetof巨集的作用,再回到container_of巨集的第二行。

我們看到,這一步用__mptr指標的位址減去member相對於type的偏移量——(這裡把__mptr強轉成char *的目的大概就是要強轉成位址量?)。也就是,用指向ptr的指標的位址,減去ptr在type中的偏移量,自然就得到了外部的這個type的位址。

最後再把它強轉成type型別的指標。

這樣,就獲取到了整個結構體的位址。

有了這個結構體的位址,相當於我們能夠訪問包含鍊錶節點的這個外面的結構體,即獲得乙個指向外部結構體的指標,那麼就可以再對其進行訪問操作,訪問其中的其他成員變數啦。

【question】

這裡有個疑問,既然ptr已經指向了結構體中的鍊錶節點,那為什麼不直接用它來和偏移量作相減,而是要大費周折地新建乙個變數__mptr?為啥呢?

linux核心鍊錶

鍊錶是一種常用的資料結構,它通過指標將一系列資料節點連線成一條資料鏈。相對於陣列,鍊錶具有更好的動態性,建立鍊錶時無需預先知道資料總量,可以隨機分配空間,可以高效地在鍊錶中的任意位置實時插入或刪除資料。鍊錶的開銷主要是訪問的順序性和組織鏈的空間損失。一 鍊錶結構 單鏈表結構如下 雙鏈表結構如圖 st...

linux核心鍊錶

include include struct list head struct mylist void list add struct list head new,struct list head prev,struct list head next void list add tail struc...

Linux核心鍊錶

核心鍊錶 核心鍊錶即,我麼在乙個鍊錶中插入或刪除乙個資料,都需要自己編寫 相當的麻煩,怎麼解決這個問題呢,為了更加方便的解決這個問題,linux中就產生了核心鍊錶,以後想要在鍊錶中插入資料或刪除資料時,只需要呼叫函式就可以了。鍊錶對比 鍊錶是一種資料結構,他通過指標將一系列的資料節點連線成一條資料鏈...