?? 漫談兼容內(nèi)核之二:關(guān)于kernel-win32的對象管理.txt
字號:
漫談兼容內(nèi)核之二:關(guān)于kernel-win32的對象管理
[align=center][size=5][b]漫談兼容內(nèi)核之二:關(guān)于kernel-win32的對象管理[/b][/size][/align]
[align=center][i]毛德操[/i][/align]
近來屢有網(wǎng)友提到一個旨在將Wineserver移入內(nèi)核的開源項目kernel-win32;有問及其本身,希望能對其代碼作一些分析、講解的,也有問及兼容內(nèi)核與此項目之間關(guān)系的。所以從這篇漫談開始就來談?wù)刱ernel-win32。
首先,兼容內(nèi)核項目應當從所有(能找到的)相關(guān)開源項目吸取營養(yǎng),有時候甚至就采取“拿來主義”,反正都是開源項目,只要遵守有關(guān)的規(guī)定就行。從這個意義上說,我們對于kernel-win32肯定要借鑒,也可能要“拿來”一些。但是這種借鑒和拿來的取舍必須以客觀的分析為基礎(chǔ),必須與我們的終極目標相一致。相信讀者在看完從本文開始的幾篇漫談以后就會明白我為什么把Wine、ReactOS、和NDISwrapper列為兼容內(nèi)核的三個主要源泉,而沒有把kernel-win32也列為主要源泉之一。
從總體上說,kernel-win32把原來由Wine服務(wù)進程提供的某些功能和機制移入了Linux內(nèi)核,具體(就目前所見版本而言)有這么一些:
1. 文件操作。
2. Semaphore操作。
3. Mutex操作。
4. Event操作。
5. 作為同步手段的WaitForMultipleObjects()系統(tǒng)調(diào)用。
所有這些機制和功能的實現(xiàn)都有個共同的基礎(chǔ),那就是各種內(nèi)核“對象(Object)”及其Handle的實現(xiàn)。由于已打開的對象是屬于進程的資源,又由同一進程中的所有線程所共享,所以又跟進程、線程的實現(xiàn)和管理有關(guān)。
此外,kernel-win32也提供了比Wine更高效的RPC機制,用以提高應用進程與Wine服務(wù)進程通信的效率。
但是kernel-win32的實現(xiàn)并不完整,甚至并不構(gòu)成局部的完整性,而且已經(jīng)實現(xiàn)的部分恰恰是相對而言難度并不高的部分,所采取的方案也還值得推敲。
特別地,kernel-win32的目標只在于提高Wine的效率,所以并不涉及設(shè)備驅(qū)動。與兼容內(nèi)核的終極目標相比,二者只是在一小段路程上有“同路”的關(guān)系。以我們的眼光看,kernel-win32無疑是朝正確的方向上在走,但是走的畢竟太近。
Kernel-win32的代碼大體上分成三個部分。第一部分是對于Linux內(nèi)核代碼的補丁,在它的kernel目錄下。第二部分是它本身的代碼,是要作為module動態(tài)安裝到內(nèi)核中去的,具體的代碼文件都在它的根目錄下。第三部分是一些測試/演示程序,這是作為應用軟件在Wine上運行,或者部分地繞過Wine、與Wine并行的,特別是其中還包括一個用于啟動系統(tǒng)調(diào)用的庫程序win32.c,這些程序都在它的test目錄下。從邏輯上說,庫程序win32.c屬于kernel-win32,而測試/演示程序則不是;就好像Linux的內(nèi)核與libc都屬于Linux,而用來測試/演示其功能的程序卻不算一樣。此外,為了幫助調(diào)試,代碼中的strace目錄下還包括了對Linux的一個調(diào)試工具strace的補丁與擴充。這個工具可以用來跟蹤應用軟件所作的Linux系統(tǒng)調(diào)用,實時地顯示所跟蹤的應用軟件進行具體系統(tǒng)調(diào)用時的參數(shù)和內(nèi)核的返回值。這對于調(diào)試當然大有幫助,但在邏輯上也并不是kernel-win32的一部分。
下面我從kernel-win32的代碼入手,對它的方方面面作一簡要的介紹和分析,本篇先說kernel-win32的對象管理,即Object和Handle的實現(xiàn),實際上也必然會牽涉到進程和線程。
Object和Handle的實現(xiàn)
我曾經(jīng)講到,Linux把設(shè)備看成文件,所以“文件”是個廣義的概念;而Windows又進一步把文件看成“對象(Object)”,“對象”是個更廣義的概念。而標志著已打開對象的“句柄(Handle)”,則雖然在物理意義上與“打開文件號”相似(實質(zhì)上都是下標),卻有著許多不同的特性,因而不能把二者混為一談。為了把Linux(內(nèi)核)的文件系統(tǒng)機制“嫁接”到Windows的系統(tǒng)調(diào)用界面,必須為每個運行著Windows應用的進程(下稱“Windows進程”或“Wine進程”)準備下一個地方,用來維持一個類似于“打開文件表”的“打開對象表”。另一方面,Windows的“進程”和“線程”都與它們的Linux對應物有所不同,從而有著不同的數(shù)據(jù)結(jié)構(gòu),因此需要為每個Windows進程提供附加的數(shù)據(jù)結(jié)構(gòu),作為對Linux“進程控制塊”、即task_struct數(shù)據(jù)結(jié)構(gòu)的補充,并且在二者之間建立起某種連接,例如在task_struct結(jié)構(gòu)中增添一個指針等等。
Kernel-win32本質(zhì)上正是這樣做的,我們不妨看一下它對task_struct結(jié)構(gòu)所打的補丁(為便于閱讀,已經(jīng)作了一些整理):
[code]struct task_struct {
. . . . . .
- spinlock_t alloc_lock;
+ rwlock_t alloc_lock;
+ struct list_head ornaments;
};[/code]
本來alloc_lock是這個數(shù)據(jù)結(jié)構(gòu)中的最后一個成分,現(xiàn)在把它的類型從spinlock_t改成了rwlock_t,這是因為原來的加鎖只是針對多處理器結(jié)構(gòu)、防止不同處理器之間互相沖突的,現(xiàn)在則范圍有所擴大。而所增添的成分,則是一個雙鏈隊列頭,稱為“ornaments”。Ornament這個詞原本是“裝飾、飾物”的意思,在這里則引伸成“附件、補充”的意思。
那么準備要鏈入這個隊列的數(shù)據(jù)結(jié)構(gòu)是什么呢?這是task_ornament數(shù)據(jù)結(jié)構(gòu):
[code]struct task_ornament {
atomic_t to_count;
struct list_head to_list;
const struct task_ornament_operations *to_ops;
};[/code]
其中的to_count顯然是個使用計數(shù),計數(shù)為0時表示該數(shù)據(jù)結(jié)構(gòu)不再有“用戶”,從而可以撤銷了。隊列頭to_list顯然就是用來將此數(shù)據(jù)結(jié)構(gòu)掛入ornaments隊列的,所以這兒實質(zhì)性的成分就是指針to_ops,它指向一個task_ornament_operations數(shù)據(jù)結(jié)構(gòu),里面主要是一些函數(shù)指針。目前kernel-win32只定義了一種task_ornament_operations數(shù)據(jù)結(jié)構(gòu),即wineserver_ornament_ops,后面還要講到。
而task_ornament數(shù)據(jù)結(jié)構(gòu),則又可以是另一個數(shù)據(jù)結(jié)構(gòu)WineThread中的一個成分。所以掛入ornaments隊列的(除特殊情況外) 實際上都是WineThread數(shù)據(jù)結(jié)構(gòu),此時task_ornament數(shù)據(jù)結(jié)構(gòu)起著“連接件”的作用。
[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數(shù)據(jù)結(jié)構(gòu)代表著一個Wine線程、即Windows線程。
在Linux內(nèi)核中,task_struct數(shù)據(jù)結(jié)構(gòu)代表著一個進程或線程,是內(nèi)核調(diào)度運行的對象。Wine線程既要受調(diào)度運行,就必須落實到一個task_struct數(shù)據(jù)結(jié)構(gòu)、即Linux線程或進程上。反過來,作為一個受調(diào)度運行單位的task_struct數(shù)據(jù)結(jié)構(gòu)(如果代表著Wine線程的話),也不能代表多個Wine線程,否則這幾個Wine線程就合并成一個調(diào)度單位了。所以這二者之間應該是一一對應的關(guān)系。既然是一一對應的關(guān)系,就應該使用指針、而不是隊列來建立互相的連系。而既然采用了隊列,隊列中又只能有一個Wine線程,那么隊列中別的成員(如果有的話),就必定是別的什么東西了。從邏輯上說,同一個隊列中的諸多成員之間有著平等的關(guān)系,可是有什么東西可以和線程處于平等的地位呢?所以這是值得推敲的,后面我還要談這個問題。
WineThread結(jié)構(gòu)中的幾個成分需要加以說明:
指針wt_obj指向一個Object指針。在Windows中線程、進程都是“對象”,都要有一個Object數(shù)據(jù)結(jié)構(gòu)作為代表。對于Object數(shù)據(jù)結(jié)構(gòu)等一下再作說明。
指針wt_task指向當前進程(線程)的task_struct數(shù)據(jù)結(jié)構(gòu),這樣就可以在一個Wine線程與其所落實的Linux線程之間建立起雙向的連系(另一個方向就是順著Linux線程的ornaments隊列)。另一個指針wt_process指向一個WineProcess數(shù)據(jù)結(jié)構(gòu)。顯然,WineProcess數(shù)據(jù)結(jié)構(gòu)代表著Widows進程。
在Linux內(nèi)核中,進程并沒有獨立于線程的數(shù)據(jù)結(jié)構(gòu),都由task_struct作為代表。一個進程初創(chuàng)(通過execve()等調(diào)用與其父進程決裂)時就成為進程,同時也可以說是該進程中的第一個線程。以后,該進程通過fork()等調(diào)用創(chuàng)建子進程。子進程在創(chuàng)建之初都是與父進程共享空間的,因而都是線程,后來才通過execve()等調(diào)用另立門戶,有了自己的空間而成為進程。然而Windows不是這樣。在Windows中。一個進程與該進程的第一個線程(以及別的線程)是兩個不同的概念,有不同的數(shù)據(jù)結(jié)構(gòu)。大體上說,進程代表著資源,特別是代表著一片用戶空間;而線程則代表著上下文。打個比方,進程就像是舞臺和劇本,而線程是演員及其表演的過程。在Windows內(nèi)核中,這兩方面的信息分置于不同的數(shù)據(jù)結(jié)構(gòu)中,而在Linux內(nèi)核中則全都存放在task_struct結(jié)構(gòu)中。但是Windows進程和線程的有些信息是task_struct結(jié)構(gòu)中所沒有或者不同的,這也正是需要在task_struct結(jié)構(gòu)之外加以“裝飾”、補充的原因。
WineProcess數(shù)據(jù)結(jié)構(gòu)的定義如下:
[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結(jié)構(gòu)中的wt_list相對應,用來構(gòu)成一個Windows進程與其所含的所有Windows線程之間的雙鏈隊列。如前所述,Windows進程在概念上并不落實到某個具體的Linux進程或線程,所以這個數(shù)據(jù)結(jié)構(gòu)中并沒有指向task_struct結(jié)構(gòu)的指針。不過實質(zhì)上當然還是有連系的,因為在Linux中一個“進程”和它的“第一個線程”是一回事。這里的wp_pid既然說是Linux的“task ID”(就是Linux的pid),實際上還是一樣,還不如改成task_struct結(jié)構(gòu)指針更好。
順便提一下,Windows進程與Linux進程在優(yōu)先級設(shè)置方面有很大的不同,而Linux內(nèi)核是根據(jù)task_struct數(shù)據(jù)結(jié)構(gòu)中記載的優(yōu)先級進行調(diào)度的,這里面有個如何換算的問題。WineProcess數(shù)據(jù)結(jié)構(gòu)中沒有關(guān)于進程優(yōu)先級的記載,顯然kernel-win32的作者還沒有考慮這個問題。
在Windows中進程也是“對象”,所以這里也有個Object結(jié)構(gòu)指針wp_obj。這與WineThread結(jié)構(gòu)中的指針wt_obj是一樣的,只不過兩個對象的類型不同,一個代表著進程,一個代表著線程。
指針數(shù)組wp_handles[ ]才是這個數(shù)據(jù)結(jié)構(gòu)的實質(zhì)所在,這就是一個Windows進程的“打開對象表”。數(shù)組中的每個有效(非0)指針都指向一個Object結(jié)構(gòu);對象的類型不同,相應Object結(jié)構(gòu)所代表的目標也就不同。例如有的代表著文件,有的代表著“事件”,有的代表著進程,等等。而具體指針在數(shù)組中的下標(嚴格地說是經(jīng)過換算的下標,見后所述),則就是打開該對象后的Handle。在代碼中,這個數(shù)組的大小定義為0,這是因為其大小取決于為WineProcess數(shù)據(jù)結(jié)構(gòu)分配的存儲空間的大小。目前,kernel-win32總是為WineProcess數(shù)據(jù)結(jié)構(gòu)分配一個4KB的物理頁面,扣除這個數(shù)據(jù)結(jié)構(gòu)的頭部以后就都用于這個數(shù)組,其大小的計算如下所示:
[code]#define MAXHANDLES ((PAGE_SIZE-sizeof(struct WineProcess))/sizeof(struct Object*))[/code]
所以“打開對象表”的大小大約是1020。這與Windows相比當然差距很大(在理論上,Windows打開對象表的大小幾乎是無限的),但是實際上也夠了。
前面我一直說Handle就是下標,其實這只是就其邏輯意義而言,嚴格地說是經(jīng)過換算的下標。這是因為:首先,0不是一個合法的handle數(shù)值。另一方面,Handle的數(shù)值都是4的倍數(shù),所反映的是以字節(jié)為單位的位移。如果index是真正意義上的下標,那么handle的數(shù)值就是(index+1)* sizeof(Object*)。
總之,每個進程都有一個打開對象表,為該進程所含的諸多線程所共享,表中的每個有效指針都指向一個Object數(shù)據(jù)結(jié)構(gòu)。內(nèi)核中的對象就好像磁盤上的文件一樣,都有個從創(chuàng)建到打開、到關(guān)閉、最后被刪除的“生命周期”。每個對象都由一個Object數(shù)據(jù)結(jié)構(gòu)作為代表,其定義為:
[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]
對象的類型是以其數(shù)據(jù)結(jié)構(gòu)中的o_class為標志的,這個指針指向哪一個ObjectClass數(shù)據(jù)結(jié)構(gòu),這個對象就是什么類型。此外,每個對象都可以有個對象名,就好像每個文件都有個文件名一樣,o_name就是用來保持對象名的數(shù)據(jù)結(jié)構(gòu)。
顯然,內(nèi)核中對象的數(shù)量可以很大,這里有個如何尋找某個特定對象的問題。所以首先要按對象的類型劃分,然后為每個類別都安排若干個以對象名的hash值劃分的隊列。具體對象的Object數(shù)據(jù)結(jié)構(gòu)就按其對象名的hash值鏈入所屬類別的某個隊列中,結(jié)構(gòu)中的隊列頭o_objlist就是用于這個目的。
讀者不難看出,Object數(shù)據(jù)結(jié)構(gòu)中各個成分所反映的基本上都是作為某類對象的共性,而并沒有反映出具體對象的個性。所以這個結(jié)構(gòu)中有個無類型的指針o_private,用來指向一個描述具體對象的數(shù)據(jù)結(jié)構(gòu)。當對象的類型為線程時,這就是個WineThread數(shù)據(jù)結(jié)構(gòu);當對象的類型為文件時,這就是個WineFile數(shù)據(jù)結(jié)構(gòu);如此等等。
如上所述,Object數(shù)據(jù)結(jié)構(gòu)中的指針o_class標志著對象的類型。這是個指向某個ObjectClass結(jié)構(gòu)的指針,每個ObjectClass結(jié)構(gòu)代表著一種對象類型,其定義如下。
[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]
結(jié)構(gòu)中的第一個隊列頭oc_next用來構(gòu)成一個對象類型、即ObjectClass數(shù)據(jù)結(jié)構(gòu)的隊列。而隊列頭數(shù)組oc_nobjs[OBJCLASSNOBJSSIZE],則用來構(gòu)成該對象類的hash隊列數(shù)組,具體的對象根據(jù)其對象名的hash值決定鏈入其中的哪一個隊列。數(shù)組的大小OBJCLASSNOBJSSIZE定義為16。對象也可以是無名的,無名的對象都鏈入所屬類別的無名隊列、即oc_aobjs隊列。
ObjectClass數(shù)據(jù)結(jié)構(gòu)中最具實質(zhì)性的成分是一組函數(shù)指針,特別是其中的“構(gòu)造”函數(shù)指針constructor,因為它決定了如何構(gòu)造出一個具體類型的對象。目前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數(shù)據(jù)結(jié)構(gòu)。
我們不妨以比較簡單的“信號量(semaphore)”類型為例來說明對象的創(chuàng)建。
函數(shù)CreateSemaphoreA()是kernel_win32對同名系統(tǒng)調(diào)用在內(nèi)核中的實現(xiàn),其目的是為當前進程創(chuàng)建并打開一個(內(nèi)核中的)帶有對象名的信號量。我們先跳過系統(tǒng)調(diào)用如何進入內(nèi)核這一步,從這個函數(shù)開始看有關(guān)的代碼。
[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]
調(diào)用參數(shù)filp指向當前線程的WineThread數(shù)據(jù)結(jié)構(gòu),這是由kernel_win32所實現(xiàn)的系統(tǒng)調(diào)用機制所提供的。另一個參數(shù)args,則是指向一個WiocCreateSemaphoreA數(shù)據(jù)結(jié)構(gòu)的指針。Kernel_win32所實現(xiàn)的系統(tǒng)調(diào)用機制把Windows系統(tǒng)調(diào)用的參數(shù)都組裝在一個數(shù)據(jù)結(jié)構(gòu)中,再把這個結(jié)構(gòu)的起始地址作為參數(shù)傳給內(nèi)核。為此,Kernel_win32為其所實現(xiàn)的每個Windows系統(tǒng)調(diào)用都定義了一個數(shù)據(jù)結(jié)構(gòu),WiocCreateSemaphoreA就是為系統(tǒng)調(diào)用CreateSemaphoreA()的參數(shù)而定義的數(shù)據(jù)結(jié)構(gòu)。
CreateSemaphoreA()的主體就是對函數(shù)CreateObject()的調(diào)用。由于要創(chuàng)建的對象是信號量,就把此種對象類型的數(shù)據(jù)結(jié)構(gòu)semaphore_objclass的起始地址也作為參數(shù)傳了下去。參數(shù)&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 + -