在linux內核中list的使用很頻繁,使用管理對象,下面來詳細說明其用法。
1鏈表結構定義
首先看鏈表的定義,位於:include\linux\types.h
1 struct list_head { 2 struct list_head *next, *prev; 3 };
一般將該數據結構嵌入到其他的數據結構中,從而使得內核可以通過鏈表的方式管理新的數據結構,比如struct device中:
1 struct device { 2 struct device *parent; 3 4 struct device_private *p; 5 6 struct kobject kobj; 7 ... 8 struct list_head devres_head;//嵌入的鏈表 9 10 struct klist_node knode_class; 11 struct class *class; 12 const struct attribute_group **groups; /* optional groups */ 13 14 void (*release)(struct device *dev); 15 struct iommu_group *iommu_group; 16 }
2 鏈表的定義和初始化
有兩種方式來定義和初始化鏈表頭:
(1)利用宏LIST_HEAD
(2)利用宏LIST_HEAD_INIT
例如定義鏈表mylist:
方法1:定義並初始化鏈表
LIST_HEAD(mylist);
方法2:先定義再初始化鏈表
struct list_head mylist; // 定義一個鏈表
INIT_LIST_HEAD(&mylist); // 用INIT_LIST_HEAD函數初始化鏈表。
看宏LIST_HEAD就知道就是用宏INIT_LIST_HEAD
#define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name)
再看宏INIT_LIST_HEAD的定義:
#define LIST_HEAD_INIT(name) { &(name), &(name) }
定義的mylist鏈表,宏展開就是
struct list_head mylist = { &(mylist), &(mylist) };
鏈表list_head結構只有兩個成員:next和prev。next和prev都被賦值為鏈表mylist的地址,也就是說,鏈表初始化后next和prev都是指向自己的。
對於struct device 中嵌入的list成員devres_head的初始化如下這樣:
struct device mydevice; INIT_LIST_HEAD(&mydevice.list); //該函數簡單地將list成員的prev和next指針指向自己。
所以鏈表結點在初始化時,就是將prev和next指向自己。對鏈表的初始化非常重要,因為如果使用一個未被初始化的鏈表結點,很有可能會導致內核異常。
3 list的操作:
對鏈表常用的操作,一般就是添加、刪除、遍歷等。內核還會有其他的操作,比如替換、移動等,但這些的操作一般都是以添加、刪除等操作為基礎完成的。
3.1 鏈表的添加
添加有兩種:
(1)list_add 將一個新鏈表結點插入到一個已知結點的后面;
(2)list_add_tail 將一個新鏈表結點插入到一個已知結點的前面
看他們的定義:
1 /** 2 * list_add - add a new entry 3 * @new: new entry to be added 4 * @head: list head to add it after 5 * 6 * Insert a new entry after the specified head. 7 * This is good for implementing stacks. 8 */ 9 static inline void list_add(struct list_head *new, struct list_head *head) 10 { 11 __list_add(new, head, head->next); 12 } 13 14 /** 15 * list_add_tail - add a new entry 16 * @new: new entry to be added 17 * @head: list head to add it before 18 * 19 * Insert a new entry before the specified head. 20 * This is useful for implementing queues. 21 */ 22 static inline void list_add_tail(struct list_head *new, struct list_head *head) 23 { 24 __list_add(new, head->prev, head); 25 }
上面鏈表添加的兩種方式是以不同的參數調用_list_add,看_list_add的定義
1 static inline void __list_add(struct list_head *new, 2 struct list_head *prev, 3 struct list_head *next) 4 { 5 next->prev = new; 6 new->next = next; 7 new->prev = prev; 8 prev->next = new; 9 }
_list_add函數將new結點插入到prev結點和next之間。
(1) list_add函數中以new、head、head->next為參數調用__list_add,將new結點插入到head和head->next之間,即是把new結點插入到已知結點head的后面。
(2)list_add_tail函數則以new、head->prev、head為參數調用__list_add,將new結點插入到head->prev和head之間,即是把new結點插入到已知結點head的前面。
3.2 鏈表的刪除
刪除也是有兩種方式:
(1)list_del 刪除鏈表中的一個結點。
(2)list_del_init 刪除鏈表中的一個結點,並初始化被刪除的結點(使被刪除的結點的prev和next都指向自己);
分別看它們的定義:
1 static inline void list_del(struct list_head *entry) 2 { 3 __list_del(entry->prev, entry->next); 4 entry->next = LIST_POISON1; 5 entry->prev = LIST_POISON2; 6 } 7 8 /** 9 * list_del_init - deletes entry from list and reinitialize it. 10 * @entry: the element to delete from the list. 11 */ 12 static inline void list_del_init(struct list_head *entry) 13 { 14 __list_del_entry(entry); 15 INIT_LIST_HEAD(entry); 16 }
_list_del_entry
1 static inline void __list_del_entry(struct list_head *entry) 2 { 3 __list_del(entry->prev, entry->next); 4 }
所以兩者都是調用了_list_del,讓prev結點和next結點互相指向。
1 /* 2 * Delete a list entry by making the prev/next entries 3 * point to each other. 4 * 5 * This is only for internal list manipulation where we know 6 * the prev/next entries already! 7 */ 8 static inline void __list_del(struct list_head * prev, struct list_head * next) 9 { 10 next->prev = prev; 11 prev->next = next; 12 }
(1)list_del 函數中以entry->prev和entry->next為參數調用__list_del函數,使得entry結點的前、后結點繞過entry直接互相指向,然后將entry結點的前后指針指向LIST_POISON1和LIST_POISON2,從而完成對entry結點的刪除。此函數中的LIST_POISON1和LIST_POISON2是內核的處理定義如下。一般刪除entry后,應該讓entry的prev和next指向NULL的。
1 /* 2 * These are non-NULL pointers that will result in page faults 3 * under normal circumstances, used to verify that nobody uses 4 * non-initialized list entries. 5 */ 6 #define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA) 7 #define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)
(2)list_del_init 函數將entry結點刪除后,與_list_del不同的是:還會對entry結點初始化,使entry結點的prev和next都指向其自己。
4 鏈表在內核中的應用
list_for_each_entry
首先看定義,位於:include\linux\list.h
1 /** 2 * list_for_each_entry - iterate over list of given type 3 * @pos: the type * to use as a loop cursor. 4 * @head: the head for your list. 5 * @member: the name of the list_struct within the struct. 6 */ 7 #define list_for_each_entry(pos, head, member) \ 8 for (pos = list_entry((head)->next, typeof(*pos), member); \ 9 &pos->member != (head); \ 10 pos = list_entry(pos->member.next, typeof(*pos), member))
實際上是一個 for 循環,用傳入的 pos 作為循環變量,從表頭 head 開始,逐項向后(next 方向)移動 pos,一直到回到head.。
(1)變量的初始化:pos = list_entry((head)->next, typeof(*pos), member),每次pos拿到的都是鏈表中一個成員,注意這個成員實際上是一個結構體
(2)執行條件 &pos->member != (head),確定拿到的成員不是head,是head的話,表示list已遍歷完。
(3)每循環一次執行 pos = list_entry(pos->member.next, typeof(*pos), member)),是pos指定鏈表中下一個成員,注意其實際上還是結構體
以上中用到typeof(),它是取變量的類型,這里是取指針pos所指向數據的類型。
4.1 看宏list_entry的定義:
1 /** 2 * list_entry - get the struct for this entry 3 * @ptr: the &struct list_head pointer. 4 * @type: the type of the struct this is embedded in. 5 * @member: the name of the list_struct within the struct. 6 */ 7 #define list_entry(ptr, type, member) \ 8 container_of(ptr, type, member)
4.2 調用了container_of。
1 /** 2 * container_of - cast a member of a structure out to the containing structure 3 * @ptr: the pointer to the member. 4 * @type: the type of the container struct this is embedded in. 5 * @member: the name of the member within the struct. 6 * 7 */ 8 #define container_of(ptr, type, member) ({ \ 9 const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 10 (type *)( (char *)__mptr - offsetof(type,member) );})
container_of是根據一個結構體變量中的一個成員變量指針ptr,來獲取指向這個結構體變量type的指針。member是結構體type中成員ptr的變量名。下面分解一步一步分析:
4.2.1 先分析第一句:const typeof( ((type *)0)->member ) *__mptr = (ptr);
(1)(type *)0) 將0強轉為一個地址,這個地址(0x0000)指向的是類型type的數據,就是指向結構體。當然,這只是個技巧,並不是真的在地址0x0000存放了數據。
(2)((type *)0)->member :‘->’是指針指取結構體成員的操作。指針就是剛才通過0強轉的地址,即結構體指針。相當於地址0x0000 是結構體類型type的首地址,通過->’取其中的成員變量member。
(3)typeof( ((type *)0)->member ) *__mptr = (ptr):拿到了member成員的類型,然后定義一個指針變量__mptr,指向的類型就是結構體成員member的類型,初始化值為ptr。
4.2.2 再分析第二句:(type *)( (char *)__mptr - offsetof(type,member) );
(1)offsetof(type,member) : 定義如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
((TYPE *)0) :將整型常量0強制轉換為TYPE型的指針,即結構體類型。且這個指針指向的地址為0,也就是將地址0開始的一塊存儲空間映射為TYPE型的對象。
((TYPE *)0)->MEMBER :指向結構體中MEMBER成員。
&((TYPE *)0)->MEMBER):對結構體中MEMBER成員進行取址,而整個TYPE結構體的首地址是0,這里獲得的地址就是MEMBER成員在TYPE中的相對偏移量。
(size_t) &((TYPE *)0)->MEMBER:最后將這個偏移量強制轉換成size_t型數據也就是無符號整型。
所以offsetof的作用就是求出結構體成員變量member在結構體中的偏移量。
(2)(char *)__mptr - offsetof(type,member):member類型的指針減去member在結構體中的偏移量,就是結構體的起始位置,即指向結構體。
至此綜上所述,list_entry 也就是container_of的作用就是:根據結構體中的成員變量和此變量的指針,拿到結構體的指針。
4.3 再回到list_for_each_entry
1 #define list_for_each_entry(pos, head, member) \ 2 for (pos = list_entry((head)->next, typeof(*pos), member); \//鏈表中的成員也是結構體,根據結構體成員head->next獲取此鏈表成員 3 &pos->member != (head); \ //確認此時拿到的鏈表成員不是鏈表的頭成員 4 pos = list_entry(pos->member.next, typeof(*pos), member))//獲取鏈表中下一個成員結構體
list_for_each_entry在內核中的應用也很常見,通常用於遍歷某一個鏈表。
