文章參照任橋位Linux核心修鍊之道3.6節編寫。
在Linux核心中大量地方使用了鏈表這個資料結構。相信科班出身的學生或者自己學習過資料結構的同學都不陌生,不錯,他就是最簡單的線性結構——鏈表。不過,在核心當中,一般採用的都是迴圈雙聯表的資料結構。因為源碼有三百多行我就不貼在這裡,有興趣的去下載一下:http://download.csdn.net/detail/huiguixian/3889011。
1. 鏈表的定義
這個跟我們在課本上學習的一樣,相當簡單。包括了一個前項指標,和後項指標。是不是有點不對勁?不錯,竟然沒有資料域!不急,我們慢慢看。
struct list_head { struct list_head *next, *prev;};
沒有資料是核心鏈表的一大特色,因為他採用的方式比較特殊,他不是用鏈表來包含資料的,而是讓資料項目反回來包含鏈表的。剛開始多多少少有點難以理解,下面會解釋的。
2. 鏈表的的定義和初始化
(1)採用LIST_HEAD宏在編譯時間靜態初始化
#defineLIST_HEAD_INIT(name) { &(name), &(name) }#defineLIST_HEAD(name) \ struct list_head name =LIST_HEAD_INIT(name)
LIST_HEAD_INIT是宏定義,也就是說在定義的時候把他擴充一下就很容易理解了。比如初始化語句為
LIST_HEAD(event_list),可以理解為
struct list_headevent_list = { &event_list, &event_list }
結構體大家應該還沒有忘記吧,裡面有一條可以按照成員順序在定義時對其進行初始化,所以這句就很明顯了。目的是把next prev指標初始化指向它本身。
(2)採用INIT_LIST_HEAD函數在運行時動態初始化,這個目的一眼就看出來了,同上面一樣。
static inline voidINIT_LIST_HEAD(struct list_head *list){ list->next = list; list->prev = list;}
3. 判斷鏈表是否為空白的操作,即是判斷是否指向自己本身而已
static inline intlist_empty(const struct list_head *head){ return head->next == head;}
4.插入操作,學過鏈表操作的都看得懂,看不懂的自己去學鏈表去。
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;}static inline voidlist_add(struct list_head *new, struct list_head *head){ __list_add(new, head,head->next);}static inline voidlist_add_tail(struct list_head *new, struct list_head *head){ __list_add(new, head->prev,head);}
5. 移動、刪除等等類似,主要講遍曆!遍曆的精彩部分在於鏈表是被資料包含著的,如何通過被包含的鏈表取出包含他的資料(有點拗口)
比如書上舉的那個例子:
struct list_head*tmp;struct usb_hub *hub;tmp =hub_event_list.next;hub = list_entry(tmp,struct usb_hub, event_list);
資料結構是usb_hub,裡麵包含著一個list_head資料項目,然後現在有一個list_head的鏈表hub_event_list,要取出裡麵包含hub_event_list.next的資料usb_hub。這就是上述代碼的功能。最重要的函數為list_entry,代碼如下:
#definelist_entry(ptr, type, member) \ container_of(ptr, type, member)
這個不用解釋,他調用的是container_of(ptr, type, member),直接看這個宏定義
#definecontainer_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member )*__mptr = (ptr); \ (type*)( (char *)__mptr - offsetof(type,member) );})#defineoffsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
這個看起來比較費力。需要一步步理解。首先,宏定義不是函數,宏定義的參數不受函數的限制,所以在list_entry和container_of的第二個參數都是以資料類型做參數的。另外GCC有一個對ISO C的擴充,就是他支援typeof操作,具體可以看這裡:http://blog.csdn.net/huiguixian/article/details/7045311 。主要看最後講解typeof。簡單來看他就是可以返回一個類型,基本可以用在你想用的任何時候。
接著上面的例子來解釋:
type為usb_hub,type *就是usb_hub*,0可以理解為NULL,也就是usb_hub->event_list就是((type *)0)->member。一整句就是定義了一個list_head類型的常量指標,指向了參數的event_list。然後下一步是通過計算位移量,讓這個指標減去這個位移量,即減去後的指標指向的可以看作是一個usb_hub的資料結構,至此就把usb_hub取出來了。