(注:最近在研究系統原始碼,發現有些時候,要自己寫才能知道自己是否真的已完全理解。也可藉此加深記憶,鍛鍊自己的表達,所以會經常在部落格中亂寫,如果有錯,千萬表拍磚……)
linux核心實現中,涉及到很多的佇列,比如執行佇列runqueue,其中儲存了所有的處於就緒狀態等待執行的程序的task_struct結構體物件,它們是怎麼鏈結起來的?當然是通過list了。如果看過task_struct的實現,就會知道其中有乙個list_head物件run_list,list_head就是linux中list的實現,其實現為雙向鍊錶。
struct list_head
學習過資料結構吧!!! 這個雙向鍊錶的實現我們很熟悉,可如果你對當年寫的鍊錶記憶猶存的話,應該會發現這個雙向鍊錶的實現中是沒有具體的資料的。當年我寫的雙線鍊錶中一般是這樣:
struct list
在data中儲存實際的資料,然後是各種佇列操作的實現,插入、刪除等等。在linux之所以將鍊錶實現跟具體的資料分開,是因為在linux核心中用到許多佇列,這也就意味著list_head要被許多地方用到,自然就要抽象出list_head的實現。
所以linux中list_head的使用形式是這樣的:
struct **queue
在需要的地方如這裡的**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) ()
#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巨集得到的)得到的就是其宿主結構的位址。
STL中list的使用
stl中list的使用 stl中的list就是一雙向鍊錶,可高效地進行插入刪除元素。現總結一下它的操作。文中所用到兩個list物件c1,c2分別有元素c1 10,20,30 c2 40,50,60 還有乙個list iterator citer用來指向c1或c2元素。list物件的宣告構造 a.li...
Mysql 中List的使用
本文介紹mysql 中list的使用。i list是mysql自己封裝的乙個list物件,並且mysql還封裝了對應的iterator物件 i list iterator 具體的 可以在sql sql list.h中找到。建立乙個i list i list var new i list 新增資料 c...
STL中list的使用
stl中的list就是一雙向鍊錶,可高效地進行插入刪除元素。現總結一下它的操作。文中所用到兩個list物件c1,c2分別有元素c1 10,20,30 c2 40,50,60 還有乙個list iterator citer用來指向c1或c2元素。list物件的宣告構造 a.listc0 空鍊錶 b.l...