深入分析 Linux作業系統的核心鍊錶 三

2021-06-22 02:20:30 字數 3530 閱讀 5438

三、 鍊錶操作介面

1. 宣告和初始化

實際上 linux 只定義了鍊錶節點,並沒有專門定義煉表頭,那麼乙個鍊錶結構是如何建立起來的呢?讓我們來看看 list_head() 這個巨集:

#define list_head_init(name)  

#define list_head(name) struct list_head name = list_head_init(name)

當我們用 list_head(nf_sockopts) 宣告乙個名為 nf_sockopts 的煉表頭時,它的 next、prev 指標都初始化為指向自己,這樣,我們就有了乙個空鍊錶,因為linux 用頭指標的 next 是否指向自己來判斷鍊錶是否為空:

static inline int list_empty(const struct list_head *head) 

除了用 list_head() 巨集在宣告的時候初始化乙個鍊錶以外,linux 還提供了乙個 init_list_head 巨集用於執行時初始化鍊錶:

#define init_list_head(ptr) do  while (0)

我們用 init_list_head(&nf_sockopts) 來使用它。

2. 插入/刪除/合併

a) 插入

對鍊錶的插入操作有兩種:在表頭插入和在表尾插入。linux為此提供了兩個介面:

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);

因為 linux 鍊錶是迴圈表,且表頭的 next、prev 分別指向鍊錶中的第乙個和最末乙個節點,所以,list_add 和 list_add_tail 的區別並不大,實際上,linux 分別用

__list_add(new, head, head->next);

__list_add(new, head->prev, head);

來實現兩個介面,可見,在表頭插入是插入在 head 之後,而在表尾插入是插入在 head->prev 之後。

假設有乙個新 nf_sockopt_ops 結構變數 new_sockopt 需要新增到 nf_sockopts 煉表頭,我們應當這樣操作:

list_add(&new_sockopt.list, &nf_sockopts);

從這裡我們看出,nf_sockopts 鍊錶中記錄的並不是 new_sockopt 的位址,而是其中的 list 元素的位址。如何通過鍊錶訪問到 new_sockopt 呢?下面會有詳細介紹。

b) 刪除

static inline void list_del(struct list_head *entry);

當我們需要刪除 nf_sockopts 鍊錶中新增的 new_sockopt 項時,我們這麼操作:

list_del(&new_sockopt.list);

被剔除下來的 new_sockopt.list,prev、next 指標分別被設為 list_position2 和 list_position1 兩個特殊值,這樣設定是為了保證不在鍊錶中的節點項不可訪問--對 list_position1 和 list_position2 的訪問都將引起頁故障。與之相對應, list_del_init() 函式將節點從煉表中解下來之後,呼叫 list_init_head() 將節點置為空鏈狀態。

c) 搬移

linux 提供了將原本屬於乙個鍊錶的節點移動到另乙個鍊錶的操作,並根據插入到新鍊錶的位置分為兩類:

static inline void list_move

(struct list_head *list, struct list_head *head);

static inline void list_move_tail

(struct list_head *list, struct list_head *head);

例如 list_move(&new_sockopt.list,&nf_sockopts) 會把 new_sockopt 從它所在的鍊錶上刪除,並將其再鏈入 nf_sockopts 的表頭。

d) 合併

除了針對節點的插入、刪除操作,linux鍊錶還提供了整個鍊錶的插入功能:

static inline void list_splice

(struct list_head *list, struct list_head *head);

假設當前有兩個鍊錶,表頭分別是 list1 和 list2(都是 struct list_head 變數),當呼叫 list_splice(&list1,&list2) 時,只要 list1 非空,list1鍊錶的內容將被掛接在 list2 鍊錶上,位於 list2 和 list2.next(原 list2 表的第乙個節點)之間。新 list2 鍊錶將以原 list1 表的第乙個節點為首節點,而尾節點不變。如圖(虛箭頭為next指標):

當 list1 被掛接到 list2 之後,作為原表頭指標的 list1 的 next、prev 仍然指向原來的節點,為了避免引起混亂,linux 提供了乙個 list_splice_init() 函式:

static inline void list_splice_init

(struct list_head *list, struct list_head *head);

該函式在將 list 合併到 head 鍊錶的基礎上,呼叫 init_list_head(list) 將 list 設定為空鏈。

3. 遍歷

遍歷是鍊錶最經常的操作之一,為了方便核心應用遍歷鍊錶,linux鍊錶將遍歷操作抽象成幾個巨集。在介紹遍歷巨集之前,我們先看看如何從鍊錶中訪問到我們真正需要的資料項。

a) 由鍊錶節點到資料項變數

我們知道,linux鍊錶中僅儲存了資料項結構中 list_head 成員變數的位址,那麼我們如何通過這個 list_head 成員訪問到作為它的所有者的節點資料呢?linux 為此提供了乙個 list_entry(ptr,type,member) 巨集,其中ptr是指向該資料中 list_head 成員的指標,也就是儲存在鍊錶中的位址值,type 是資料項的型別,member 則是資料項型別定義中 list_head 成員的變數名,例如,我們要訪問 nf_sockopts 鍊錶中首個 nf_sockopt_ops 變數,則如此呼叫:

list_entry(nf_sockopts->next, struct nf_sockopt_ops, list);

這裡 "list" 正是 nf_sockopt_ops 結構中定義的用於鍊錶操作的節點成員變數名。

linux的man命令深入分析

man有如下8個模組.1 shell中使用者可用的命令 2 使用函式庫中程式可用的系統呼叫 3 程式中可用的庫函式 4 dev目錄中可用的裝置 5 多種雜項系統檔案 ex etc 6 如果有的話,遊戲程式 7 雜項資訊 8 管理員可用的命令 1 man命令是如何搜尋命令對映的幫助檔案的?2 幫助檔案...

Linux 檢視作業系統版本和linux核心版本

root tg lsb release a lsb version core 4.1 amd64 core 4.1 noarch distributor id centos description centos linux release 7.5.1804 core release 7.5.1804...

new的深入分析

new 是c 的乙個關鍵字,同時也是操作符。關於new的話題非常多,因為它確實比較複雜,也非常神秘,下面我將把我了解到的與new有關的內容做乙個總結。new的過程 當我們使用關鍵字new在堆上動態建立乙個物件時,它實際上做了三件事 獲得一塊記憶體空間 呼叫建構函式 返回正確的指標。當然,如果我們建立...