今天終于有時(shí)間來(lái)研究一下一個(gè)很大很大的工程編譯成一個(gè)exe和若干dll后,程序是如果執(zhí)行它的第一條指令的?操作系統(tǒng)以什么規(guī)則來(lái)找到應(yīng)該執(zhí)行的第一條指令(或說(shuō)如何找到第一個(gè)入口函數(shù)的)?
成都創(chuàng)新互聯(lián)公司是由多位在大型網(wǎng)絡(luò)公司、廣告設(shè)計(jì)公司的優(yōu)秀設(shè)計(jì)人員和策劃人員組成的一個(gè)具有豐富經(jīng)驗(yàn)的團(tuán)隊(duì),其中包括網(wǎng)站策劃、網(wǎng)頁(yè)美工、網(wǎng)站程序員、網(wǎng)頁(yè)設(shè)計(jì)師、平面廣告設(shè)計(jì)師、網(wǎng)絡(luò)營(yíng)銷人員及形象策劃。承接:成都做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)、網(wǎng)站改版、網(wǎng)頁(yè)設(shè)計(jì)制作、網(wǎng)站建設(shè)與維護(hù)、網(wǎng)絡(luò)推廣、數(shù)據(jù)庫(kù)開(kāi)發(fā),以高性價(jià)比制作企業(yè)網(wǎng)站、行業(yè)門(mén)戶平臺(tái)等全方位的服務(wù)。 我們以前寫(xiě)windows控制臺(tái)程序時(shí),都是先寫(xiě)個(gè)main()函數(shù),寫(xiě)windows窗口程序時(shí),首先要寫(xiě)winmain()函數(shù),然后再寫(xiě)自己的邏輯;然后編譯,然后點(diǎn)擊exe就能運(yùn)行我們的程序了;并且認(rèn)為main或winmain是程序中第一個(gè)運(yùn)行的程序,也是必須存在的函數(shù),但深入了解window的編程就會(huì)發(fā)現(xiàn),main或winmain函數(shù)不是第一個(gè)運(yùn)行的函數(shù),在他們之前首先會(huì)運(yùn)行另一個(gè)函數(shù),會(huì)對(duì)全局變量進(jìn)行初始化和資源的分配,以及程序結(jié)束后會(huì)對(duì)資源的釋放。
我們以前寫(xiě)的程序在編譯器編譯成為一個(gè)模塊(可能是obj文件或其他形式),然后連接器會(huì)將一些所需要的庫(kù)文件和剛才編譯器生成的文件進(jìn)行連接,最終生成一個(gè)exe文件,在所連接的庫(kù)文件中就包含CRT運(yùn)行時(shí)庫(kù),這就是我們今天談?wù)摰闹鹘?。在運(yùn)行時(shí)庫(kù)里面有好一個(gè)已經(jīng)定義如下的函數(shù)函數(shù):
(1)mainCRTStartup(或 wmainCRTStartup) //使用 /SUBSYSTEM:CONSOLE 的應(yīng)用程序
(2)WinMainCRTStartup(或 wWinMainCRTStartup)//使用 /SUBSYSTEM:WINDOWS 的應(yīng)用程序
(3)_DllMainCRTStartup //調(diào)用 DllMain(如果存在),DllMain 必須用 __stdcall 來(lái)定義
其中w開(kāi)頭的函數(shù)時(shí)unicode版本的,分割符‘//’后面的是入口點(diǎn)函數(shù)匹配的subsystem屬性設(shè)置。
如果未指定 /DLL 或 /SUBSYSTEM (也就是subsystem選項(xiàng))選項(xiàng),則鏈接器將根據(jù)是否定義了 main 或 WinMain 來(lái)選擇子系統(tǒng)和入口點(diǎn)。 函數(shù) main、WinMain 和 DllMain 是三種用戶定義的入口點(diǎn)形式。
在默認(rèn)情況下,如果你的程序中使用的是main()或_main()函數(shù),這連接器會(huì)將你的使用(1)中的函數(shù)連接到你的exe中;如果你的函數(shù)是以WinWain()函數(shù)開(kāi)始的則連接器使用(2)中的函數(shù)連接進(jìn)exe中;如果我們寫(xiě)的是DLL程序這連接進(jìn)DLL的是(3)中的函數(shù)。
用我們寫(xiě)的程序最終生成的exe執(zhí)行時(shí),一開(kāi)始執(zhí)行的就是上面的函數(shù)之一,而不是我們程序所寫(xiě)的main或WinMain等。那么連接器為什么要這樣做呢?這就是因?yàn)槲覀儗?xiě)的程序必須要使用到各種各樣的運(yùn)行時(shí)庫(kù)函數(shù)才能正常工作,所有在執(zhí)行我們自己寫(xiě)程序之前必須要先準(zhǔn)備好所需要的一切庫(kù),噢,明白了吧,之所以要連接它們是因?yàn)樗麄兗缲?fù)著很重要的使命,就是初始化好運(yùn)行時(shí)庫(kù),準(zhǔn)備我們的程序執(zhí)行時(shí)調(diào)用。
那么這些函數(shù)具體做了什么呢?通過(guò)MSDN我們可以知道---它們會(huì)去進(jìn)一步調(diào)用其他函數(shù),使得C/C++ 運(yùn)行時(shí)庫(kù)代碼在靜態(tài)非局部變量上調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)。
設(shè)有一個(gè)Win32下的可執(zhí)行文件MyApp.exe,這是一個(gè)Win32應(yīng)用程序,符合標(biāo)準(zhǔn)的PE格式。MyApp.exe的主要執(zhí)行代碼都集中在其源文件MyApp.cpp中,該文件第一個(gè)被執(zhí)行的函數(shù)是WinMain。初學(xué)者會(huì)認(rèn)為程序就是首先從這個(gè)WinMain函數(shù)開(kāi)始執(zhí)行,其實(shí)不然。
在WinMain函數(shù)被執(zhí)行之前,有一系列復(fù)雜的加載動(dòng)作,還要執(zhí)行一大段啟動(dòng)代碼。運(yùn)行程序MyApp.exe時(shí),操作系統(tǒng)的加載程序首先為進(jìn)程分配一個(gè)4GB的虛擬地址空間,然后把程序MyApp.exe所占用的磁盤(pán)空間作為虛擬內(nèi)存映射到這個(gè)4GB的虛擬地址空間中。一般情況下,會(huì)映射到虛擬地址空間中0X00400000的位置。加載一個(gè)應(yīng)用程序的時(shí)間比一般人所設(shè)想的要少,因?yàn)榧虞d一個(gè)PE文件并不是把這個(gè)文件整個(gè)一次性的從磁盤(pán)讀到內(nèi)存中,而是簡(jiǎn)單的做一個(gè)內(nèi)存映射,映射一個(gè)大文件和映射一個(gè)小文件所花費(fèi)的時(shí)間相差無(wú)幾。當(dāng)然,真正執(zhí)行文件中的代碼時(shí),操作系統(tǒng)還是要把存在于磁盤(pán)上的虛擬內(nèi)存中的代碼交換到物理內(nèi)存(RAM)中。但是,這種交換也不是把整個(gè)文件所占用的虛擬地址空間一次性的全部從磁盤(pán)交換到物理內(nèi)存中,操作系統(tǒng)會(huì)根據(jù)需要和內(nèi)存占用情況交換一頁(yè)或多頁(yè)。當(dāng)然,這種交換是雙向的,即存在于物理內(nèi)存中的一部分當(dāng)前沒(méi)有被使用的頁(yè)也可能被交換到磁盤(pán)中。
接著,系統(tǒng)在內(nèi)核中創(chuàng)建進(jìn)程對(duì)象和主線程對(duì)象以及其它內(nèi)容。
然后操作系統(tǒng)的加載程序搜索PE文件中的引入表,加載所有應(yīng)用程序所使用的動(dòng)態(tài)鏈接庫(kù)。對(duì)動(dòng)態(tài)鏈接庫(kù)的加載與對(duì)應(yīng)用程序的加載完全類似。
再接著,操作系統(tǒng)執(zhí)行PE文件首部所指定地址處的代碼,開(kāi)始應(yīng)用程序主線程的執(zhí)行。首先被執(zhí)行的代碼并不是MyApp中的WinMain函數(shù),而是被稱為C Runtime startup code的WinMainCRTStartup函數(shù),該函數(shù)是連接時(shí)由連接程序附加到文件MyApp.exe中的。該函數(shù)得到新進(jìn)程的全部命令行指針和環(huán)境變量的指針,完成一些C運(yùn)行時(shí)全局變量以及C運(yùn)行時(shí)內(nèi)存分配函數(shù)的初始化工作。如果使用C++編程,還要執(zhí)行全局類對(duì)象的構(gòu)造函數(shù)。最后,WinMainCRTStartup函數(shù)調(diào)用WinMain函數(shù)。
WinMainCRTStartup函數(shù)傳給WinMain函數(shù)的4個(gè)參數(shù)分別為:hInstance、hPrevInstance、lpCmdline、nCmdShow。
hInstance:該進(jìn)程所對(duì)應(yīng)的應(yīng)用程序當(dāng)前實(shí)例的句柄。WinMainCRTStartup函數(shù)通過(guò)調(diào)用GetStartupInfo函數(shù)獲得該參數(shù)的值。該參數(shù)實(shí)際上是應(yīng)用程序被加載到進(jìn)程虛擬地址空間的地址,通常情況下,對(duì)于大多數(shù)進(jìn)程,該參數(shù)總是0X00400000。
hPrevInstance:應(yīng)用程序前一實(shí)例的句柄。由于Win32應(yīng)用程序的每一個(gè)實(shí)例總是運(yùn)行在自己的獨(dú)立的進(jìn)程地址空間中,因此,對(duì)于Win32應(yīng)用程序,WinMainCRTStartup函數(shù)傳給該參數(shù)的值總是NULL。如果應(yīng)用程序希望知道是否有另一個(gè)實(shí)例在運(yùn)行,可以通過(guò)線程同步技術(shù),創(chuàng)建一個(gè)具有唯一名稱的互斥量,通過(guò)檢測(cè)這個(gè)互斥量是否存在可以知道是否有另一個(gè)實(shí)例在運(yùn)行。
lpCmdline:命令行參數(shù)的指針。該指針指向一個(gè)以0結(jié)尾的字符串,該字符串不包括應(yīng)用程序名。
nCmdShow:指定如何顯示應(yīng)用程序窗口。如果該程序通過(guò)在資源管理器中雙擊圖標(biāo)運(yùn)行,WinMainCRTStartup函數(shù)傳給該參數(shù)的值為SW_SHOWNORMAL。如果通過(guò)在另一個(gè)應(yīng)用程序中調(diào)用CreatProcess函數(shù)運(yùn)行,該參數(shù)由CreatProcess函數(shù)的參數(shù)lpStartupInfo(STARTUPINFO.wShowWindow)指定。
操作系統(tǒng)裝載應(yīng)用程序后,做完初始化工作就轉(zhuǎn)到程序的入口點(diǎn)執(zhí)行。程序的默認(rèn)入口點(diǎn)由連接程序設(shè)置, 不同的連接器選擇的入口函數(shù)也不盡相同。在VC++下,連接器對(duì)控制臺(tái)程序設(shè)置的入口函數(shù)是 mainCRTStartup,mainCRTStartup 再調(diào)用main 函數(shù);對(duì)圖形用戶界面(GUI)程序設(shè)置的入口函數(shù)是 WinMainCRTStartup,WinMainCRTStartup 調(diào)用你自己寫(xiě)的 WinMain 函數(shù)。具體設(shè)置哪個(gè)入口點(diǎn)是由連接器的“/subsystem:”選項(xiàng)確定的,它告訴操作系統(tǒng)如何運(yùn)行編譯生成的.EXE文件??梢灾付ㄋ姆N方式:CONSOLE|WINDOWS|NATIVE|POSIX。如果這個(gè)選項(xiàng)參數(shù)的值為 WINDOWS,則表示該應(yīng)用程序運(yùn)行時(shí)不需要控制臺(tái),有關(guān)連接器參數(shù)選項(xiàng)的詳細(xì)說(shuō)明請(qǐng)參考 MSDN 庫(kù)。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。