標籤:
1.看原始碼必須搞懂Android的資料結構。在init原始碼中雙向鏈表listnode使用非常多,它僅僅有prev和next兩個指標,沒有不論什麼資料成員。這個和linux核心的list_head如出一轍,由此可見安卓深受linux核心的影響的。本來來分析一下這個listnode資料結構。
這裡須要考慮的一個問題是,鏈表操作都是通過listnode進行的,但是那隻是是個串連件。假設我們手上有個宿主結構,那當然知道了它的某個listnode在哪裡,從而以此為參數調用list_add和list_del函數。但是,反過來。當我們順著鏈表取得當中一項的listnode結構時,又如何找到其宿主結構呢?在listnode結構中並沒有指向其宿主結構的指標啊。畢竟。我們我真正關心的是宿主結構。而不是串連件。對於這個問題,我們舉例核心中的list_head的範例來解決。核心的page結構體含有list_head成員,問題是:知道list_head的地址。如何擷取page宿主的地址?以下是取自mm/page_alloc.c中的一行代碼:
page = memlist_entry(curr, struct page, list);
這裡的memlist_entry將一個list_head指標curr換算成其宿主結構的起始地址,也就是取得指向其宿主page結構的指標。讀者可能會對memlist_entry()的實現感到困惑。
#define memlist_entry list_entry
而list_entry定義則在include/linux/list.h中
135 /**136 * list_entry getthe struct for this entry137 * @ptr: the &struct list_head pointer.138 * @type: the type of the struct this is embedded in.139 * @member: the name of the list_struct within the struct.140 */141 #define list_entry(ptr, type, member) 142 ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
這樣我們應該就明確了。curr是一個page結構內部成分list的地址,而我們所須要的卻是那個page結構本身的地址,所以要從curr減去一個位移量,即成分list在page內部的位移量。
那麼這個位移量怎麼求?&((struct page*)0)->list就表示當結構page正好在地址0上時其成分list的地址,這就是所求的位移量。
2.測試代碼
#include<stdio.h>#include<stddef.h>typedef struct _listnode{ struct _listnode *prev; struct _listnode *next;}listnode;#define node_to_item(node,container,member) (container*)(((char*)(node))-offsetof(container,member))//向list雙向鏈表尾部加入node節點,list始終指向雙向鏈表的頭部(這個頭部僅僅含有prev/next)void list_add_tail(listnode *list,listnode *node){ list->prev->next=node; node->prev=list->prev; node->next=list; list->prev=node;}//定義一個測試的宿主結構typedef struct _node{ int data; listnode list;}node;int main(){ node n1,n2,n3,*n; listnode list,*p; n1.data=1; n2.data=2; n3.data=3; list.prev=&list; list.next=&list; list_add_tail(&list,&n1.list); list_add_tail(&list,&n2.list); list_add_tail(&list,&n3.list); for(p=list.next;p!=&list;p=p->next) { n=node_to_item(p,node,list); printf("%d\n",n->data); } return 0;}
Android中的雙向鏈表