Linux核心通用鍊錶詳解

2021-09-19 17:44:20 字數 3879 閱讀 8363

linux核心中充斥著大量的資料結構,這些資料結構很多都是使用結構體來表示:如cdev結構體用於描述乙個字元裝置,再如task_struct結構體,是我們所說的程序控制塊pcb,用於描述乙個程序的所有資訊。追尋核心原始碼我們會發現很多都是表示裝置的結構體中都有list_head這樣的字段,沒錯這就是核心鍊錶的節點型別。描述裝置的結構體中只要包含這個字段,核心就能通過鍊錶來管理我們的裝置,試想浩瀚的核心中有大量不同型別的裝置或結構體,如何把他們統一起來進行管理呢?核心鍊錶在這裡就立下了汗馬功勞,它的設計非常的巧妙,也體現了核心設計者的強大的智慧型,下面跟隨我的腳步來探尋下核心鍊錶(以下都叫做通用鍊錶)是如何實現的,又是如何管理我們不同型別的結構體的。 

首先我們討論下普通的鍊錶的特點:和陣列相比它的節點記憶體分配可以是不連續的,可以動態的增減節點也就是鍊錶長度可以不固定,但是它的資料域在定義鍊錶節點的時候必須固定,也就是說普通的鍊錶只能維護一條相同的資料型別的節點,如果資料域長度改變必須重新定義鍊錶節點。 

核心中由於要管理大量的裝置,但是各種裝置各不相同,必須將他們統一起來管理,於是核心設計者就想到了使用通用鍊錶來處理,通用鍊錶看似神秘,實際上就是雙向迴圈鍊錶,這個鍊錶的每個節點都是只有指標域,沒有任何資料域。 

圖1 普通的單鏈表 

使用通用鍊錶的好處是:1.通用鍊錶中每個節點中沒有資料域,也就是說無論資料結構有多複雜在鍊錶中只有前後級指標。2.如果乙個資料結構(即是描述裝置的裝置結構體)想要用通用鍊錶管理,只需要在結構體中包含節點的字段即可。3.雙向鍊錶可以從任意乙個節點的前後遍歷整個鍊錶,遍歷非常方便。4.使用迴圈鍊錶使得可以不斷地迴圈遍歷管理節點,像程序的排程:作業系統會把就緒的程序放在乙個管理程序的就緒佇列的通用鍊錶中管理起來,迴圈不斷地,為他們分配時間片,獲得cpu進行周而復始的程序排程。 

圖2 通用鍊錶

有了這些理論的東西,下面我們來實現一下通用鍊錶,並使用它來管理乙個學生資訊的結構體。其他型別的任何結構體管理亦是如此。我們會把主要的注釋放在程式**中,結合圖示來進行闡述。 

以下是list_head.**件,描述通用鍊錶的節點型別和操作函式的宣告。

#ifndef __list_head_h__

#define __list_head_h__

//通用鍊錶節點型別 雙向鍊錶

struct list_head;

void init_list_head(struct list_head *list);//初始化通用鍊錶

void list_add(struct list_head *node,struct list_head *head);//插入節點

void list_add_tail(struct list_head *node,struct list_head *head);//尾插

void list_del(struct list_head *node);//刪除節點

* 從頭節點的上乙個節點開始遍歷

*/#define list_for_next_each(pos,head)\for(pos=(head)->next;pos!=(head);pos=pos->next)#define list_for_prev_each(pos,head)\for(pos=(head)->prev;pos!=(head);pos=pos->prev)

//提取資料結構 ptr 是鏈結因子的指標 type是包含了鏈結因子的資料型別 member是鏈結因子成員名#define container_of(ptr,type,member)\

(type *)( (int)ptr - (int)(&((type *)0)->member) )

#endif

通用鍊錶需要解決的就是如何通過鍊錶節點的指標得到裝置結構體的首位址,只有得到了裝置結構體的首位址才能拿到結構體中的所有成員,這也是通用鍊錶的核心演算法,實現理解起來稍微有點難度,但是只要感悟出來,也並無那麼難。下面畫圖講解container_of的實現:(可以發現是通過偏移量來求得首位址的,裝置結構體一旦分配空間在記憶體中就有了一塊區域,鏈結因子list也就有了位址,使用對0位址的強轉為裝置結構體指標的型別,能夠找到0位址中的鏈結因子的位址,通過兩個鏈結因子的差值,當然這裡將位址強轉為int型,不然差值是指向資料的個數,這樣就能得到我們想要的首位址了)。

圖3 container_of的理解圖

以下是list_head.c檔案,是操作函式的實現。

#include "list_head.h"

void init_list_head(struct list_head *list)//初始化通用鍊錶

void list_add(struct list_head *node,struct list_head *head)//插入節點

void list_add_tail(struct list_head *node,struct list_head *head)//尾插

void list_del(struct list_head *node)//刪除節點

圖4 通用鍊錶的頭尾插 

圖5 通用鍊錶的節點刪除

以下是stu.**件,描述裝置資訊的結構體定義。

#ifndef __stu_h__

#define __stu_h__

#define n 32

#include "list_head.h"

struct stu;

#endif

以下是test.c檔案,用於測試我們的通用鍊錶。

#include #include #include "list_head.h"

#include "stu.h"

int main(int argc, const char *ar**)

//初始化通用鍊錶

init_list_head(&stu_list);

for(i=0;i<5;i++) //賦值學生資訊

//遍歷學生資訊

list_for_next_each(pos,&stu_list)

puts("--------------------------------");

list_for_prev_each(pos,&stu_list)

return 0;

}

以下是簡單的makefile, 利用make的隱含規則,使得makefile非常簡單:

test:list_head.o test.o

.phony:clean

clean:

rm -rf test *.o

最後是執行結果: 

以上是本人對於核心鍊錶的理解,核心中的許多思想和演算法值得我們去學習應用,如核心的統一性、物件導向的思想、分層的思想、分離的思想等等。核心中最核心也是最有魅力的莫過於演算法,而c語言的魅力所在也就是它對於指標的靈活應用,使得他能夠去操作硬體,表示許多複雜的資料結構。通用鍊錶也不過是向我們提供了一種統一不同裝置結構體的演算法而已。

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中就產生了核心鍊錶,以後想要在鍊錶中插入資料或刪除資料時,只需要呼叫函式就可以了。鍊錶對比 鍊錶是一種資料結構,他通過指標將一系列的資料節點連線成一條資料鏈...