在下面的比較中,我壹般先介紹下WINDOWS的,然後再介紹LINUX的。
1、觀念:商業 VS 開源
WINDOWS是個商業軟件,它的源碼是保密的. 當然,其他非MS的人也還是有機會看到源碼的. 如果妳和MS 簽訂壹個NDA(NON DISCLOSURE AGREEMENT),那麽妳也有可能拿到WINDOWS代碼.
不過對於廣大窮學生,以及連VISUAL STUDIO都在用盜版的摳門公司來說,和MS簽個NDA幾乎是不可想象的. 所以在WINDOWS世界,想了解WINDOW 內核的具體信息變得很難. 只能靠DDK(DRIVER DEVELOPMENT KIT) 和WINDBG(內核調試工具)泄漏出來的壹些. 然後就是REVERSE ENGINEERING (逆向工程,可以簡單的理解為反匯編,實際上更復雜壹些).
這也造成了 <WINDOWS INTERNALS> 壹書超級火爆的原因. 因為它是微軟授權的,而且公布了很多內部細節. 另外壹本講內核的書是<UNDOCUMENTED WINDOWS 2K SECRETS>,雖然老了點,但是很多內幕。關於WINDOWS, undocumented 和secrets 這2個字絕對是可以類比“超級美女”的字眼。因為這些東西平時是看不到的.
與此對應,在LINUX世界,常見的壹個詞是RTFS。也就是READ THE FXXXXXX SOURCE CODE (這句話據說最早出於linus torvalds, 也就是LINUX之父)。意思也就是說“去讀該死的代碼”。言外之意,我把代碼都給妳妳了,妳還想要啥啊?這就好像壹個男人對他GF / LP / LD說,我把全部的銀行帳戶密碼都給妳了,妳還想要啥啊?
其實他不知道(或者認識不到)女人還需要妳的時間,精力來陪她。就好像LINUX 程序員意識不到文檔也是很重要的。當然,LINUX程序員應該也是知道文檔的重要的,不過壹個是維護成本太高,另外是LINUX 內核變化太快。所以LINUX 的文檔總感覺比MSDN要差點。
話說當年WIN 2K的源碼泄漏出來了壹些,我也迫不及待的下載了壹份.雖然至今也沒看過,但是拿到WINDOWS 源碼的感覺,絕對不比娶了壹個絕世美女差. (當然,真要娶老婆還是看內在).
相比之下, LINUX 是開源的,代碼隨時可見. 這對剛從WINDOWS世界轉過來的我是十分震撼的. 雖然我壹直都知道這個事實, 但是當妳發現了以前需要用盡各種方法,采用各種手段才可以得到只言片語的信息現在完全呈獻在妳面前的時候,妳才能真正體會開源確實是壹件偉大的工程.
看了LINUX源碼之後,我終於發現,原來內核裏大部分也是C語言(而不是以前想象的匯編). 同時內核似乎也就那樣,不像之前想象的那麽神秘. 原來編譯內核也就是比編譯個普通程序稍微麻煩點,用的時間長點. 原來編譯內核用普通的C編譯器就可以. 原來內核也是壹個普通的可執行文件.(PS: 我懷疑MS也是用VS來編譯WINDOWS的. 同時我也知道WINDOWS內核也是壹個可執行文件.) 原來更換內核是如此的簡單.
終於,內核可以被我隨便改了. 哇哈哈哈!
言規正傳,我覺得商業也還是有好處的。比如兼容性好,我以前用WDM寫壹個驅動,最多改下編譯選項就可以在WIN 98, WIN 2K, WIN XP下運行。十分方便。而如果換成LINUX,那麽妳只好祈禱不同的內核版本之間沒改那些妳用到的頭文件,函數接口。否則就要改代碼了。
同時,開源的好處是適合學習,十分靈活。我覺得LINUX十分適合學校,學生。因為開源,當妳發現不明白的地方的時候,可以直接去看源碼(還記得RTFS? )。看不懂還可以到論壇上問。而對於WINDOWS,妳想了解它的內部機制就只好GOOGLE,然後祈禱了。比較好的壹個資源是MSDN下面的壹個雜誌,其中有壹個主題叫UNDER THE HOOD, 或者搜搜 BUGSLAYER 也可以。這2個專題的作者Matt Pietrek和John Robbins都是大牛級的人物。
順便說下UNDER THE HOOD 這個名字本身。以前壹直不太理解,因為查字典的話,HOOD 的意思也就是個蓋子。那麽蓋子下面有啥呢?為啥要看蓋子下面呢?
來到美國之後,我漸漸明白了。HOOD 在這裏應該理解為汽車的引擎蓋。在美國,汽車是很普遍的。如果妳開車,但是從來沒打開過引擎蓋,那麽說明妳只會用,而不了解汽車內部。那麽如果妳打開蓋子看看呢?就可以看到很多內部細節,比如發動機啥的了。
在美國這個汽車王國,很多軟件術語和汽車有關,因為人們日常生活中對汽車也很了解。比如“引擎”這個詞,以前玩3D遊戲的時候,常會看到介紹說,本遊戲采用了最新的3D引擎。啥意思呢?就是遊戲最核心的部分(汽車引擎)已經升級了。不是只把外面的人物形象改了下而已。
另外,開源軟件也經常用汽車來類比。開源意外著妳買了車(軟件)後,可以隨便拿到壹個修理廠去修。也就是什麽人都可以改,只要他懂。而COPY RIGHT 軟件呢,就是妳買了車,但是引擎蓋子是鎖著的,壞了只能去生產廠家修,其他人修不了。如果萬壹生產廠家不想修或者不會修呢?那妳就只能認命了。
扯得有點遠了,打住。
1.1、發布:2進制 VS 源碼
這裏主要討論下WINDOWS和LINUX在發布程序采用的不同的形式和觀念,這些和前面的商業還是開源的基本觀念是聯系在壹起的。
在WINDOWS 世界,安裝程序幾乎全部都是以二進制形式發布的。也就是說,用戶下載了壹個程序,然後雙擊,壹路NEXT,NEXT,NEXT就可以了。這個方法很適合初學者。在LINUX世界也有類似的機制,比如YUM, APT-GET 等。不過YUM和APT-GET都是比較晚才出現的,在那之前,在LINUX世界安裝程序要更麻煩些。
有的時候,LINUX的YUM, APT-GET還不夠用。比如有的人寫的壹個小軟件,沒有放到這些大的公***的庫裏面。這時,妳就會發現他們壹般提供壹個或者壹堆源文件,然後需要使用者自己下載,“編譯”,安裝。這也就是LINUX世界常見的源代碼發布的形式。
壹開始的時候,十分不習慣LINUX的這種發布形式。用慣了WINDOWS的雙擊安裝,總覺得LINUX的安裝很麻煩,又要自己./CONFIGURE, MAKE, MAKE INSTALL. 萬壹這個軟件又依賴於其他的庫,那麽又要自己去找那些庫,萬壹那些庫又依賴其他的庫...... 另外,各種庫的版本也是壹個問題,萬壹不兼容,那麽又要找壹個兼容的。
為什麽LINUX世界這麽多源代碼發布呢?為什麽WINDOWS世界流行2進制文件發布,而不是源代碼呢?關於後者,很好解釋,因為WINDOWS那邊很多源代碼都是商業秘密,是不公開的。同時,WINDOWS的程序用到的那些庫在壹般的系統裏都裝好了。所以2進制發布可行,也十分方便。
關於前壹個問題,我覺得源代碼發布的壹個好處是可以在編譯的時候進行壹些優化和設置。比如同樣的代碼,在32或64位平臺下編譯的時候可以進行適當的優化。另外,用戶也可以在編譯的時候設置壹些開關,這樣在編譯期間的優化壹般要好於運行時間的優化。
不過源代碼發布的壹個壞處就是對使用者要求較高。如果運行configue,make命令順利的話還好。如果萬壹不順利,要自己改下頭文件啥的,無疑是壹般的使用者無法做到的。另外庫之間的依賴關系如果是人手工處理的話也十分麻煩。好在LINUX世界後來有了YUM APT-GET之類的包管理系統。大多數軟件都可以很方便的安裝了。
2、進程及其創建 CreateProcess VS fork+execv
在WINDOWS世界,創建進程最常用的WIN 32 API 是 CreateProcess以及相關函數。這個函數需要壹堆參數(WINDOWS API 的特點),不過很多參數可以簡單的用NULL, TRUE OR FALSE來表示。另外,妳直接告訴它要執行的是哪個文件。
到了LINUX世界,我模糊的知道fork是用來創建壹個新進程的。但是當我看fork的函數說明的時候,呆住了。因為fork不需要任何參數。習慣了 CreateProcess 的10來個參數,突然換成壹個不要任何參數的函數,感覺很奇妙。壹方面覺得似乎事情簡單了很多,不用去把10來個參數的每個意思都搞明白。另外壹方面又很疑惑,我怎麽告訴它我要執行某個文件呢?
後來才知道,LINUX中的進程的含義和WINDOWS中是不壹樣的。LINUX中的進程本身是可以執行的。而WINDOWS中,進程只是表示壹個資源的擁有體,是不能執行的。要執行的話,壹定需要壹個線程。這也部分解釋了為什麽CreateProcess中為啥壹定要傳入要執行的文件的名字。
而fork的含義是把進程本身CLONE壹個新的出來。也就是說,FORK之後,父進程和子進程都執行同樣的壹段代碼。如果想區分的話,可以根據FORK的返回值來區分。引用壹段fork的說明:
On success, the PID of the child process is returned in the parent's thread of execution, and a 0 is returned in the child's thread of execution.
同時在LINUX程序中,常見的寫法如下:
int pid;
pid = fork();
switch (pid)
{
case 0: //I am the child
case -1: //failed.
default: //I am the parent
}
為什麽要這樣設計呢?因為LINUX的設計目標之壹就是應用於服務器。這種情況下,壹個SERVICE可能會啟動很多進程(線程)來服務不同的CLIENT. 所以FORK設計成快速復制父進程。子進程直接使用父親的地址空間,只有子進程加載壹個新的可執行文件的時候才創建自己的地址空間。
這樣節省了創建地址空間這個龐大的開銷,使得LINUX的進程創建十分快。不過實際上,這裏的進程相對於WINDOWS中的線程,所以同WINDOWS中的線程創建相比,二者的開銷應該差不多。
那麽如何才能讓新的進程加載壹個可執行文件呢,這時就要用execv以及相關函數了。所以LINUX中,代替CreateProcess()的函數是fork+execv
3、文件格式 PE VS ELF
WINDOWS中的可執行文件格式是PE。到了LINUX就變成了ELF。2者有相似的地方,比如都分成幾個SECTION,包含代碼段,數據段等。但是2個又不壹樣。使得從壹個轉到另外壹個的人不得不重新學習下。有點象在國內開慣了車的人,到了香港或者英國開車,雖然也是4個輪子壹個方向盤,但是壹個靠左行駛,壹個靠右。總是需要些時間來習慣。
那麽為啥LINUX不能和WINDOWS用同樣的文件格式呢?我覺得可能的原因有幾個。首先可能是2個差不多同時在設計的,彼此不知道對方的存在。所以也沒法壹方壓倒壹方。另外壹個可能的原因是PE格式最開始還是保密的(後來MS公開了PE的SPEC),所以即使LINUX想直接用PE都不行。
順便說下,MS OFFICE 的文檔格式以前也是保密的,直到最近(好像是2008年)才公開。希望這可以使得OPEN OFFICE的開發順利很多。
4、內核API:固定 VS 非固定
WINDOWS內核有壹套固定的API,而且向後兼容。這使得WINDOWS 驅動的開發人員在不同版本之間移植時變得很容易。比如我用WDM (WINDOWS DEVICE MODEL) 開發壹個驅動,最多改下編譯選項就可以在WIN 98, 2K, XP, 2003 下使用。VISTA 我覺得也許都可以。
而LINUX沒有固定的內核API。2.4版本的內核模塊在2.6幾乎很大可能是不能兼容的。要移植的話,不只是改個編譯選項,而是要改壹堆的頭文件和實現文件等。而麻煩的是,即使都是2.6內核,不同的小版本之間也有些不同。如果妳的內核模塊剛好用到了變化的部分,那麽也只好重新學習,然後改自己的頭文件或者實現文件了。
固定內核API的好處是兼容性好,壞處是包袱比較大,不得不隨時支持老的,也許是過時的接口。比如WINDOWS內核裏有WDM 壹套API, 但是又有網卡專用的 NDIS 壹套API. 實際上2套API的很多設計目標是重合的。那麽為什麽有2個呢?因為NDIS是先出來的,為了兼容性,壹定要支持。而NDIS又只針對網卡,所以又出來了WDM。
不固定API的壞處是升級很麻煩,外圍的內核模塊維護者很辛苦。好處是可以隨時采用更新的設計。
5. WINDOWS與LINUX中的中斷處理比較
5.1不同之處:
在WINDOWS中,有壹個IRQL (註意不是IRQ)的概念。最早的時候,我以為是CPU設計裏就包括了這個東東。後來看INTEL CPU手冊,發現似乎沒有。最近又看了壹遍WINDOWS INTERALS 4TH。感覺這個東西應該是包括在PIC OR APIC裏面的(關於APIC,可以看我以前的帖子)。對於X86-32,硬件設備的IRQ於IRQL之間的關系是:IRQL= 27-IRQ。引入IRQL的動機似乎是這樣的:當CPU運行在低IRQL時,如果來了壹個高IRQL對應的中斷,那麽低的中斷的ISR是會被高的ISR搶過去的。就是說低的ISR又被壹個更高級的ISR中斷了。這樣的好處是優先級高的ISR可以更快的得到響應。
另外,在具體實現中,由於操作PIC OR APCI改IRQL是比較費時的,所以WINDOWS是盡量不去直接操作硬件,而是等到萬不得已的時候才改。
在LINUX中,似乎沒有類似IRQL這樣的觀念。就我目前看過的書和代碼來看,LINUX中的ISR或者是KERNLE最多是操作下CPU上的中斷標誌位(IF)來開啟或者關閉中斷。也就是說,要麽中斷全開,要麽全關。
從這壹點來看,LINUX在這部分的設計上比WINDOWS簡單。
5.2 相似之處:
WINDOWS和LINUX似乎都把中斷分成了2部分。在LINUX中叫ISR(還是其他?)和BOTTOM HALF。而WINODWS中,DPC(Deferred Procedure Calls)和APC(Asynchronous Procedure Calls)就非常類似BOTTOM HALF。二者把中斷分成兩部分的動機是差不多的。都是為了把ISR搞得越快越好。LINUX中,在ISR裏壹般關中斷,所以時間太長的話,其他中斷就得不到響應。WINDOWS中,ISR跑在壹個很高的IRQL裏面,同樣會阻塞其他IRQL比較低的任務。
LINUX中的BOTTOM HALF 又可以分為TASKLET 和SOFIRQ。二者的主要區別是復雜度和並發性(CONCURRENCY)。下面COPY自<UNDERSTANDING LINUX NETWORK INTERNALS>壹書。
Tasklet: Only one instance of each tasklet can run at any time. Different tasklets can run concurrently on different CPUs.
Softirq: Only one instance of each softirq can run at the same time on a CPU. However, the same softirq can run on different CPUs concurrentlyOnly one instance of each softirq can run at the same time on a CPU. However, the same softirq can run on different CPUs concurrently.
WINDOWS中的DPC有點類似TASKLET和SOFTIRQ。 DPC是系統範圍內的,並且運行在DPC IRQL。是壹個類似中斷上下文的環境(INTERRUPT CONTEXT)。APC和DPC的區別是運行在更低級別的APC IRQL。另外,APC是針對每壹個線程的。執行在某個線程環境中。主要目的也是把壹部分事情放到以後去執行。APC又分為KERNEL APC 和USER APC。APC這個觀念在LINUX中似乎沒有類似的?至少我還沒想到。
5.3 參考文獻:
1. WINDOWS INTERALS 4TH
2. UNDERSTANDING LINUX NETWORK INTERNALS, 2005
UNICODE VS ASCII
KERNEL 4M/4K MIXED PAGE VS 4K PAGE
FS SEGMENT VS NO FS
GDI VS XWINDOWS
IRP VS FUNCTION POINTER
註冊表 VS 普通文件