linux核心資料結構實現 鍊錶 佇列和雜湊

2021-10-14 08:45:55 字數 3792 閱讀 1022

c是面向過程的語言,但是linux核心卻用c實現了一套物件導向的設計模式,linux核心中處處體現著物件導向的思想。

我們在語法書上學到的鍊錶都是在原資料結構的基礎上增加指標域next(或者prev),從而使各個節點能鏈結在一起。

比如

typedef

傳統的鍊錶有個最大的缺點就是不好通用化,linux核心的實現可以說是獨樹一幟,它實現了一種更通用的鍊錶。

在資料結構中塞入list_head,那麼當組成鍊錶的時候,所有的node節點的list域串聯在一起組成鍊錶,我們一次遍歷其實就是遍歷所有的list_head域。

linux核心提供了list_entry的巨集,來通過成員物件指標來獲取到整個物件的指標。

這到底是怎麼實現的呢?

#define list_entry(ptr, type, member) /

container_of

(ptr, type, member)

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

#define offsetof(type, member) ((size_t) &((type *)0)->member)

我們將巨集完全展開,就得到如下的**:

#define list_entry(ptr, type, member) /

((type *)(

(char*)

(ptr)-(

unsigned

long)(

&((type *)0

)->member)

))

這個巨集返回指向type結構的指標。

生產者和消費者模型中,生產者創造資料,而消費者讀取訊息和處理包,或者以其他方式消費這些資料。

最簡單的實現方法是佇列,生產者將資料推進佇列,消費者從佇列中讀取資料。消費者獲取資料的順序和生產者推入佇列的順序一致。

佇列,即fifo(first in first out,先進先出)。linux核心通用佇列實現稱為kfifo,在kernel/kfifo.c檔案中實現,宣告在檔案中。

首先看一下kfifo的資料結構:

struct kfifo 

;

定義自旋鎖的目的為了防止多程序/執行緒併發使用kfifo。因為in和out在每次get和out時,發生改變。

kfifo的巧妙之處在於in和out定義為無符號型別,在put和get時,in和out都是增加,當達到最大值時,產生溢位,使得從0開始,進行迴圈使用。

初始化

put超出末尾,移到前面

**如下:

static

inline

unsigned

intkfifo_put

(struct kfifo *fifo,

const

unsigned

char

*buffer,

unsigned

int len)

static

inline

unsigned

intkfifo_get

(struct kfifo *fifo,

unsigned

char

*buffer,

unsigned

int len)

unsigned

int__kfifo_put

(struct kfifo *fifo,

const

unsigned

char

*buffer,

unsigned

int len)

unsigned

int__kfifo_get

(struct kfifo *fifo,

unsigned

char

*buffer,

unsigned

int len)

hash 最重要的是選擇適當的hash函式,從而平均的分配關鍵字在桶中的位置,從而優化查詢 插入和刪除所用的時間。

核心雜湊資料結構:

struct hlist_head 

;struct hlist_node

;

因為雜湊鍊錶並不需要雙向迴圈的技能,它一般適用於單向雜湊的場景。

所以,為了減少開銷,並沒有用struct hlist_node{}來代表雜湊表頭,而是重新設計struct hlist_head{}這個資料結構。此時,乙個雜湊表頭就只需要4byte了,相比於struct hlist_node{}來說,儲存空間已經減少了一半。

這樣一來,在需要大量用到雜湊鍊錶的場景,其儲存空間的節約是非常明顯的,特別是在嵌入式裝置領域。

在hlist中,表頭中沒有prev,只有乙個first,為了能統一地修改表頭的first指標hlist就設計了pprev。

node節點裡的pprev其實指向的是其前乙個節點裡的第乙個指標元素的位址。對於hlist_head來說,它裡面只有乙個指標元素,就是first指標;而對於hlist_node來說,第乙個指標元素就是next。

所以,當在**中看到類似與 *(hlist_node->pprev) 這樣的**時,表示此時正在雜湊表裡操作當前節點前乙個節點裡的第乙個指標元素所指向的記憶體位址。

教課書上的hash函式一般都是對模數取餘,模數一般就是hash表的長度(桶的個數),通常為了較好的雜湊性,還把模數調整為乙個質數.

那麼核心中的hash函式是什麼樣呢?

其實不同的使用場景,需要用到的hash函式是不同的。

比如linux netfilter中對會話的hash 函式

static u_int32_t __hash_conntrack

(const

struct nf_conntrack_tuple *tuple,

unsigned

int size,

unsigned

int rnd)

static

inline u_int32_t hash_conntrack

(const

struct nf_conntrack_tuple *tuple)

Linux核心資料結構

一 概述 linux核心提供了一些常用的資料結構並建議開發者盡量重用,而不需要再去實現一些類似的資料結構。這篇部落格主要描述一下linux核心提供的鍊錶 佇列 對映及二叉樹這四種常用資料結構。當然這裡提到的每一種資料結構要說清楚都需要不少的篇幅,而這篇博文只是簡要分析一下linux核心設計的理念,以...

linux核心資料基本結構1 核心鍊錶

linux核心 大量使用了鍊錶這種資料結構。鍊錶是在解決陣列不能動態擴充套件這個缺陷而產生的一種資料結構。鍊錶所包含的元素可以動態建立並插入和刪除。鍊錶的每個元素都是離散存放的,因此不需要占用連續的記憶體。鍊錶通常由若干節點組成,每個節點的結構都是一樣的,由有效資料區和指標區兩部分組成。有效資料區用...

linux核心系列 二 核心資料結構之鍊錶

傳統鍊錶與linu核心鍊錶的區別圖 圖一 圖二從上圖中看出在傳統鍊錶中各種不同鍊錶間沒有通用性,因為各個資料域不同,而在linux核心中巧妙將鍊錶結構內嵌到資料域結構中使得不同結構之間能連線起來 核心中煉表實現檔案路徑 include linux list.h 鍊錶結構定義 struct list ...