linux中list的使用

 (注:最近在研究系統源碼,發現有些時候,要自己寫才能知道自己是否真的已完全理解。也可藉此加深記憶,鍛鍊自己的表達,所以會經常在博客中亂寫,如果有錯,千萬表拍磚……)

        linux內核實現中,涉及到很多的隊列,比如運行隊列runqueue,其中保存了所有的處於就緒狀態等待運行的進程的task_struct結構體對象,它們是怎麼鏈接起來的?當然是通過list了。如果看過task_struct的實現,就會知道其中有一個list_head對象run_list,list_head就是linux中list的實現,其實現爲雙向鏈表。

struct list_head{
struct list_head *next,*prev;
}

        學習過數據結構吧!!! 這個雙向鏈表的實現我們很熟悉,可如果你對當年寫的鏈表記憶猶存的話,應該會發現這個雙向鏈表的實現中是沒有具體的數據的。當年我寫的雙線鏈表中一般是這樣:

struct list{
valuetype data;
struct list *prev;
struct list *next;
}

       在data中保存實際的數據,然後是各種隊列操作的實現,插入、刪除等等。在linux之所以將鏈表實現跟具體的數據分開,是因爲在linux內核中用到許多隊列,這也就意味着list_head要被許多地方用到,自然就要抽象出list_head的實現。

       所以linux中list_head的使用形式是這樣的:

struct **queue{
     ....
     list_head *list;
}

       在需要的地方如這裏的**queue中加入list_head,然後如果需要構建一個**queue的隊列,假如說是taskqueue則只需要聲明一個雙向鏈表頭taskqueue,

list_head taskqueue;

        通過list_head就可以將它們鏈接起來形成隊列。是不是很高明??? 問題還是有的……想想linux中的進程調度,它是怎麼做的?是的,它要遍歷運行隊列runqueue(貌似是這個名字……,知道是運行隊列就好了),取得進程的task_struct,計算其權值,以此來決定調度那個進程運行。

       這就奇怪了?通過list_head鏈接起來的運行隊列,其中只有list_head(task_struct是通過它裏面的list_head結構對象run_list鏈接到運行隊列的),那麼如何根據結構中list_head成員對象,找到其宿主對象,如task_struct呢???看到list_entry操作函數的實現你就會明白了。

#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)
 
#define container_of(ptr, type, member) ({			\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - offsetof(type,member) );})
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

       具體list_entry的使用是,通過傳入隊列指針對象,宿主結構體類型,宿主結構體中list_head的成員名來取得具體對象指針。比如取得要獲取隊列p中的task_struct,則可以list_entry(p,struct task_struct,run_list)。

list_entry的實現原理是。offsetof宏將0強制轉換爲type類型對象指針,以->MEMBER取得其成員對象,再以&取得成員對象地址,因爲起始是0,所以這裏得到的也就是MEMBER的偏移地址。container_of宏中((type *)0)->member 道理是一樣的,不過它通過typeof取得成員的類型,並定義成員類型的指針__mptr並賦值爲傳入的對象指針ptr,指針也即存的對象的地址,所以將成員指針__mptr減去成員的偏移量(也即offsetof宏得到的)得到的就是其宿主結構的地址。





 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章