一般的鍊錶如下,它是將資料結構塞入鍊錶,這樣的話,每一種資料結構都需要重新定義。
struct student_node
核心版本:4.1.15, 在目錄include/linux/types.h中定義了list_head結構:
struct list_head
;
可以看到,很簡單,linux不是將資料結構塞入鍊錶,而是將鍊錶塞入資料結構。
在目錄include/linux/types.h中定義了list_head的操作函式:
static
inline
void
init_list_head
(struct list_head *list)
//初始化乙個煉表頭,
static
inline
void
__list_add
(struct list_head *_new,
struct list_head *prev,
struct list_head *next)
static
inline
void
list_add
(struct list_head *new,
struct list_head *head)
//在頭部新增乙個節點
static
inline
void
list_add_tail
(struct list_head *_new,
struct list_head *head)
//在尾部新增乙個節點
#define container_of(ptr, type, member) (
)#define list_entry(ptr, type, member) \
container_of
(ptr, type, member)
#define list_for_each_entry(pos, head, member) \
//遍歷head
for(pos =
list_entry
((head)
->next,
typeof
(*pos)
, member)
; \ &pos->member !=
(head)
; \
pos =
list_entry
(pos->member.next,
typeof
(*pos)
, member)
)
struct student
;int
main()
;struct list_head stu_head;
init_list_head
(&stu_head)
;struct student *stu1;
stu1 =
malloc
(sizeof
(*stu1));
strcpy
(stu1->name,
"jack");
stu1->age =20;
struct student *stu2;
stu2 =
malloc
(sizeof
(*stu2));
strcpy
(stu2->name,
"bob");
stu2->age =30;
struct student *stu3;
stu3 =
malloc
(sizeof
(*stu3));
strcpy
(stu3->name,
"bobc");
stu3->age =31;
list_add
(&stu1->list,
&stu_head)
;list_add
(&stu2->list,
&stu_head)
;list_add
(&stu3->list,
&stu_head)
;struct student *stu;
list_for_each_entry
(stu,
&stu_head, list)
return0;
}
輸出如下:
stu->name=bobc
stu->name=bob
stu->name=jack
可以看到,linux核心中煉表操作的都是list_head結構,那麼它是怎樣通過list_head來訪問對應的資料結構裡的成員的呢?
從上面的**可以知道,遍歷是通過list_for_each_entry巨集來實現的,該巨集是乙個for迴圈,可以使用gcc的預編譯命令來檢視list_for_each_entry展開後的**:
gcc -e -o main.i main.c
檢視main.i中巨集的位置,展開後如下:
struct student *stu;
for(stu =()
;&stu->list !=
(&stu_head)
; stu =()
)
展開後分為for迴圈中的三個部分:
stu =()
&stu->list !=
(&stu_head)
;stu =
()
分別對應for語句中的三份**,可以看到,遍歷的終止條件是&stu->list != (&stu_head),如果&stu->list = (&stu_head),說明遍歷到頭節點了,迴圈結束,下面簡化一下第乙份**:
const list_head *__mptr =((
&stu_head)
->next);(
struct student *)(
(char
*)__mptr -
((size_t)&(
(struct student *)0
)->list)
;
可以看到, 最終stu的值是:
(
struct student *)(
(char
*)__mptr -
((size_t)&(
(struct student *)0
)->list)
;
將第一步細化實現如下:
/*手動實現從list_head訪問student*/
struct list_head *mptr = stu_head.next;
unsigned
long
long offset =
(unsigned
long
long)(
&(((
struct student *)0
)->list));
mptr =
(unsigned
long
long
)mptr - offset;
stu =
(struct student *
)mptr;
printf
("stu = %s %p\n"
, stu->name, stu)
;printf
("stu1 = %p\n"
, stu1)
;printf
("stu2 = %p\n"
, stu2)
;printf
("stu3 = %p\n"
, stu3)
;
輸出如下:
stu = bobc 0x55d8023972c0
stu1 =
0x55d802397260
stu2 =
0x55d802397290
stu3 =
0x55d8023972c0
可以看到,stu最終等於stu3,因為stu3最後乙個插入。畫了乙份草圖便於理解:
可以看到,linux能夠實現從list_head訪問到student主要是使用了offset,offset是student結構體的起始位址到list_head的大小,再使用list_head的位址減去這段大小即可得到該結構體的起始位址,從而訪問結構體成員。
**鏈結
linux核心原始碼「雙向鍊錶list head」
摘要 linux核心原始碼真是好東東,是眾多高手思維的結晶,在 linux 源 中有個頭檔案為 list.h 很多 linux 下的源 都會使用這個標頭檔案,它裡面定義了乙個結構 struct list head 如果您之前學過雙向鍊錶,那麼當你看到這個結構的時候,會覺得似曾相識。豈止似曾相識,如果...
在Linux下鍊錶使用介紹一 list head
在看這個知識點的時候我相信大家對資料結構已經有所了解,尤其是對鍊錶的了解,因此在這裡不過多講解傳統的鍊錶基本知識,這裡只給出通常雙向鍊錶的資料結構。struct list node 在linux核心程式設計中為什麼不使用通常的鍊錶呢?因為它的缺點是 對每種型別串起來的鍊錶,我們需要編寫一系列的函式實...
linux核心鍊錶
鍊錶是一種常用的資料結構,它通過指標將一系列資料節點連線成一條資料鏈。相對於陣列,鍊錶具有更好的動態性,建立鍊錶時無需預先知道資料總量,可以隨機分配空間,可以高效地在鍊錶中的任意位置實時插入或刪除資料。鍊錶的開銷主要是訪問的順序性和組織鏈的空間損失。一 鍊錶結構 單鏈表結構如下 雙鏈表結構如圖 st...