Win32 exe 檔案的啟動過程(zz)
數以後的。前者涉及很多windows作業系統內部的知識,後者需要看mfc原始碼。雖然大多數程式不需要了解太多關於os載入應用程式這方面的知識,但能較深入瞭解windows os的執行情況對程式設計師是很有幫助的。
關於os如何載入程式,它包括程序建立,主執行緒 建立,PE檔案載入,程式c執行時啟動函式以及四種
main函式的呼叫等。從mfc程式設計角度來說,這些都是不得見的,不過了解這些對程式設計師編制好的windows程式是有好處的。到底在桌面雙擊一個exe程式,os呼叫的第一個函式是什麼?
要了解一個.exe程式的啟動過程就不得不瞭解一下有關作業系統方面的知識,such as“程序,執行緒,虛擬記憶體"的基本的知識。
未真正開始之前,先統一一下本文出現的一些名詞的含義:
App.exe----------假定為應用mfc的AppWizard做出的一個SDI程式,App是它的名字。你
可以把它看為一個標準的"hello mfc!"程式。
PE------------不要以為它是“體育課”的縮寫呦。它可是微軟的標準win32可執行檔案
.exe和動態連結庫.dll的檔案格式,它的english name是Portable Executable File Forma
t。
下面可要正式開始了。
一個microsoft的.exe程式的啟動方法有很多,這裡我們以雙擊App.exe圖示啟動為例(其他方法,我想也是一樣的)。在補充一下,我所用的os是Windows2000Server,所以這裡也
主要討論win2000下的應用程式,過要涉及較多關於NT核心,畢竟微軟主推win2000/winxp和
Unicode。
一個microsoft的.exe程式的啟動過程如下:
(1)當我們雙擊App.exe圖示啟動程式時,系統首先做什麼呢,讓我們先聽一聽侯捷是如何說的吧“執行起來的App程序其實是shell呼叫CreateProcess啟用的”----"深入淺出MFC second edition" page39載。很多書上都是如是說的,shell又名“命令直譯器”,是win32作業系統基於瀏覽器的一個32位使用者介面,它是一個多執行緒的好例子,螢幕上每一個資料夾瀏覽視窗都是它的一個執行緒。它是作業系統引導時載入的系統程序,它具體表現為windows explorer.exe。explorer.exe是所有使用者應用程式的創造者。你完全可以將shell看成是所有應用程式程序的父程序,就像桌面(desktop)可看成所有視窗的父視窗一樣。shell的用途很多,如啟動應用程式,管理檔案系統,將應用程式與相應檔案相關聯等等。我們常見的桌面上的帶有小箭頭的快捷方式(shortcut)就是一個shell連結,shell負責管理一個叫"名字空間"的類似檔案系統似的“超檔案系統”,它允許應用程式在任何地方在不知訪問物件名字和位置的前提下訪問到這個物件,此類物件有:檔案,目錄,驅動器,印表機以及網路資源。而名字空間就是shell把這些物件有層次組織起來的一個結構。名字空間為使用者和應用程式提供了一種可靠和高效的方法來訪問和管理物件。好了不論它是什麼,凡正它呼叫了CreateProcess,一切就從這裡開始了。
(2)CreateProcess這個函式可作了不少工作。App程序由此誕生。當CreateProcess這個函式被呼叫,系統就會建立一個“程序核心物件”。程序核心物件可以看作一個作業系統用來管理程序的核心物件,它也是系統用來存放關於程序統計資訊的地方(一個小的資料結構),其實它的真正建立者是一個叫NTCreateProcess的windows2000系統服務函式(也叫執行體服務函式),他建立了程序核心物件供使用者擴充套件。程序核心物件的初始使用計數為1。然後系統為該程序建立4GB(=2^32)的虛擬地址空間(所謂虛擬就不是真的建立4GB的實體記憶體空間,這些空間不是真在實體記憶體上).用於載入App.exe可執行檔案和任何必要的dll檔案的資料和程式碼。
(3)下面概述一下系統的載入器(可稱為loader)是如何載入這些東東的。首先了解一下系統為該程序建立4GB的虛擬地址空間是如何分配的,對於win2000/winxp來說,預設情況下每個使用者程序可以佔有2GB的私有地址空間;作業系統佔有剩餘的2GB空間。在32位x86系統上,
從0x00000000到0x7fffffff的空間中存放著 應用程式程式碼,全域性變數,每個執行緒堆疊,dll
程式碼。
從0x80000000到0xc0000000的空間中存放著 核心和執行體,HAL(硬體抽象層),引導驅動程式
。
從0xc0000000到0xc0800000的空間中存放著 程序頁表和超空間。
從0xc0800000到0xffffffff的空間中存放著 系統快取記憶體,分頁緩衝池,非分頁緩衝池。
首先,CreateProcess開啟應用程式檔案(.exe),它先掃描該檔案的檔案頭,該檔案頭裡含有檔案能執行在那個環境之下,如果是win32環境,系統就直接載入檔案的程式碼和資料並輸入(import)該檔案執行所需的dll函式。如果不是win32環境比如時os/2的.exe則先載入相應的環境子系統,載由該環境載入該檔案的程式碼和資料以及該檔案執行所需的dll函式。至於系統是如何知道檔案的程式碼和資料以及該檔案執行所需的dll函式所在的位置就需要你瞭解一下PE檔案格式了,其實也很簡單,PE檔案擁有很多sections,資料和程式碼都放在不同的section裡面,檔案執行所需的dll也放在單獨的section(.idata)裡,這裡就不詳述了。而且在載入過程中涉
及到有關虛擬記憶體,記憶體對映檔案等很多較深的知識,我會在以後的系列文章中詳細專題論述的。
(4)程序載入程式碼和資料完畢後,就開始建立執行緒來執行程序空間內的程式碼。程序是靜態的,它只是執行緒的容器。一個程序至少因該有一個執行緒(main thread),其它執行緒都是主執行緒通過呼叫CreateThread函式建立的。執行緒也是核心物件,他的實際建立者是一個叫NtCreateThread的windows2000系統服務函式。一個執行緒其實只是一個執行緒核心物件和兩個堆疊(一個核心堆疊,用於執行緒執行在核心態;一個使用者堆疊,用於執行緒執行在使用者態),執行緒與程序類似,也擁有執行緒核心物件計數和執行緒控制代碼,這裡不詳述。執行緒用於描述程序中的執行路徑。每當程序被初始化時,系統就要建立一個主執行緒。該執行緒與c/c++執行時庫的啟動程式碼一道開始執行,啟動程式碼則呼叫進入點函式(就是我們的main函式,它也是主執行緒的進入點函式),並且繼續執行直到進入點函式返回並且c/c++執行時庫的啟動程式碼呼叫ExitProcess為止。每個執行緒都有自己的入口點函式,主執行緒入口點函式名字必須是main,wmain,WinMain或wWinMain.而其他的執行緒入口點函式名字可使用任何名字。每個執行緒函式必須有一個返回值,它將作為執行緒的退出程式碼。對於主執行緒來說,這個返回值將傳給c/c++執行時庫的啟動函式。
(5)c/c++執行時庫的啟動函式它其實是一個程式的真正呼叫的第一個函式,它是在程式連結時由連結程式選擇相應的啟動函式並加到程式的開始處。c/c++執行時庫有四個版本的啟動函式,他們分別對應不同型別的應用程式。比如,需要ANSI字元和字串的GUI應用程式的啟動函式是WinMainCRTStartup,其對應的進入點函式是WinMain,需要Unicode字元和字串的GUI應用程式的啟動函式是wWinMainCRTStartup,其對應
的進入點函式是wWinMain,而需要ANSI字元和字串的CUI應用程式(如控制檯console程式)的應用程式的啟動函式是mainCRTStartup,對應的入口點函式為main;需要Unicode字元和字串的CUI應用程式(如控制檯console程式)的應用程式的啟動函式為wmainCRTStartup,對應的入口點函式為wmain;c/c++執行時庫的啟動函式的功能如下:
以wWinMainCRTStartup(大多數執行在windows2000下的應用程式的啟動函式都是它)為例。它負責:
*檢索指向新程序的完整命令列指標;
*檢索指向新程序的環境變數的指標;
*對c/c++執行時的全域性變數進行初始化;
*對c執行期的記憶體單元分配函式(比如malloc,calloc)和其他低層I/O例程使用的記憶體棧進行初始化。
*為C++的全域性和靜態類呼叫建構函式。
當這些初始化工作完成後,該啟動函式就呼叫wWinMain函式進入應用程式的執行。
當wWinMain函式執行完畢返回時,wWinMainCRTStartup啟動函式就呼叫c執行期的exit()函式,將返回值(nMainRetVal)傳遞給它。之後exit()便開始收尾工作:
*呼叫由_onexit()函式呼叫和註冊的任何函式。
*為C++的全域性和靜態類呼叫解構函式;
*呼叫作業系統的ExitProcess函式,將nMainRetVal傳遞給它,這使得作業系統能夠撤銷進
程並設定它的exit 程式碼。
(6)至此啟動函式的任務完成,至於中間wWinMain函式的執行過程看看mfc原始碼即可。不過我還要提一下,wWinMain函式其實只是呼叫了mfc的AfxWinMain()函式,而一切的真正程式碼的運
行也是從AfxWinMain()開始的。
以上只是粗略將一下一個microsoft的.exe程式的啟動過程,其中有很多深奧的知識我只是提了一下,有些知識在以後的文章中還會陸續提到的。