中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何分析Linux內核雙向鏈表

發布時間:2022-01-26 17:45:05 來源:億速云 閱讀:112 作者:柒染 欄目:開發技術

這篇文章將為大家詳細講解有關如何分析Linux內核雙向鏈表,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。

Linux中雙向鏈表是指將雙向鏈表節點嵌套在其它的結構體中;在遍歷鏈表的時候,根據雙鏈表節點的指針獲取”它所在結構體的指針”,從而再獲取數據。

首先讓我們看一下在 include/linux/types.h 里的主結構體:

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

讓我們看幾個例子來理解一下在內核里是如何使用 list_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

現在讓我們看看它是如何使用鏈表的。首先看一下結構體 miscdevice:

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

可以看到結構體miscdevice的第四個變量list 是所有注冊過的設備的鏈表。

在源代碼文件的開始可以看到這個鏈表的定義:

 static LIST_HEAD(misc_list);

它實際上是對用list_head 類型定義的變量的擴展。

 #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name)

然后使用宏 LIST_HEAD_INIT 進行初始化,

這會使用變量name 的地址來填充prev和next 結構體的兩個變量。

 #define LIST_HEAD_INIT(name) { &(name), &(name) }

現在來看看注冊雜項設備的函數misc_register。

它在一開始就用函數 INIT_LIST_HEAD 初始化了miscdevice->list。

 INIT_LIST_HEAD(&misc->list);

作用和宏LIST_HEAD_INIT一樣。

 static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; }

接下來,在函數device_create 創建了設備后,

我們就用下面的語句將設備添加到設備鏈表:

 list_add(&misc->list, &misc_list);

內核文件list.h 提供了向鏈表添加新項的 API 接口。

我們來看看它的實現:

 static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); }

實際上就是使用3個指定的參數來調用了內部函數__list_add:  

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

這里,我們在prev和next 之間添加了一個新項。

所以我們開始時用宏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 int main() { int i = 0; printf("i = %d\n", ({++i; ++i;})); return 0; }

最終會打印出2。

下一點就是typeof,它也很簡單。

就如你從名字所理解的,它僅僅返回了給定變量的類型。當我第一次看到宏container_of的實現時,讓我覺得最奇怪的就是表達式((type *)0)中的0。實際上這個指針巧妙的計算了從結構體特定變量的偏移,這里的0剛好就是位寬里的零偏移。

比如:

 #include struct s { int field1; char field2; char field3; }; int main() { printf("%p\n", &((struct s*)0)->field3); return 0; }

結果顯示0x5。

下一個宏offsetof會計算從結構體起始地址到某個給定結構字段的偏移。

它的實現和上面類似: #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 現在我們來總結一下宏container_of。只需給定結構體中list_head類型 字段的地址、名字和結構體容器的類型,它就可以返回結構體的起始地址。在宏定義的第一行,聲明了一個指向結構體成員變量ptr的指針mptr,并且把ptr 的地址賦給它。現在ptr 和mptr 指向了同一個地址。從技術上講我們并不需要這一行,但是它可以方便地進行類型檢查。第一行保證了特定的結構體(參數type)包含成員變量member。第二行代碼會用宏offsetof計算成員變量相對于結構體起始地址的偏移,然后從結構體的地址減去這個偏移,最后就得到了結構體。

當然了list_add 和 list_entry不是

提供的唯一功能。雙向鏈表的實現還提供了如下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

關于如何分析Linux內核雙向鏈表就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

龙岩市| 万盛区| 平泉县| 榕江县| 白朗县| 新田县| 荔波县| 邵武市| 西吉县| 资兴市| 门源| 鲁甸县| 来安县| 中卫市| 娄底市| 湾仔区| 上栗县| 泾阳县| 丁青县| 赤城县| 哈尔滨市| 湟中县| 吴堡县| 东乌| 江华| 大英县| 汉阴县| 普安县| 大邑县| 越西县| 当阳市| 金门县| 昌江| 天峨县| 建阳市| 离岛区| 安化县| 延庆县| 甘洛县| 景宁| 寿光市|