进程——Windows核心编程学习手札系列之四
進程
——Windows核心編程學習手札系列之四
進程是一個正在運行的程序的實例,有兩個部分組成:一個是操作系統用來管理進程的內核對象,內核對象是系統用來存放關于進程的統計信息的地方;另一個是地址空間,它包含所有可執行模塊或DLL模塊的代碼和數據,還包含動態內存分配的空間,如線程棧和堆分配空間。進程必須擁有一個在它的環境中運行的線程,負責執行包含在進程的地址空間中的代碼。單個進程可能包含若干個線程,所有這些線程都“同時”執行進程地址空間中的代碼,每個線程有自己的一組CPU寄存器和堆棧。每個進程至少擁有一個線程來執行進程地址空間中的代碼,進程創建時系統會自動創建一個主線程,后該線程可以創建其他的線程。
Windows支持兩種類型的應用程序,一種是基于圖形用戶界面GUI的應用程序,另一種是基于控制臺用戶界面CUI的應用程序。基于GUI的應用程序有一個圖形前端程序,可創建窗口,擁有菜單,可通過對話框與用戶交互,并使用所有的標準Windows組件。基于控制臺的應用程序屬于文本操作的應用程序,通常不創建窗口或處理消息,不需要圖形用戶界面,包含在屏幕上的窗口中,窗口只包含文本,命令外殼程序CMD.EXE(用于Windows2000)和COMMAND.COM(用于Windows98)就是典型的基于CUI應用程序。
Microsoft Visual C++創建應用程序時,可通過設置鏈接程序開關選擇執行GUI或CUI,用于CUI應用程序的開關是/SUBSYSTEM:CONSOLE,GUI應用程序則是/SUBSYSTEM:WINDOWS,應用程序的開關設置在Microsoft Visual C++6.0中Project Settings的Link頁。當用戶運行一個程序時,操作系統的加載程序會查看可執行圖形程序的標題,抓取該子系統的值,如果該值指明是CUI應用程序窗口,則加載程序會為該應用程序創建文本控制臺窗口;如果是GUI應用程序,加載程序不創建控制臺窗口而加載應用程序,啟動后,操作系統不需要考慮應用程序提供什么樣的用戶界面。
Windows陰功程序必須擁有一個在應用程序啟動運行時調用的進入點函數,有四個:
int WINAPI WinMain(
?????????? HINSTANCE hinstExe,
?????????? HINSTANCE,
?????????? PSTR pszCmdLine,
?????????? Int nCmdShow);
int WINAPI wWinMain(
?????????? HINSTANCE hinstExe,
?????????? HINSTANCE,
?????????? PWSTR pszCmdLine,
?????????? int nCmdShow);
int __cdecl main(
????????? int argc,
????????? char *argv[],
????????? char *envp[]);
int __cdecl wmain(
??????? ??int argc,
????????? wchar_t *argv[],
????????? wchar_t *envp[]);
操作系統并不直接執行這四個進入點函數,而是調用C/C++運行期啟動函數。C/C++運行期啟動函數負責對C/C++運行期庫進行初始化,可調用malloc和free之類的函數,確保已經聲明的任何全局對象和靜態C++對象能夠在代碼執行以前正確地創建。下面給出了源代碼中選擇進入點及何時使用進入點。
======================================================================
? 應用程序類型?????????????????????????????? 進入點??? 嵌入可執行文件的啟動函數
? 需要ANSI字符和字符串的GUI應用程序?????? WinMian? WinMainCRTStartup
? 需要UNICODE字符和字符串的GUI應用程序?? wWinMain wWinMainCRTStartup
? 需要ANSI字符和字符串的CUI應用程序?????? main????? mainCRTStartup
? 需要UNICODE字符和字符串的CUI應用程序?? wmain??? wmainCRTStartup
鏈接程序負責在連接可執行文件時選擇相應的C/C++運行期啟動函數。如設置了/SUBSYSTEM:WINDOWS鏈接程序開關,那鏈接程序期望找到WinMain或wWinMain的進入點函數,如不存在,鏈接程序返回一個“未轉換的外部符號”的錯誤消息,否則分別選擇WinMainCRTStartup或wWinMainCRTStartup函數。如果應用程序中刪除/SUBSYSTEM鏈接程序開關,鏈接程序會自動尋找這四個函數(Winmain/wWinMain/main/wmain)以確定進入那個子系統和可執行程序應該嵌入到那個C/C++啟動函數。
上面四個C/C++運行期啟動函數在執行有何區別呢?基本作用是相同的,差別是處理ANSI或UNICODE字符串,以及選擇哪個進入點函數。Visual C++有C運行期庫的源代碼,可以在CRt0.c文件找到這四個啟動函數代碼,主要功能如下:
1)檢索指向新進程的完整命令行的指針;
2)檢索指向新進程的環境變量的指針;
3)對C/C++運行期庫的全局變量進行初始化,如包含了StdLib.h文件,代碼就能訪問該文件里的全局變量;
4)對C運行期內存單元分配函數(malloc和calloc)和其他低層輸入輸出例程使用的內存棧進行初始化;
5)為所有全局和靜態C++類對象調用構造函數。
這些初始化操作完成手,C/C++啟動函數就調用應用程序的進入點函數。當進入點函數返回時,啟動函數便調用C運行期的exit函數,將返回值(nMainRetVal)傳遞給Exit函數,并完成下面操作:
1)? 調用由_onexit函數的調用而注冊的任何函數;
2)為所有全局的和靜態的C++類對象調用析構函數;
3)調用操作系統的ExitProcess函數,將nMainRetVal傳遞進去,使操作系統能夠撤消進程并設置exit代碼。
加載到進程地址空間的每個可執行文件或DLL文件均被賦予一個唯一的實例句柄,可執行文件的實例作為進入點函數(w)WinMain的第一個參數hinstExe來傳遞。(w)WinMain的hinstExe參數的實際值是系統將可執行文件的映象加載到進程的地址空間時使用的基本地址空間。如系統打開了可執行文件并且將它的內容加載到地址0x00400000中,那么(w)WinMain的hinstExe參數的值就是0x00400000。可執行文件的映象加載到的基地址是有鏈接程序決定的。不同的鏈接程序可以使用不同的默認基地址。Visual C++鏈接程序使用的默認基地址是0x00400000,這是運行Windows98時可執行文件的映象可以加載到的最低地址,可以改變應用程序加載到的基地址,使用Microsoft的鏈接程序中的/BASE:address鏈接程序開關。函數GetModuleHandle返回可執行文件或DLL文件加載到進程的地址空間時所用的句柄或基地址:
HMODULE GetModuleHandle(PCTSTR pszModule);
調用該函數,傳遞一個以0結尾的字符串,用于設定加載到調用進程的地址空間的可執行文件或DLL文件的名字。如果系統找到了指定的可執行文件或DLL文件名,GetModuleHandle將返回該可執行文件或DLL文件映象加載到的基地址,如沒有找到該文件則返回NULL。也可為pszModule參數傳遞NULL,則GetModuleHandle將返回調用的可執行文件的基地址,這是C運行期啟動代碼調用(w)WinMain函數時候代碼執行的操作。該函數只查看調用進程的地址空間。
當一個進程創建時,要傳遞一個命令行,進程能夠接收由單個字符組成的命令行,即字符串結尾處的零。當C運行期的啟動代碼開始運行的時候,要檢索進程的命令行,跳過可執行文件的名字,將指向命令行其余部分的指針傳遞傳遞給WinMain的pszCmdLine參數。pszCmdLine參數總是指向一個ANSI字符串,如將WinMain改成wWinMain就能夠訪問進程的Unicode版本命令行。應用程序可以按照它選擇的方法來分析和轉換命令行字符串,可以寫入pszCmdLine參數指向的內存緩存,但在任何情況下都不應該寫到緩存外面去,因為僅是只讀緩存。如果想修改命令行,首先將命令行拷貝到應用程序的本地緩存然后修改本地緩存。獲得一個指向進程的完整命令行的指針,可調用方法:
PTSTR GetCommanLine();
該函數返回一個指向包含完整命令行的緩存的指針,命令行包括執行文件的完整路徑名。
每個進程都有一個與它相關的環境塊,環境塊是進程地址空間中分配的內存塊,每個環境塊都包含一組字符串,同時需將一個0字符置于所有環境變量的結尾處,以表示環境塊的結束。使用GetEinvironmentVariable函數可確定某個環境變量是否存在以及它的值:
DWORD GetEnvironmentVariable(
???????????? PCTSTR pszName,
???? ????????PTSTR pszValue,
???????????? DWORD cchValue);
其中,pszName指向需要的變量名,pszValue指向用于存放變量值緩存,cchValue用于指明緩存的大小(用字符數表示),該函數可以返回拷貝到緩存的字符數,如果在環境塊中找不到該變量,可返回0。可以通過SetEnvironmentVariable函數來添加、刪除、修改變量的值:
BOOL SetEnvironmentVariable(
???????????? PCTSTR pszName,
???????????? PCTSTR pszValue);
該函數可以使環境變量保持有序排列。
創建進程的函數CreatProcess,系統會調用個億 進程內核對象,其初始使用計數是1,進程內核對象不是進程本身,是操作系統管理進程時使用的一個較小數據機構;系統還會為進程分配一個虛擬地址空間,并將可執行文件或任何必要的DLL文件的代碼和數據加載到進程的地址空間中;接著,系統為新進程的主線程創建一個線程內核對象(其使用計數為1),也是操作系統管理線程的小型數據結構;通過執行C/C++運行期啟動代碼,主線程便開始運行,如成功創建了新進程和主線程CreateProcess邊返回TRUE。具體函數如下:
BOOL CreateProcess(
?????????? PCTSTR pszApplicationName,
?????????? PTSTR pszCommandLine,
?????????? PSECURITY_ATTRIBUTES psaProcess,
?????????? PSECURITY_ATTRIBUTES psaThread,
?????????? BOOL bInheritHandles,
?????????? DWORD fdwCreate,
?????????? PVOID pvEnvironment,
?????????? PCTSTR pszCurDir,
?????????? PSTARTUPINFO psiStartInfo,
?????????? PPROCESS_INFOMATION ppiProcInfo);
pszApplicationName和pszCommandLine參數分別用于設定新進程將要使用的可執行文件的名字和傳遞給新進程的命令行字符串;fdwCreate參數用于標識標志,以便用于規定如何來創建新進程;pvEnvironment參數用于指向包含新進程將要使用的環境字符串的內存塊;pszCurDir參數允許父進程設置子進程的當前驅動器和目錄。
終止進程的運行的方法:
1)主線程的進入點函數返回(最好使用這個方法);
2)進程中的一個線程調用ExitProcess函數(應用避免使用這個方法);
3)另一個進程中的線程調用TerminateProcess函數(應用避免使用這個方法);
4)進程中的所有線程自行終止運行(比較少發生);
設計應用程序應在主線程的進入點函數返回時,進程才終止運行,可保證所有線程資源能夠得到正確清除:
1)該線程創建的任何C++對象將能使用它們的析構函數正確地撤消;
2)操作系統將正確地釋放該線程的堆棧使用的內存;
3)系統將進程的退出碼(在進程的內核對象中維護)設置為進入點函數的返回值;
4)系統將進程的內核對象的返回值遞減1;
函數ExitProcess可終止進程運行:
VOID ExitProcess(UINT fuExitCode);
該函數終止進程的運行,并將進程的退出碼設置為fuExitCode,函數不返回任何值,因為進程已終止運行,如在ExitProcess之后又增加處理代碼,則代碼將不會被執行。當主線程的進入點函數(WinMain、wWinMain、main、wmain)返回時,將返回給C/C++運行期啟動代碼,可正確清除該進程使用的所有C運行期資源。當C運行期資源被釋放之后,C運行期啟動代碼就顯示調用ExitProcess,并將進入點函數返回的值傳遞給它。進程中運行的任何其他線程都隨著進程終止運行,如果如果在進入點函數中調用ExitThread,而不是調用ExitProcess或者僅僅是返回,那應用程序的主線程將停止運行,但進程如仍有線程在運行那么將不會終止。只有從進入點函數返回,C/C++運行期就能執行它的清除操作,并正確撤消任何或所有C++對象。
函數TerminateProcess也能終止進程運行:
BOOL TerminateProcess(
????????????? HANDLE hProcess,
????????????? UINT fuExitCode);
任何線程都可以調用TerminateProcess來終止另一進程或自己進程的運行。hProcess參數用于標識要終止運行進程的句柄,當進程終止運行時,它的退出代碼將作為fuExitCode參數的值來傳遞。該函數是異步運行的函數,只有其他方法無法終止進程時,才使用該函數,因為終止運行的進程得不到關于它將終止運行的任何通知,因此無法正確清除并不能避免自己被撤消(除非通過正常的安全機制),例如進程將無法將內存中它擁有的任何信息迅速送往磁盤。
當進程終止運行時,下面操作將啟動運行:
1)進程中剩余的所有線程全部終止運行;
2)進程指定的所有用戶對象和GDI對象均被釋放,所有內核對象均被關閉(如果沒有其他進程打開它們的句柄,那這些內核對象將被撤消);
3)進程的退出代碼將從STILL_ACTIVE改為傳遞給ExitProcess或TerminateProcess的代碼;
4)進程內核對象的狀態變成收到通知狀態,系統中其他線程可以掛起,直到進程終止運行;
5)進程內核對象使用計數遞減1;
本學習手札給出了幾種類不同枚舉系統進程的代碼:
頭文件包含:#include? "tlhelp32.h"
具體代碼:
LPPROCESSENTRY32? pinfo=(LPPROCESSENTRY32)malloc(sizeof(PROCESSENTRY32));
pinfo->dwSize?? =?? sizeof(PROCESSENTRY32);??
HANDLE? hProcess=(HANDLE)CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
BOOL report=Process32First(hProcess,pinfo);
while(report)
{
?????? CString strFile=pinfo->szExeFile;
?????? int iProID=pinfo->th32ProcessID;
?????? printf("進程名稱:%s-----進程ID:%d /n",strFile,iProID);
?????? report=Process32Next(hProcess, pinfo);??
}
free(pinfo);
??????????????????? 如非? 2008-12-1
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的进程——Windows核心编程学习手札系列之四的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对程序错误的处理——Windows核心编
- 下一篇: 内核对象——Windows核心编程学习手