1. 程式人生 > >Linux內核(10) - 內核中的鏈表

Linux內核(10) - 內核中的鏈表

例子 電梯 是你 head 上下 color ati 老師 控制器

早上上班坐地鐵要排隊,到了公司樓下等電梯要排隊,中午吃飯要排隊,下班了追求一個女孩子也要排隊,甚至在網上下載個什麽門的短片也要排隊,每次看見人群排成一條長龍時,才真正意識到自己是龍的傳人。那麽下面咱們就說說隊列(鏈表)。

使用鏈表的目的很明確,因為有很多事情要做,於是就把它放進鏈表裏,一件事一件事的處理。比如在USB子系統裏,U盤不停的提交urb請求,USB鍵盤也提交,USB鼠標也提交,那USB主機控制器咋應付得過來呢?很簡單,建一個鏈表,然後你每次提交就是往裏邊插入,然後USB主機控制器再統一去調度,一個一個來執行。這裏有力得證明了,譚浩強大哥的C程序設計是我們學習Linux的有力武器,書中對鏈表的介紹無疑是英明的,譚大哥,您不是一個人在戰鬥!

內核中鏈表的實現位於include/linux/list.h文件,鏈表數據結構的定義也很簡單。

21 struct list_head {

22 struct list_head *next, *prev;

23 };

list_head結構包含兩個指向list_head結構的指針prev和next,由此可見,內核中的鏈表實際上都是雙鏈表(通常都是雙循環鏈表)。

通常,我們在數據結構課堂上所了解的鏈表定義方式是這樣的(以單鏈表為例):

struct list_node {

struct list_node *next;

ElemType data;

};

通過這種方式使用鏈表,對每一種數據類型,都要定義它們各自的鏈表結構。而內核中的鏈表卻與此不同,它並沒有數據域,不是在鏈表結構中包含數據,而是在描述數據類型的結構中包含鏈表。

比如在hub驅動中使用struct usb_hub來描述hub設備,hub需要處理一系列的事件,比如當探測到一個設備連進來時,就會執行一些代碼去初始化該設備,所以hub就創建了一個鏈表來處理各種事件,這個鏈表的結構如下圖。

技術分享圖片

(1)聲明與初始化。

鏈表的聲明可以使用兩種方式,一種為使用LIST_HEAD宏在編譯時靜態初始化,一種為使用INIT_LIST_HEAD()在運行時進行初始化。

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

26

27 #define LIST_HEAD(name) /

28 struct list_head name = LIST_HEAD_INIT(name)

29

30 static inline void INIT_LIST_HEAD(struct list_head *list)

31 {

32 list->next = list;

33 list->prev = list;

34 }

無論采用哪種方式,新生成的鏈表頭的兩個指針next、prev都初始化為指向自己。

(2)判斷鏈表是否為空。

298 static inline int list_empty(const struct list_head *head)

299 {

300 return head->next == head;

301 }

(3)插入。

有了鏈表,自然就要往裏面加東西、減東西。就像我們每個人每天都在不停的走進去,又走出來,似是夢境又不是夢境。一切都是不經意的。走進去是一年四季,走出來是春夏秋冬。list_add()和list_add_tail()這兩個函數就是往隊列裏加東西。

67 static inline void list_add(struct list_head *new, struct list_head *head)

68 {

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

70 }

84 static inline void list_add_tail(struct list_head *new, struct list_head *head)

85 {

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

87 }

其中,list_add()將數據插入在head之後,list_add_tail()將數據插入在head->prev之後。其實對於循環鏈表來說,表頭的next、prev分別指向鏈表中的第一個和最後一個節點,所以,list_add()和list_add_tail()的區別並不大。

(4)刪除。

搞懂譚浩強那本書之後看這些鏈表的代碼那就是小菜一碟。再來看下一個 list_del_init(),裏的元素不能只加不減,沒用了的元素就該刪除掉,把空間騰出來給別人。郭敬明說過,我生命裏的溫暖就那麽多,我全部給了你,但是你離開了我,你叫我以後怎麽再對別人笑……

鏈表裏的元素不能只加不減,沒用了的元素就應該刪除掉。

254 static inline void list_del_init(struct list_head *entry)

255 {

256 __list_del(entry->prev, entry->next);

257 INIT_LIST_HEAD(entry);

258 }

list_del_init()從鏈表裏刪除一個元素,並且將其初始化。

(5)遍歷。

內核中的鏈表僅僅保存了list_head結構的地址,我們如何通過它或取一個鏈表節點真正的數據項?這就要提到有關鏈表的所有操作裏面,最為重要超級經典的list_entry宏了,我們可以通過它很容易地獲得一個鏈表節點的數據。

425 #define list_entry(ptr, type, member) /

426 container_of(ptr, type, member)

我相信,list_entry()這個宏在Linux內核代碼中的地位,就相當於廣告詞中的任靜付笛生的洗洗更健康,相當於大美女關之琳的一分鐘輕松做女人,這都是耳熟能詳婦孺皆知的,是經典中的經典。如果你說你不知道list_entry(),那你千萬別跟人說你懂Linux內核,就好比你不知道陳文登不知道任汝芬你就根本不好意思跟人說你考過研,要知道每個考研人都是左手一本陳文登右手一本任汝芬。

可惜,關於list_entry,這個譚浩強老師的書裏就沒有了,當然你不能指責譚浩強的書不行,再好的書也不可能包羅萬象。

關於list_entry(),讓我們結合實例來看,還是hub驅動的那個例子,當我們真的要處理hub的事件的時候,我們當然需要知道具體是哪個hub觸發了這起事件。而list_entry的作用就是,從struct list_head event_list得到它所對應的struct usb_hub結構體變量。比如以下四行代碼:

struct list_head *tmp;

struct usb_hub *hub;

tmp = hub_event_list.next;

hub = list_entry(tmp, struct usb_hub, event_list);

從全局鏈表hub_event_list中取出一個來,叫做tmp,然後通過tmp,獲得它所對應的struct usb_hub。

Linux內核(10) - 內核中的鏈表