linux核心鍊錶list head的原理與使用

2021-10-05 06:51:36 字數 4453 閱讀 1564

一般的鍊錶如下,它是將資料結構塞入鍊錶,這樣的話,每一種資料結構都需要重新定義。

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...