?? 漫談兼容內核之二:關于kernel-win32的對象管理.txt
字號:
漫談兼容內核之二:關于kernel-win32的對象管理
[align=center][size=5][b]漫談兼容內核之二:關于kernel-win32的對象管理[/b][/size][/align]
[align=center][i]毛德操[/i][/align]
近來屢有網友提到一個旨在將Wineserver移入內核的開源項目kernel-win32;有問及其本身,希望能對其代碼作一些分析、講解的,也有問及兼容內核與此項目之間關系的。所以從這篇漫談開始就來談談kernel-win32。
首先,兼容內核項目應當從所有(能找到的)相關開源項目吸取營養,有時候甚至就采取“拿來主義”,反正都是開源項目,只要遵守有關的規定就行。從這個意義上說,我們對于kernel-win32肯定要借鑒,也可能要“拿來”一些。但是這種借鑒和拿來的取舍必須以客觀的分析為基礎,必須與我們的終極目標相一致。相信讀者在看完從本文開始的幾篇漫談以后就會明白我為什么把Wine、ReactOS、和NDISwrapper列為兼容內核的三個主要源泉,而沒有把kernel-win32也列為主要源泉之一。
從總體上說,kernel-win32把原來由Wine服務進程提供的某些功能和機制移入了Linux內核,具體(就目前所見版本而言)有這么一些:
1. 文件操作。
2. Semaphore操作。
3. Mutex操作。
4. Event操作。
5. 作為同步手段的WaitForMultipleObjects()系統調用。
所有這些機制和功能的實現都有個共同的基礎,那就是各種內核“對象(Object)”及其Handle的實現。由于已打開的對象是屬于進程的資源,又由同一進程中的所有線程所共享,所以又跟進程、線程的實現和管理有關。
此外,kernel-win32也提供了比Wine更高效的RPC機制,用以提高應用進程與Wine服務進程通信的效率。
但是kernel-win32的實現并不完整,甚至并不構成局部的完整性,而且已經實現的部分恰恰是相對而言難度并不高的部分,所采取的方案也還值得推敲。
特別地,kernel-win32的目標只在于提高Wine的效率,所以并不涉及設備驅動。與兼容內核的終極目標相比,二者只是在一小段路程上有“同路”的關系。以我們的眼光看,kernel-win32無疑是朝正確的方向上在走,但是走的畢竟太近。
Kernel-win32的代碼大體上分成三個部分。第一部分是對于Linux內核代碼的補丁,在它的kernel目錄下。第二部分是它本身的代碼,是要作為module動態安裝到內核中去的,具體的代碼文件都在它的根目錄下。第三部分是一些測試/演示程序,這是作為應用軟件在Wine上運行,或者部分地繞過Wine、與Wine并行的,特別是其中還包括一個用于啟動系統調用的庫程序win32.c,這些程序都在它的test目錄下。從邏輯上說,庫程序win32.c屬于kernel-win32,而測試/演示程序則不是;就好像Linux的內核與libc都屬于Linux,而用來測試/演示其功能的程序卻不算一樣。此外,為了幫助調試,代碼中的strace目錄下還包括了對Linux的一個調試工具strace的補丁與擴充。這個工具可以用來跟蹤應用軟件所作的Linux系統調用,實時地顯示所跟蹤的應用軟件進行具體系統調用時的參數和內核的返回值。這對于調試當然大有幫助,但在邏輯上也并不是kernel-win32的一部分。
下面我從kernel-win32的代碼入手,對它的方方面面作一簡要的介紹和分析,本篇先說kernel-win32的對象管理,即Object和Handle的實現,實際上也必然會牽涉到進程和線程。
Object和Handle的實現
我曾經講到,Linux把設備看成文件,所以“文件”是個廣義的概念;而Windows又進一步把文件看成“對象(Object)”,“對象”是個更廣義的概念。而標志著已打開對象的“句柄(Handle)”,則雖然在物理意義上與“打開文件號”相似(實質上都是下標),卻有著許多不同的特性,因而不能把二者混為一談。為了把Linux(內核)的文件系統機制“嫁接”到Windows的系統調用界面,必須為每個運行著Windows應用的進程(下稱“Windows進程”或“Wine進程”)準備下一個地方,用來維持一個類似于“打開文件表”的“打開對象表”。另一方面,Windows的“進程”和“線程”都與它們的Linux對應物有所不同,從而有著不同的數據結構,因此需要為每個Windows進程提供附加的數據結構,作為對Linux“進程控制塊”、即task_struct數據結構的補充,并且在二者之間建立起某種連接,例如在task_struct結構中增添一個指針等等。
Kernel-win32本質上正是這樣做的,我們不妨看一下它對task_struct結構所打的補丁(為便于閱讀,已經作了一些整理):
[code]struct task_struct {
. . . . . .
- spinlock_t alloc_lock;
+ rwlock_t alloc_lock;
+ struct list_head ornaments;
};[/code]
本來alloc_lock是這個數據結構中的最后一個成分,現在把它的類型從spinlock_t改成了rwlock_t,這是因為原來的加鎖只是針對多處理器結構、防止不同處理器之間互相沖突的,現在則范圍有所擴大。而所增添的成分,則是一個雙鏈隊列頭,稱為“ornaments”。Ornament這個詞原本是“裝飾、飾物”的意思,在這里則引伸成“附件、補充”的意思。
那么準備要鏈入這個隊列的數據結構是什么呢?這是task_ornament數據結構:
[code]struct task_ornament {
atomic_t to_count;
struct list_head to_list;
const struct task_ornament_operations *to_ops;
};[/code]
其中的to_count顯然是個使用計數,計數為0時表示該數據結構不再有“用戶”,從而可以撤銷了。隊列頭to_list顯然就是用來將此數據結構掛入ornaments隊列的,所以這兒實質性的成分就是指針to_ops,它指向一個task_ornament_operations數據結構,里面主要是一些函數指針。目前kernel-win32只定義了一種task_ornament_operations數據結構,即wineserver_ornament_ops,后面還要講到。
而task_ornament數據結構,則又可以是另一個數據結構WineThread中的一個成分。所以掛入ornaments隊列的(除特殊情況外) 實際上都是WineThread數據結構,此時task_ornament數據結構起著“連接件”的作用。
[code]struct WineThread {
#ifdef WINE_THREAD_MAGIC
unsigned wt_magic; /* magic number */
#endif
struct task_ornament wt_ornament; /* Linux task attachment */
struct task_struct *wt_task; /* Linux task */
Object *wt_obj; /* thread object */
struct WineProcess *wt_process; /* Wine process record */
struct list_head wt_list; /* process's thread list */
enum WineThreadState wt_state; /* thread state */
unsigned wt_exit_status; /* thread exit status */
pid_t wt_tid; /* thread ID */
};[/code]
當然,每個WineThread數據結構代表著一個Wine線程、即Windows線程。
在Linux內核中,task_struct數據結構代表著一個進程或線程,是內核調度運行的對象。Wine線程既要受調度運行,就必須落實到一個task_struct數據結構、即Linux線程或進程上。反過來,作為一個受調度運行單位的task_struct數據結構(如果代表著Wine線程的話),也不能代表多個Wine線程,否則這幾個Wine線程就合并成一個調度單位了。所以這二者之間應該是一一對應的關系。既然是一一對應的關系,就應該使用指針、而不是隊列來建立互相的連系。而既然采用了隊列,隊列中又只能有一個Wine線程,那么隊列中別的成員(如果有的話),就必定是別的什么東西了。從邏輯上說,同一個隊列中的諸多成員之間有著平等的關系,可是有什么東西可以和線程處于平等的地位呢?所以這是值得推敲的,后面我還要談這個問題。
WineThread結構中的幾個成分需要加以說明:
指針wt_obj指向一個Object指針。在Windows中線程、進程都是“對象”,都要有一個Object數據結構作為代表。對于Object數據結構等一下再作說明。
指針wt_task指向當前進程(線程)的task_struct數據結構,這樣就可以在一個Wine線程與其所落實的Linux線程之間建立起雙向的連系(另一個方向就是順著Linux線程的ornaments隊列)。另一個指針wt_process指向一個WineProcess數據結構。顯然,WineProcess數據結構代表著Widows進程。
在Linux內核中,進程并沒有獨立于線程的數據結構,都由task_struct作為代表。一個進程初創(通過execve()等調用與其父進程決裂)時就成為進程,同時也可以說是該進程中的第一個線程。以后,該進程通過fork()等調用創建子進程。子進程在創建之初都是與父進程共享空間的,因而都是線程,后來才通過execve()等調用另立門戶,有了自己的空間而成為進程。然而Windows不是這樣。在Windows中。一個進程與該進程的第一個線程(以及別的線程)是兩個不同的概念,有不同的數據結構。大體上說,進程代表著資源,特別是代表著一片用戶空間;而線程則代表著上下文。打個比方,進程就像是舞臺和劇本,而線程是演員及其表演的過程。在Windows內核中,這兩方面的信息分置于不同的數據結構中,而在Linux內核中則全都存放在task_struct結構中。但是Windows進程和線程的有些信息是task_struct結構中所沒有或者不同的,這也正是需要在task_struct結構之外加以“裝飾”、補充的原因。
WineProcess數據結構的定義如下:
[code]struct WineProcess {
int wp_magic; /* magic number */
struct nls_table *wp_nls; /* unicode-ascii translation */
pid_t wp_pid; /* Linux task ID */
enum WineProcessState wp_state; /* process state */
struct list_head wp_threads; /* thread list */
rwlock_t wp_lock;
struct Object *wp_obj; /* process object */
struct Object *wp_handles[0]; /* handle map */
};[/code]
其中的隊列頭wp_threads與上面WineThread結構中的wt_list相對應,用來構成一個Windows進程與其所含的所有Windows線程之間的雙鏈隊列。如前所述,Windows進程在概念上并不落實到某個具體的Linux進程或線程,所以這個數據結構中并沒有指向task_struct結構的指針。不過實質上當然還是有連系的,因為在Linux中一個“進程”和它的“第一個線程”是一回事。這里的wp_pid既然說是Linux的“task ID”(就是Linux的pid),實際上還是一樣,還不如改成task_struct結構指針更好。
順便提一下,Windows進程與Linux進程在優先級設置方面有很大的不同,而Linux內核是根據task_struct數據結構中記載的優先級進行調度的,這里面有個如何換算的問題。WineProcess數據結構中沒有關于進程優先級的記載,顯然kernel-win32的作者還沒有考慮這個問題。
在Windows中進程也是“對象”,所以這里也有個Object結構指針wp_obj。這與WineThread結構中的指針wt_obj是一樣的,只不過兩個對象的類型不同,一個代表著進程,一個代表著線程。
指針數組wp_handles[ ]才是這個數據結構的實質所在,這就是一個Windows進程的“打開對象表”。數組中的每個有效(非0)指針都指向一個Object結構;對象的類型不同,相應Object結構所代表的目標也就不同。例如有的代表著文件,有的代表著“事件”,有的代表著進程,等等。而具體指針在數組中的下標(嚴格地說是經過換算的下標,見后所述),則就是打開該對象后的Handle。在代碼中,這個數組的大小定義為0,這是因為其大小取決于為WineProcess數據結構分配的存儲空間的大小。目前,kernel-win32總是為WineProcess數據結構分配一個4KB的物理頁面,扣除這個數據結構的頭部以后就都用于這個數組,其大小的計算如下所示:
[code]#define MAXHANDLES ((PAGE_SIZE-sizeof(struct WineProcess))/sizeof(struct Object*))[/code]
所以“打開對象表”的大小大約是1020。這與Windows相比當然差距很大(在理論上,Windows打開對象表的大小幾乎是無限的),但是實際上也夠了。
前面我一直說Handle就是下標,其實這只是就其邏輯意義而言,嚴格地說是經過換算的下標。這是因為:首先,0不是一個合法的handle數值。另一方面,Handle的數值都是4的倍數,所反映的是以字節為單位的位移。如果index是真正意義上的下標,那么handle的數值就是(index+1)* sizeof(Object*)。
總之,每個進程都有一個打開對象表,為該進程所含的諸多線程所共享,表中的每個有效指針都指向一個Object數據結構。內核中的對象就好像磁盤上的文件一樣,都有個從創建到打開、到關閉、最后被刪除的“生命周期”。每個對象都由一個Object數據結構作為代表,其定義為:
[code]/*
* object definition
* - object namespace is indexed by name and class
*/
typedef struct Object {
struct list_head o_objlist; /* obj list (must be 1st) */
#ifdef OBJECT_MAGIC
int o_magic; /* magic number (debugging) */
#endif
atomic_t o_count; /* usage count */
wait_queue_head_t o_wait; /* waiting process list */
struct ObjectClass *o_class; /* object class */
struct oname o_name; /* name of object */
void *o_private; /* type-specific data */
} Object;[/code]
對象的類型是以其數據結構中的o_class為標志的,這個指針指向哪一個ObjectClass數據結構,這個對象就是什么類型。此外,每個對象都可以有個對象名,就好像每個文件都有個文件名一樣,o_name就是用來保持對象名的數據結構。
顯然,內核中對象的數量可以很大,這里有個如何尋找某個特定對象的問題。所以首先要按對象的類型劃分,然后為每個類別都安排若干個以對象名的hash值劃分的隊列。具體對象的Object數據結構就按其對象名的hash值鏈入所屬類別的某個隊列中,結構中的隊列頭o_objlist就是用于這個目的。
讀者不難看出,Object數據結構中各個成分所反映的基本上都是作為某類對象的共性,而并沒有反映出具體對象的個性。所以這個結構中有個無類型的指針o_private,用來指向一個描述具體對象的數據結構。當對象的類型為線程時,這就是個WineThread數據結構;當對象的類型為文件時,這就是個WineFile數據結構;如此等等。
如上所述,Object數據結構中的指針o_class標志著對象的類型。這是個指向某個ObjectClass結構的指針,每個ObjectClass結構代表著一種對象類型,其定義如下。
[code]struct ObjectClass {
struct list_head oc_next;
const char oc_type[6]; /* type name (5 chars + NUL) */
int oc_flags;
#define OCF_DONT_NAME_ANON 0x00000001 /* don't name anonymous objects */
int (*constructor)(Object *, void *);
int (*reconstructor)(Object *, void *);
void (*destructor)(Object *);
int (*describe)(Object *, struct wineserver_read_buf *);
int (*poll)(struct wait_table_entry *, struct WineThread *);
void (*detach)(Object *, struct WineProcess *);
/* lock governing access to object lists */
rwlock_t oc_lock;
/* named object hash */
struct list_head oc_nobjs[OBJCLASSNOBJSSIZE];
/* anonymous object list */
struct list_head oc_aobjs;
};[/code]
結構中的第一個隊列頭oc_next用來構成一個對象類型、即ObjectClass數據結構的隊列。而隊列頭數組oc_nobjs[OBJCLASSNOBJSSIZE],則用來構成該對象類的hash隊列數組,具體的對象根據其對象名的hash值決定鏈入其中的哪一個隊列。數組的大小OBJCLASSNOBJSSIZE定義為16。對象也可以是無名的,無名的對象都鏈入所屬類別的無名隊列、即oc_aobjs隊列。
ObjectClass數據結構中最具實質性的成分是一組函數指針,特別是其中的“構造”函數指針constructor,因為它決定了如何構造出一個具體類型的對象。目前keenel-win32定義了event_objclass、file_objclass、mutex_objclass、semaphore_objclass、process_objclass、thread_objclass、rpc_client_objclass、rpc_server_objclass、rpc_service_objclass、section_objclass等11種對象類型的ObjectClass數據結構。
我們不妨以比較簡單的“信號量(semaphore)”類型為例來說明對象的創建。
函數CreateSemaphoreA()是kernel_win32對同名系統調用在內核中的實現,其目的是為當前進程創建并打開一個(內核中的)帶有對象名的信號量。我們先跳過系統調用如何進入內核這一步,從這個函數開始看有關的代碼。
[code]int CreateSemaphoreA(struct WineThread *filp, struct WiocCreateSemaphoreA *args)
{
HANDLE hSemaphore;
Object *obj;
obj = CreateObject(filp,&semaphore_objclass,args->lpName,args, &hSemaphore);
. . . . . .
return (int) hSemaphore;
} /* end CreateSemaphoreA() */[/code]
調用參數filp指向當前線程的WineThread數據結構,這是由kernel_win32所實現的系統調用機制所提供的。另一個參數args,則是指向一個WiocCreateSemaphoreA數據結構的指針。Kernel_win32所實現的系統調用機制把Windows系統調用的參數都組裝在一個數據結構中,再把這個結構的起始地址作為參數傳給內核。為此,Kernel_win32為其所實現的每個Windows系統調用都定義了一個數據結構,WiocCreateSemaphoreA就是為系統調用CreateSemaphoreA()的參數而定義的數據結構。
CreateSemaphoreA()的主體就是對函數CreateObject()的調用。由于要創建的對象是信號量,就把此種對象類型的數據結構semaphore_objclass的起始地址也作為參數傳了下去。參數&hSemaphore則是用來返回Handle的。
[code][CreateSemaphoreA() > CreateObject()]
Object *CreateObject(struct WineThread *thread, struct ObjectClass *clss,const char *name, void *data, HANDLE *hObject)
{
struct WineProcess *process;
struct oname oname;
Object *obj, **ppobj, **epobj;
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -