最近在看ucore操做系統的實驗指導。裏面提要一個雙向循環鏈表的數據結構,挺有意思的。node
其實這個數據結構自己並不複雜。在普通鏈表的基礎上加一個前向指針,咱們就獲得了雙向鏈表,再把頭尾節點連起來就是雙向循環鏈表了。通常的實現方式以下:redis
typedef struct link_node { ele_type element; link_node *prev, *next; } link_node; ... // 相關的操做比較簡單,這裏就不實現了
可是這樣有必定的侷限性,就是裏面的數據域(element)是固定類型的,若是有不少種成員變量都須要這種鏈表結構,就免不了重複編碼。數據結構
記得以前看redis代碼的時候也有這種相似用C語言實現多態的狀況,那裏面是用一個void *ptr
的指針來指向具體的底層實現。具體實現以下:this
typedef struct link_node_r { void *ptr link_node_r *prev, *next; } link_node_r;
而後,在真正使用的時候把這個類型強轉一下。編碼
而ucore裏面採用了一種不一樣的實現方式。實現以下:atom
struct list_entry { struct list_entry *prev, *next; };
能夠看到,這個鏈表裏面並無數據域。是的你沒有看錯,這個數據結構裏沒有數據域!但是若是沒有數據域的話,這個數據結構有什麼實用價值呢?這裏是把鏈表域放到須要使用鏈表的具體結構了,算是逆向思惟吧。 具體實現以下:操作系統
/* * * struct Page - Page descriptor structures. Each Page describes one * physical page. In kern/mm/pmm.h, you can find lots of useful functions * that convert Page to other data types, such as phyical address. * */ struct Page { atomic_t ref; // page frame's reference counter …… list_entry_t page_link; // free list link };
能夠看到,這裏是須要使用雙向循環鏈表的數據類型應用了list_entry_t
結構,這樣子,鏈表的結構和操做就不須要變了。設計
可是,這裏有一個問題。當咱們查找到鏈表中某一個節點是,怎麼獲取到其宿主結構,也就是說,要怎麼拿到這個對象的其餘部分。正常思惟都是從一個結構裏拿到它的子結構xx->xxx
或者xx.xxx
。這裏咱們要用到一個le2page宏。指針
le2page宏的使用至關簡單:code
// convert list entry to page #define le2page(le, member) \ to_struct((le), struct Page, member)
而相比之下,它的實現用到的to_struct宏和offsetof宏則有一些難懂:
/* Return the offset of 'member' relative to the beginning of a struct type */ #define offsetof(type, member) \ ((size_t)(&((type *)0)->member)) /* * * to_struct - get the struct from a ptr * @ptr: a struct pointer of member * @type: the type of the struct this is embedded in * @member: the name of the member within the struct * */ #define to_struct(ptr, type, member) \ ((type *)((char *)(ptr) - offsetof(type, member)))
這裏採用了一個利用gcc編譯器技術的技巧,即先求得數據結構的成員變量在本宿主數據結構中的偏移量,而後根據成員變量的地址反過來得出屬主數據結構的變量的地址。
咱們首先來看offsetof宏,size_t最終定義與CPU體系結構相關,本實驗都採用Intel X86-32 CPU,顧szie_t等價於 unsigned int。 ((type )0)->member的設計含義是什麼?其實這是爲了求得數據結構的成員變量在本宿主數據結構中的偏移量。爲了達到這個目標,首先將0地址強制"轉換"爲type數據結構(好比struct Page)的指針,再訪問到type數據結構中的member成員(好比page_link)的地址,便是type數據結構中member成員相對於數據結構變量的偏移量。在offsetof宏中,這個member成員的地址(即「&((type )0)->member)」)實際上就是type數據結構中member成員相對於數據結構變量的偏移量。對於給定一個結構,offsetof(type,member)是一個常量,to_struct宏正是利用這個不變的偏移量來求得鏈表數據項的變量地址。接下來再分析一下to_struct宏,能夠發現 to_struct宏中用到的ptr變量是鏈表節點的地址,把它減去offsetof宏所得到的數據結構內偏移量,即就獲得了包含鏈表節點的屬主數據結構的變量的地址。