Linux核心之資料雙連結串列
導讀 | 核心中自己實現了雙向連結串列,可以在 include/linux/list.h 找到定義。我們將會首先從雙向連結串列資料結構開始介紹核心裡的資料結構。為什麼?因為它在核心裡使用的很廣泛,你只需要在 free-electrons.com 檢索一下就知道了。 |
struct list_head { struct list_head *next, *prev; };
舉個例子來說,在 glib 庫裡是這樣實現的:
struct GList { gpointer data; GList *next; GList *prev; };
但是 Linux 核心中的連結串列實現並沒有這樣做。所以問題來了:連結串列在哪裡儲存資料呢?實際上,核心裡實現的連結串列是侵入式連結串列(Intrusive list)。侵入式連結串列並不在節點內儲存資料-它的節點僅僅包含指向前後節點的指標,以及指向連結串列節點資料部分的指標——資料就是這樣附加在連結串列上的。這就使得這個資料結構是通用的,使用起來就不需要考慮節點資料的型別了。
struct nmi_desc { spinlock_t lock; struct list_head head; };
如上所述,在核心裡有很多很多不同的地方都用到了連結串列。我們來看一個在雜項字元驅動裡面的使用的例子。在 drivers/char/misc.c 的雜項字元驅動 API 被用來編寫處理小型硬體或虛擬裝置的小驅動。這些驅動共享相同的主裝置號:
#define MISC_MAJOR 10
ls -l /dev | grep 10 crw------- 1 root root 10, 235 Mar 21 12:01 autofs drwxr-xr-x 10 root root 200 Mar 21 12:01 cpu crw------- 1 root root 10, 62 Mar 21 12:01 cpu_dma_latency crw------- 1 root root 10, 203 Mar 21 12:01 cuse drwxr-xr-x 2 root root 100 Mar 21 12:01 dri crw-rw-rw- 1 root root 10, 229 Mar 21 12:01 fuse crw------- 1 root root 10, 228 Mar 21 12:01 hpet crw------- 1 root root 10, 183 Mar 21 12:01 hwrng crw-rw----+ 1 root kvm 10, 232 Mar 21 12:01 kvm crw-rw---- 1 root disk 10, 237 Mar 21 12:01 loop-control crw------- 1 root root 10, 227 Mar 21 12:01 mcelog crw------- 1 root root 10, 59 Mar 21 12:01 memory_bandwidth crw------- 1 root root 10, 61 Mar 21 12:01 network_latency crw------- 1 root root 10, 60 Mar 21 12:01 network_throughput crw-r----- 1 root kmem 10, 144 Mar 21 12:01 nvram brw-rw---- 1 root disk 1, 10 Mar 21 12:01 ram10 crw--w---- 1 root tty 4, 10 Mar 21 12:01 tty10 crw-rw---- 1 root dialout 4, 74 Mar 21 12:01 ttyS10 crw------- 1 root root 10, 63 Mar 21 12:01 vga_arbiter crw------- 1 root root 10, 137 Mar 21 12:01 vhci
struct miscdevice { int minor; const char *name; const struct file_operations *fops; struct list_head list; struct device *parent; struct device *this_device; const char *nodename; mode_t mode; };
在原始碼檔案的開始可以看到這個連結串列的定義:
static LIST_HEAD(misc_list);
它實際上是對用list_head 型別定義的變數的擴充套件。
#define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name)
這會使用變數name 的地址來填充prev和next 結構體的兩個變數。
#define LIST_HEAD_INIT(name) { &(name), &(name) }
它在一開始就用函式 INIT_LIST_HEAD 初始化了miscdevice->list。
INIT_LIST_HEAD(&misc->list);
static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; }
我們就用下面的語句將裝置新增到裝置連結串列:
list_add(&misc->list, &misc_list);
我們來看看它的實現:
static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); }
new - 新項。
head - 新項將會插在head的後面
head->next - 插入前,head 後面的項。
__list_add的實現非常簡單:
static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; }
所以我們開始時用宏LIST_HEAD_INIT定義的misc 連結串列會包含指向miscdevice->list 的向前指標和向後指標。
這兒還有一個問題:如何得到列表的內容呢?這裡有一個特殊的宏:
#define list_entry(ptr, type, member) \ container_of(ptr, type, member)
ptr - 指向結構 list_head 的指標;
type - 結構體型別;
member - 在結構體內型別為list_head 的變數的名字;
const struct miscdevice *p = list_entry(v, struct miscdevice, list)
然後我們就可以使用p->minor 或者 p->name來訪問miscdevice。讓我們來看看list_entry 的實現:
#define list_entry(ptr, type, member) \ container_of(ptr, type, member)
如我們所見,它僅僅使用相同的引數呼叫了宏container_of。初看這個宏挺奇怪的:
#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})
編譯器會執行花括號內的全部語句,然後返回最後的表示式的值。
#include <stdio.h> int main() { int i = 0; printf("i = %d\n", ({++i; ++i;})); return 0; }
最終會列印出2。
就如你從名字所理解的,它僅僅返回了給定變數的型別。當我第一次看到宏container_of的實現時,讓我覺得最奇怪的就是表示式((type *)0)中的0。實際上這個指標巧妙的計算了從結構體特定變數的偏移,這裡的0剛好就是位寬裡的零偏移。
#include <stdio.h> struct s { int field1; char field2; char field3; }; int main() { printf("%p\n", &((struct s*)0)->field3); return 0; }
結果顯示0x5。
它的實現和上面類似:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
現在我們來總結一下宏container_of。只需給定結構體中list_head型別 欄位的地址、名字和結構體容器的型別,它就可以返回結構體的起始地址。在宏定義的第一行,宣告瞭一個指向結構體成員變數ptr的指標__mptr,並且把ptr 的地址賦給它。現在ptr 和__mptr 指向了同一個地址。從技術上講我們並不需要這一行,但是它可以方便地進行型別檢查。第一行保證了特定的結構體(引數type)包含成員變數member。第二行程式碼會用宏offsetof計算成員變數相對於結構體起始地址的偏移,然後從結構體的地址減去這個偏移,最後就得到了結構體。
提供的唯一功能。雙向連結串列的實現還提供瞭如下API:
list_add list_add_tail list_del list_replace list_move list_is_last list_empty list_cut_position list_splice list_for_each list_for_each_entry
等等很多其它API。
原文來自:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2915717/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 資料結構-單連結串列、雙連結串列資料結構
- 【資料結構】雙連結串列(c++)資料結構C++
- 實戰PHP資料結構基礎之雙連結串列PHP資料結構
- 看得見的資料結構Android版之雙連結串列篇資料結構Android
- 深入理解Redis 資料結構—雙連結串列Redis資料結構
- 單雙連結串列
- 資料結構思維 第五章 雙連結串列資料結構
- Linux 核心資料結構:雙向連結串列Linux資料結構
- 基本資料結構實現--雙連結串列【含測試程式碼】資料結構
- 4-雙連結串列的操作
- 019 通過連結串列學Rust之雙連結串列實現PeekRust
- 019 透過連結串列學Rust之雙連結串列實現PeekRust
- 圖解雙連結串列(Java實現)圖解Java
- Linux核心連結串列Linux
- 016 通過連結串列學習Rust之安全的雙連結串列佈局Rust
- 016 透過連結串列學習Rust之安全的雙連結串列佈局Rust
- 核心中的連結串列資料結構資料結構
- 迴圈雙連結串列的簡單操作
- 資料結構之「連結串列」資料結構
- 資料結構之連結串列資料結構
- linux核心資料結構之kfifo【轉】Linux資料結構
- 雙連結串列插入排序例項程式碼排序
- Linux核心連結串列-通用連結串列的實現Linux
- JavaScript資料結構 之 連結串列JavaScript資料結構
- 資料結構之連結串列【上】資料結構
- 資料結構之連結串列操作資料結構
- JAVA資料結構之連結串列Java資料結構
- 資料結構之單連結串列資料結構
- js在Node.js下實現單連結串列與雙連結串列結構Node.js
- 陣列模擬雙連結串列,你get到了嗎?陣列
- 資料結構之迴圈連結串列資料結構
- 資料結構之雙向連結串列資料結構
- linux核心原始碼 -- list連結串列Linux原始碼
- 深入分析LInux核心連結串列Linux
- 資料結構實驗之連結串列九:雙向連結串列資料結構
- 資料結構實驗之連結串列二:逆序建立連結串列資料結構
- Redis基礎資料結構之連結串列Redis資料結構
- JavaScript資料結構之連結串列--設計JavaScript資料結構