exe程序的启动过程
?? 最近我正在研究一個microsoft的.exe程序的啟動過程,這也是以上所說的關于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 Format。
?
?? 下面可要正式開始了。
一個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)可看成所有窗口的父窗口一樣。(可以用pexplore查看od啟動后,進程關系。)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程序的啟動過程,其中有很多深奧的知識我只是提了一下,有些知識在以后的文章中還會陸續提到的。
???????????????????????????????????????????????????????
?
?
?
?
轉:Windows中.exe程序的啟動過程和C/C++運行時庫
Windows系統中,.exe后綴的文件一般可以雙擊運行。編程時,編譯出來的最終結果一般也表現為一個exe程序和其他的為程序執行提供支持的dll。我們雙擊一個exe程序的時候,在操作系統層面上,做了些什么使得應用程序能夠執行呢?
現在有一個App.exe文件,根據這篇文章的說法,我總結了一下,雙擊App.exe之后操作系統做的工作如下:
1、 shell調用CreateProcss激活一個App.exe進程。Shell即命令解釋器,是操作系統引導時即加載的一個系統進程,在Windows任務管理器里面可以看到一個名為”Explorer.exe”的進程,就是它了。
2、 CreateProcss創建了一個進程內核對象,而系統為該進程創建4GB的虛擬地址空間(在Win2000/WinXP下,每個進程可以有2GB的私有地址空間,剩余的2GB由操作系統占用)用來加載App.exe和其他必要的DLL函數;
3、 CreateProcess加載exe文件,分析文件頭(具體格式見PE文件格式分析)以識別文件的運行環境,根據文件頭決定由那個環境進行加載操作;
4、 加載App.exe及其必要的DLL文件數據和代碼后,CreateProcss即創建主線程,執行C/C++運行時的啟動代碼,由啟動代碼執行剩下的過程。
從上面的描述可以看出,一個程序真正調用的第一個應該是C/C++運行時的啟動函數。那么C/C++運行時庫在程序運行時起到了什么樣的作用?下面是關于C/C++運行時的一些學習體會。
什么是C/C++運行時庫,網上隨便一搜,能得到一大串結果。運行時庫是一個library,我們日常編寫的程序代碼都是運行在這個庫上的,運行時庫完成了一些底層的基礎的工作,例如初始化運行期間的內存單元分配函數,初始化底層I/O例程使用的內存棧,初始化C/C++運行時的全局變量,為C++全局和靜態類調用構造函數等等。這樣的運行時庫使得程序員不必關心過于底層的內容,專注于自己的應用程序邏輯。運行時庫還提供一些基礎的庫函數調用,如memcpy,malloc之類的,更重要的是,運行時庫還為應用程序添加啟動函數。
Windows環境下,VC提供的 C run-time library又分為動態運行時庫和靜態運行時庫。動態運行時庫主要是msvcrt.dll(Debug版:msvcrtd.dll),對應的庫文件是msvcrt.lib(Debug版:msvcrtd.lib)。靜態運行時庫對應的主要文件是:libc.lib (單線程靜態庫)和libcmt.lib (多線程靜態庫)。其中msvcrt.dll提供幾千個C函數,包括printf這么低級的函數也在msvcrt.dll中。
應用程序編寫完成后進行編譯和鏈接時,編譯器根據編譯選項(Visual Studio中即為工程設置),如單線程、多線程或DLL,自動為應用程序鏈接不同的運行時庫的啟動函數。在VS2005中,通過下面的操作可以查看或修改選項以決定鏈接哪個運行時庫:
打開工程屬性,選擇左側的配置屬性—->C/C++—->代碼生成,查看【運行時庫】,選擇不同的運行時庫即可。
回到第一個問題,主線程執行C/C++運行時的啟動代碼,由啟動函數調用對應的入口點函數,進入應用程序執行代碼邏輯。
不使用寬字節的控制臺程序的啟動函數為mainCRTStartup。這個函數在VC安裝目錄下的crt\src\ crt0.c文件中。下面是一個從網上找到的簡化版:
void mainCRTStartup(void)
{
int mainret;
?
_osver = GetVersion();
_winminor = ( _osver >> 8 ) & 0×00FF ;
_winmajor = _osver & 0×00FF ;
_winver = ( _winmajor << 8 ) + _winminor;
_osver = ( _osver >> 16 ) & 0×00FFFF ;
_ioinit();
?
_acmdln = (char *) GetCommandLineA();
?
_aenvptr = (char *) __crtGetEnvironmentStringsA();
_setargv();
_setenvp();
_cinit();
__initenv = _environ;
mainret = main( __argc, __argv, _environ );
exit( mainret );
}
從以上代碼可知,運行庫在調用用戶程序的main或WinMain函數之前,進行了一些初始化工作。初始化完成后,接著才調用了我們自己編寫的main或WinMain函數。這樣, C/C++運行時庫和應用程序就正常地工作起來了。
除了crt0.c外,C運行時庫中還包含wcrt0.c、 wincrt0.c、wwincrt0.c三個文件用來提供初始化函數。wcrt0.c是crt0.c的寬字符集版,wincrt0.c中包含 windows應用程序的入口函數,而wwincrt0.c則是wincrt0.c的寬字符集版。
同樣由上面簡化版的代碼可知,當用戶程序的main或WinMain函數執行結束后,返回值被當做參數傳入exit函數中,有exit完成程序執行的收尾工作,包括析構C++全局和靜態類,調用操作系統的ExitProcess函數,告知進程退出等。
以上是我對于程序執行過程和C/C++運行時庫的一點理解,若有錯漏,歡迎指正。
轉載于:https://www.cnblogs.com/big4panda/archive/2010/09/25/6417603.html
總結
以上是生活随笔為你收集整理的exe程序的启动过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: switchHosts 介绍
- 下一篇: 迪士尼手机官方专卖东家京破产