日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

《Windows核心编程》读书笔记四 进程

發布時間:2024/1/8 windows 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《Windows核心编程》读书笔记四 进程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

第四章 進程


本章內容


4.1 編寫第一個Windows應用程序

4.2 CreateProcess函數

4.3 終止進程

4.4 子進程

4.5 管理員以標準用戶權限運行時


進程定義為一個正在運行的程序的一個實例,它可以由一下兩部分構成。

a. 一個內核對象,操作系統用它來管理進程。內核對象也是系統保存進程統計信息的地方。

b.一個地址空間,其中包含所有可執行文件或DLL模塊的代碼和數據。還包含動態內存分配,比如線程棧和堆的分配。


進程是有“惰性”的,進程要做任何事情,都必須讓一個線程在他的上下文(環境 內存地址空間等)中運行。該線程執行進程地址空間包含的代碼。

進程可以包含多個線程,所有線程都在進程的地址空間中“同時”執行代碼。每個線程有自己的堆棧和自己的一組CPU寄存器

系統創建進程的時候會創建一個主線程。然后由主線程再創建更多的子線程。

如果沒有線程要繼續執行的代碼,進程就失去了存在的理由。系統回自動銷毀進程和其地址空間。


操作系統會輪流為每一個線程調度一些cpu時間。它采取循環(round-robin,輪詢或輪流)方式,為每個線程都分配時間片(稱為“量”或者“量程”)從而營造出并發的假象。

如果計算機裝載多cpu或者(多核cpu),操作系統會采用更復雜的算法為線程分配cpu時間。




4.1 編寫第一個Windows應用程序

Windows支持兩種類型的應用程序,GUI(Graphic User Interface)和CUI(Console User Interface)


事實上CUI程序也能顯示出圖形界面。也可以在一個GUI程序中像控制臺輸出文本。

GUI和CUI程序在VS中主要取決于連接器的設置。 CUI /SUBSYSTEM:CONSOLE

GUI :/SUBSYSTEM:WINDOWS


用戶運行應用程序時,操作系統加載程序(loader)會檢查可執行文件映像的文件頭,并獲取這個子系統值。然后進行相應的加載(開啟一個命令行窗口 或是創建一個主窗口)

等程序運行以后,操作系統就不再關心是CUI還是GUI了。

Windows應用程序的入口函數

INT WINAPI _tWinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPSTR lpCmdLine,_In_ int nShowCmd);int _tmain(_In_ int _Argc,_In_reads_(_Argc) _Pre_z_ wchar_t ** _Argv,_In_z_ wchar_t ** _Env);
操作系統本身并不會調用入口函數main和Winmain 而是調用C/C++運行實現并在連接時使用-entry:命令選項來設置的一個C/C++運行時的啟動函數。

該函數初始化C/C++運行庫,使我們可以調用malloc free之類的函數。 還確保在代碼執行前任何全局和靜態的C++對象都被正確構造。


連接器選擇正確的C/C++運行庫啟動函數。如果指定SUBSYSTEM:WINDOWS 連接器就會尋找WinMain

如果沒有找到WinMain則返回“unresolved external symbol”

如果選擇/SUBSYSTEM:CONSOLE 連接器默認會尋找main或者wmain 如果找不到則返回"unresolved external symbol"

可以自行關閉SUBSYSTEM連接器開關,讓連接器自動判斷(入口是main 還是winmain)。


可以從VC++自帶的運行庫的源代碼 crtexe.c文件中找到4個啟動函數的源代碼。所有啟動函數的用途簡單總結如下

1)獲取指向新進程的完整命令行的一個指針

2)獲取指向新進程的環境變量的一個指針

3)初始化C/C++運行庫的全局變量。如果保含了Stdlib.h 就可以訪問這些變量。

4)初始化C運行庫內存分配函數(malloc 和 calloc)和底層的I/O例程使用的堆

5)調用所有全局和靜態C++類對象的構造函數


完成以上所有初始化以后,C/C++啟動函數就會調用應用程序的入口函數。

如果我們定義了Unicode C/C++標準庫將執行以下代碼

STARTUPINFO StartupInfo;GetStartupInfo(&StartupInfo);int nMainRetVal = wWinMain((HINSTANCE)&__ImageBase, NULL, pszCommandLineUnicode,(STARTUPINFO.dwFlags & STARTF_USESHOWWINDOW)? STARTUPINFO.wShowWindow : SW_SHOWDEFAULT);
如果沒有定義Unicode則調用過程如下

STARTUPINFO StartupInfo;GetStartupInfo(&StartupInfo);int nMainRetVal = WinMain((HINSTANCE)&__ImageBase, NULL, pszCommandLineAnsi,(STARTUPINFO.dwFlags & STARTF_USESHOWWINDOW)? STARTUPINFO.wShowWindow : SW_SHOWDEFAULT);
_ImageBase是連接器定義的一個偽變量,表明可執行文件被映射到應用程序內存中的什么位置。


如果是CUI程序main函數的調用如下

int nMainRetVal = main(argc, argv, envp);

注意用Visual studio生存的默認main函數沒有第三個參數。可以自行增加表示環境變量

int main(int argc, char* argv[], char * env[]) {return 0; }
main函數返回以后,啟動函數調用C運行庫的exit,向其返回值nMainRetVal


exit函數執行以下任務

調用_onexit函數所注冊的一個回調函數。

調用所有全局和靜態C++類對象的析構函數

在DEBUG生成中,如果設置了_CRTDBG_LEAK_CHECK_DF標志, 會調用_CrtDumpMemoryLeaks函數來生成內存泄漏的報告。

調用操作系統的ExitProcess函數,向其傳入nMainRetVal,這會導致操作系統殺死進程,并設置他的退出代碼。


4.1.1 進程的實例句柄

加載到進程地址空間的每一個可執行文件或DLL文件都被賦予了一個獨一無二的實例句柄。可執行文件的實例句柄被當做WinMain函數的第一個參數hInstanceExe傳入。

在需要加載資源的函數中需要用到此句柄。例如

WINUSERAPI HICON WINAPI LoadIconW(_In_opt_ HINSTANCE hInstance,_In_ LPCWSTR lpIconName);
有的函數需要一個HMODULE類型的參數和HINSTANCE一致

WINBASEAPI _Success_(return != 0) _Ret_range_(1, nSize) DWORD WINAPI GetModuleFileNameW(_In_opt_ HMODULE hModule,_Out_writes_to_(nSize, ((return < nSize) ? (return + 1) : nSize)) LPWSTR lpFilename,_In_ DWORD nSize);
hInstanceExe參數實際是一個內存基地址,系統將可執行文件的映像加載到進程地址空間中的這個位置。

例如打開一個exe文件,并將他的內容加載到地址0x0040 0000 ?則WinMain的hInstanceExe參數值為 ?0x0040 0000.

基地址是由連接器決定的,使用/BASE:address 可以設置要將應用程序加載到哪個基地址。


為了知道一個可執行文件或DLL文件被加載到進程地址空間的什么位置,可以使用GetModuleHandle來返回一個句柄/基地址

WINBASEAPI _When_(lpModuleName == NULL, _Ret_notnull_) _When_(lpModuleName != NULL, _Ret_maybenull_) HMODULE WINAPI GetModuleHandleW(_In_opt_ LPCWSTR lpModuleName);
可以傳入NULL 就會獲得主調進程可執行文件的地址。


也可以通過連接器的偽變量__ImageBase查看



第二種方法是調用調用GetModuleHandleEx, 將GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS作為他的第一個參數,將當前函數的地址作為第二個參數,

最后一個參數是一個HMODULE的指針。GetModuleHandleEx會傳入函數所在DLL的基地址來填寫指針。

一個測試代碼

#include <tchar.h> #include <windows.h>extern "C" const IMAGE_DOS_HEADER __ImageBase;void DumpModule() {// Get the base address of the running application.// Can be different from the running module if this code is in a DLL.HMODULE hModule = GetModuleHandle(NULL);_tprintf(TEXT("with GetModuleHandle(NULL) = 0x%x\r\n"), hModule);// Use the pseudo-variable __ImageBase to get// the address of the current module hModule/hInstance._tprintf(TEXT("with __ImageBase = 0x%x\r\n"), (HINSTANCE)&__ImageBase);// Pass the address of the current method DumpModule// as parameter to GetModuleHandleEx to get the address// of the current module hModule/hInstance.hModule = NULL;GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,(PCTSTR)DumpModule,&hModule);_tprintf(TEXT("with GetModuleHandleEx = 0x%x\r\n"), hModule); }int main(int argc, char* argv[], char * env[]) {DumpModule();return 0; }
執行結果



4.1.2 進程的前一個實例的句柄

hPrevInstance 參數用于16位windows系統,在32位系統中不要使用此參數。


可以不在參數列表中寫參數變量,也可以通過宏

UNREFERENCED_PARAMETER(hPrevInstance); ?來讓編譯器不發出警告



4.1.3 ?進程的命令行

命令行至少會有一個參數,也就是可執行文件的文件名。 C運行庫會調用GetCommandLine來獲取完整的命令行,忽略可執行文件的名稱,然后將剩余的部分指針傳遞給WinMain的pszCmdLine參數


PTSTR GetCommandLine(); 獲取完整命令行的指針

CUI的命令行參數傳入的為 argc 和 argv 可以利用 Shell32.dll的導出函數CommandLineToArgv 將完整的命令行參數轉換為argc和argv

SHSTDAPI_(LPWSTR *) CommandLineToArgvW(_In_ LPCWSTR lpCmdLine, _Out_ int* pNumArgs);
改函數會在內部分配內存,需要釋放。或者等進程退出時由操作系統釋放(leak)

一個例子

int main(int argc, char* argv[], char * env[]) {int nNumArgs;PWSTR *ppArgv = CommandLineToArgvW(GetCommandLineW(), &nNumArgs);// Use the arguments...// Free the memory blockHeapFree(GetProcessHeap(), 0, ppArgv);return 0; }
在watch中查看



4.1.4 進程的環境變量

每個進程都有一個與他關聯的環境塊,這是在進程地址空間內分配的內存塊,其中包含字符串類似下面



前面是環境變量名,后面是環境變量值

使用GetEnvironmentStrings函數能獲得完整的環境塊,類似上面的字符串。

以下例子展示了如何在這樣的串中提取內容

void DumpEnvStrings() {PTSTR pEnvBlock = GetEnvironmentStrings();// Parse the block with the following format:// =::=::\// =...// var=value\0// ...// var=value\0\0// Note that some other strings might begin with '='.// Here is an example when the application is started from a network share.// [0] =::=::\// [1] =C:=C:\Windows\System32// [2] =ExitCode=00000000//TCHAR szName[MAX_PATH];TCHAR szValue[MAX_PATH];PTSTR pszCurrent = pEnvBlock;HRESULT hr = S_OK;PCTSTR pszPos = NULL;int current = 0;while (pszCurrent != NULL) {// Skip the meaningless strings like:// "=::=::\"if (*pszCurrent != TEXT('=')) {// Look for '=' separator.pszPos = _tcschr(pszCurrent, TEXT('='));// Point now to the first character of the value.pszPos++;// Copy the variable name.size_t cbNameLength = // Without the '='(size_t)pszPos - (size_t)pszCurrent - sizeof(TCHAR);hr = StringCbCopyN(szName, MAX_PATH, pszCurrent, cbNameLength);if (FAILED(hr)) {break;}// Copy the variable value with the last NULL character// and allow truncation because this is for UI only.hr = StringCchCopyN(szValue, MAX_PATH, pszPos, _tcslen(pszPos) + 1);if (SUCCEEDED(hr)) {_tprintf(TEXT("[%u] %s=%s\r\n"), current, szName, szValue);}else if (hr == STRSAFE_E_INSUFFICIENT_BUFFER) { // something wrong happened; check for truncation._tprintf(TEXT("[%u] %s=%s...\r\n"), current, szName, szValue);}else { // This should never occur._tprintf(TEXT("[%u] %s=???\r\n"), current, szName);break;} }else {_tprintf(TEXT("[%u] %s\r\n"), current, pszCurrent);}// Next variable please.current++;// Move to the end of the string.while (*pszCurrent != TEXT('\0'))pszCurrent++;pszCurrent++;// Check if it was not the last string.if (*pszCurrent == TEXT('\0'))break;}// Don't forget to free the memory.FreeEnvironmentStrings(pEnvBlock); }
運行結果



訪問環境變量的第二種方式是CUI程序專用。他通main函數入口的TCHAR *env[]參數來實現。env是一個字符串指針數組,每個指針都指向一個不同的環境變量。(名稱=值)的格式。

在指向自后一個環境變量字符串的指針后面會有一個NULL指針,表明這是數組末尾。

代碼如下

void DumpEnvVariables(PTSTR pEnvBlock[]) {int current = 0;PTSTR *pElement = (PTSTR*)pEnvBlock;PTSTR pCurrent = NULL;while (pElement != NULL) {pCurrent = (PTSTR)(*pElement);if (pCurrent == NULL) {pElement = NULL;}else {_tprintf(TEXT("[%u] %s\r\n"), current, pCurrent);current++;pElement++;} } }
運行結果



環境變量的值和名稱可以包含空格,只是靠等號分割。

例如 XYZ= Windows

ABC=Windows

兩個變量的值不同

XYZ =home

XYZ=Work

兩個變量的名稱不同

系統環境變量

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment

當前用戶環境變量

HKEY_CURRENT_USER\Environment


可以使用各種api來修改環境變量,但是用戶必須注銷。

也可以發送消息WM_SETTINGCHANGE

SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)TEXT("Environment"));


父進程可以通過CreateProcess來控制子進程繼承哪些環境變量。子進程繼承后環境塊屬于自己,可以自行增刪而不影響父進程。


使用環境變量GetEnvironmentVariable

WINBASEAPI _Success_(return != 0 && return < nSize) DWORD WINAPI GetEnvironmentVariableW(_In_opt_ LPCWSTR lpName,_Out_writes_to_opt_(nSize, return + 1) LPWSTR lpBuffer,_In_ DWORD nSize);
第三個參數傳入0的時候返回 保持環境變量值所需要字符個數,包含結尾的'\0'

一個測試代碼

void PrintEnvironmentVariable(PCTSTR pszVariableName) {PTSTR pszValue = NULL;// Get the size of the buffer that is required to store the valueDWORD dwResult = GetEnvironmentVariable(pszVariableName, NULL, 0);if (dwResult != 0) {// Allocate the buffer to store the environment variable valueDWORD size = dwResult * sizeof(TCHAR);pszValue = (PTSTR)malloc(size);GetEnvironmentVariable(pszVariableName, pszValue, size);_tprintf(TEXT("%s=%s\n"), pszVariableName, pszValue);free(pszValue);}else {_tprintf(TEXT("'%s'=<unknown value>\n"), pszVariableName);} }int _tmain(int argc, TCHAR* argv[], TCHAR * env[]) {PrintEnvironmentVariable(TEXT("PATH"));return 0; }
運行結果



可以將環境變量展開的函數 ExpandEnvironmentStrings

WINBASEAPI _Success_(return != 0 && return <= nSize) DWORD WINAPI ExpandEnvironmentStringsW(_In_ LPCWSTR lpSrc,_Out_writes_to_opt_(nSize, return) LPWSTR lpDst,_In_ DWORD nSize);
例子

int _tmain(int argc, TCHAR* argv[], TCHAR * env[]) {const TCHAR szSrc[] = TEXT("PATH='%PATH%'");DWORD chValue = ExpandEnvironmentStrings(szSrc, NULL, 0);if (chValue != 0){PTSTR pszBuffer = new TCHAR[chValue];chValue = ExpandEnvironmentStrings(szSrc, pszBuffer, chValue);_tprintf(TEXT("%s\r\n"), pszBuffer);delete[] pszBuffer;}return 0; }
SetEnvironmentVariable函數可以添加一個變量,刪除一個或者修改一個變量的值。

將pszName所表示的變量設置成pszValue的值。 設置為NULL則刪除


4.1.5 進程的關聯性

通常進程中的線程可以在主機的任何cpu上運行,然而,也可以強迫線程在可用cpu的一個子集上運行,這稱為處理器相關性(processor affinity)


4.1.6 進程的錯誤模式

每個進程都關聯了一組標志,這些標志的作用是讓進程知道進程如何響應嚴重錯誤,包括磁盤介質錯誤,未處理的異常,文件查找錯誤以及數據對齊錯誤。

進程可以調用SetErrorMode來通知系統如何處理這些錯誤

UINT SetErrorMode(UINT fuErrorMode);


錯誤模式會被子進程繼承,除非在CreatePcocess中設置CREATE_DEFAULT_ERROR_MODE


4.1.7 進程當前所在的驅動器和目錄

WINBASEAPI _Success_(return != 0 && return < nBufferLength) DWORD WINAPI GetCurrentDirectoryW(_In_ DWORD nBufferLength,_Out_writes_to_opt_(nBufferLength, return + 1) LPWSTR lpBuffer);WINBASEAPI BOOL WINAPI SetCurrentDirectoryW(_In_ LPCWSTR lpPathName);如果進程中的某一線程調用了SetCurrentDirectory修改了默認的進程目錄,則所有使用相對路徑處理的可能會導致意外的錯誤。

GetCurrentDirectory可以傳入 ?(0, NULL) 會返回需要字符的個數包括末尾的'\0'

或者直接傳入MAX_PATH 尺寸的buffer

4.1.8 進程的當前目錄

系統跟著記錄這進程的當前驅動器和目錄,但沒有記錄每個驅動器的當前目錄。可以通過環境變量來支持

=C:=C:\Utility\Bin

=D:=D:\Program Files

例如假定當前進程的目錄是C:\Utility\Bin ,而我們調用CreateFile來打開D:ReadMe.Txt 那么系統會查找環境變量=D:

由于=D:是存在的,系統將嘗試從D:\Program Files目錄打開ReadMe.txt文件,如果=D:不存在,系統會嘗試從D盤根目錄打開ReadMe.txt文件

Windows的文件函數從來不會添加或更改驅動器號。他們只是讀取這種變量。

_chdir也可以更改當前目錄其內部調用SetCurrentDirectory, _chdir還會調用SetEnvironmentVariable來添加或修改環境變量,從而使不同驅動器的當前目錄得以保留。


獲取每個驅動器的當前目錄

TCHAR szCurDir[MAX_PATH] = { 0 };DWORD cchLength = GetFullPathName(TEXT("D:"), MAX_PATH, szCurDir, NULL);_tprintf(TEXT("%s\n"), szCurDir);
驅動器號環境變量通常必須放在環境塊的開始處。

以下例子修改D盤的當前目錄并調用GetFullPathName獲取

//SetEnvironmentVariable(TEXT("=D:"), TEXT("D:\\Program Files (x86)"));//_chdir("D:\\Program Files (x86)");SetCurrentDirectory(TEXT("D:\\Program Files (x86)"));TCHAR szCurDir[MAX_PATH] = { 0 };DWORD cchLength = GetFullPathName(TEXT("d:"), MAX_PATH, szCurDir, NULL);_tprintf(TEXT("%s\n"), szCurDir);
!注意修改當前目錄是必須真實存在的目錄,否則會設置失敗。但SetEnvironmentVariable并不會檢查目錄是否存在。

而SetCurrentDirectory又會直接更改當前的驅動器和目錄。



如果一個父進程創建了一個希望傳遞給子進程的環境塊,子進程的環境塊不會自動繼承父進程的當前目錄。子進程的默認當前目錄為每個驅動器的根目錄。

如果希望子進程繼承父進程的當前目錄,父進程就必須在生成子進程之前,創建這些驅動器號的環境變量,并把它添加到環境塊中。


GetCurrentDirectory

SetCurrentDirectory

GetFullPathName 在多線程應用中應該特別小心,因為他們的值是進程相關的。在獲取過程中可能被其他線程所修改了。而且也應該避免使用相對路徑


4.1.9 系統版本

DWORD GetVersion(); //主版本號在低字節,次版本號在高字節


NOT_BUILD_WINDOWS_DEPRECATE WINBASEAPI __drv_preferredFunction("IsWindows*", "Deprecated. Use VerifyVersionInfo* or IsWindows* macros from VersionHelpers.") BOOL WINAPI GetVersionExW(_Inout_ LPOSVERSIONINFOW lpVersionInformation);此函數傳入一個OSVERSIONINFO的結構體并賦值給他。

typedef struct _OSVERSIONINFOW {DWORD dwOSVersionInfoSize;DWORD dwMajorVersion;DWORD dwMinorVersion;DWORD dwBuildNumber;DWORD dwPlatformId;WCHAR szCSDVersion[ 128 ]; // Maintenance string for PSS usage } OSVERSIONINFOW, *POSVERSIONINFOW, *LPOSVERSIONINFOW, RTL_OSVERSIONINFOW, *PRTL_OSVERSIONINFOW;
OSVERSIONINFOEX結構

typedef struct _OSVERSIONINFOEXW {DWORD dwOSVersionInfoSize;DWORD dwMajorVersion;DWORD dwMinorVersion;DWORD dwBuildNumber;DWORD dwPlatformId;WCHAR szCSDVersion[ 128 ]; // Maintenance string for PSS usageWORD wServicePackMajor;WORD wServicePackMinor;WORD wSuiteMask;BYTE wProductType;BYTE wReserved; } OSVERSIONINFOEXW, *POSVERSIONINFOEXW, *LPOSVERSIONINFOEXW, RTL_OSVERSIONINFOEXW, *PRTL_OSVERSIONINFOEXW;

參考MSDN 文檔

Getting the System Version

https://msdn.microsoft.com/en-gb/library/ms724429.aspx


VISTA以上的系統還提供了VerifyVersionInfo 能比較主機系統和應用程序需求的版本。


需要構建一個OSVERSIONINFOEX結構,并用宏(VER_SET_CONDITION)設置一個 ?dwConditionMask

就可以調用VerifyVersionInfo來判斷操作系統版本了。

以下例子展示了如何測試主機系統是不是Windows Vista


// Prepare the OSVERSIONINFOEX structure to indicate Windows Vista.OSVERSIONINFOEX osver = { 0 };osver.dwOSVersionInfoSize = sizeof(osver);osver.dwMajorVersion = 6;osver.dwMinorVersion = 0;osver.dwPlatformId = VER_PLATFORM_WIN32_NT;// Prepare the conditionmask.DWORDLONG dwlConditionMask = 0; // you must initialize this to 0.VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_EQUAL);VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_EQUAL);VER_SET_CONDITION(dwlConditionMask, VER_PLATFORMID, VER_EQUAL);// Perform the version test.if (VerifyVersionInfo(&osver, VER_MAJORVERSION | VER_MINORVERSION | VER_PLATFORMID,dwlConditionMask)) {_tprintf(TEXT("The host system is Windows Vista exactly.\n"));}else {_tprintf(TEXT("The host system is NOT Windows Vista.\n"));}
這里進行的是VER_EQUAL對比,筆者的測試機器是Win7 x64 sp1這里返回非VISTA系統。

The host system is NOT Windows Vista.


4.2 CreateProcess函數

使用CreateProcess來創建進程原型如下

WINBASEAPI BOOL WINAPI CreateProcessW(_In_opt_ LPCWSTR lpApplicationName,_Inout_opt_ LPWSTR lpCommandLine,_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,_In_ BOOL bInheritHandles,_In_ DWORD dwCreationFlags,_In_opt_ LPVOID lpEnvironment,_In_opt_ LPCWSTR lpCurrentDirectory,_In_ LPSTARTUPINFOW lpStartupInfo,_Out_ LPPROCESS_INFORMATION lpProcessInformation);
1)系統將創建一個進程內核對象,其初始引用計數器為1.進程內核對象并不是進程本身,而是操作系統用來管理該進程用的一個小型數據結構。

2)然后系統為進程創建一個虛擬地址空間,并將可執行文件(和所有必要的DLL)的代碼以及數據加載到進程的地址空間。

3)接著系統為進程的主線程創建一個線程內核對象(引用計數器為1)。線程的內核對象也是一個小型數據結構用來管理線程。這個主線程也會調用C/C++運行庫的啟動例程(由連接器設定的應用程序入口)最終調用WinMain 或者main函數。如果成功創建了新進程和主線程,CreateProcess返回TRUE。


CreateProcess在進程完全初始化好之前就返回TRUE。OS的進程Loader尚未嘗試定位所有DLL。如果一個DLL找不到或者無法正確初始化,進程就會終止。但因為CreateProcess返回TRUE,所以父進程并不會注意到任何初始化問題。


4.2.1 pszApplicationName和pszCommandLine參數

前者是新進程可執行文件的名字

后者是要傳遞給新進程的命令行參數。 CreateProcess實際會修改我們傳給他的命令行字符。但在CreateProcess返回前,他會將此字符還要成原來的形式。

這是很重要的,不能傳遞只讀內存區。否則可能會報錯



在調用之前將字符串常量復制到一個臨時變量則不會報錯。如下 。cc編譯器的/Gf開關可以消除重復的字符串,并判斷是否將那些字符串放在一個只讀的區域。注意/ZI開關,會關閉/Gf

STARTUPINFO si = { sizeof(si) };PROCESS_INFORMATION pi;TCHAR szCommandLine[] = TEXT("NOTEPAD");CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
另外在Vista系統上調用ANSI版本的CreateProcess不會發生違規,因為其內部創建了在堆上一個Unicode的副本(可讀可寫)

CreateProcess會解析pszCommandLine的第一個token 并假定標記是我們要運行的可執行文件名稱。并且默認擴展名是exe。CreateProcess按照以下順序搜索可執行文件

1)主調進程EXE文件所在的目錄

2)主調進程當前的目錄(GetCurrentDirectory)

3)Windows系統目錄,(GetSystemDirectory 通常是system32)

4)Windows目錄

5)PATH環境變量中列出的目錄

當然如果包含了絕對路徑,則會直接使用絕對路徑來查找可執行文件。


只要pszApplicationName設置為NULL,就會發生上述情況。

也可以在pszApplicationName只指定應用程序的名字,但是必須指定擴展名。CreateProcess假定當前目錄,除非文件名之前有指定目錄。并且CreateProcess不會在其他路徑查找文件。


4.2.2 psaProcess,psaThread和bInheritHandles參數

psaProcess和psaThread指定進程對象和線程對象的安全性。也可傳入NULL(默認的安全描述符。)

為psaProcess和psaThread參數使用SECURITY_ATTRIBUTES結構可以使其支持繼承(繼承父進程的句柄表)

一個繼承的例子

1.進程A創建了進程B 并且設置進程B的進程內核對象可繼承,主線程內核對象不可繼承。(兩個對象在進程B創建以后會更新A的句柄表,并且可以看到兩個內核對象的繼承標志)

2.由于創建進程B的時候CreateProcess的bInheritHandles設置為FALSE,所以進程B不會繼承進程A中的任何“可繼承”的內核對象。

3.接著進程A又創建了進程C,并設置CreateProcess的兩個SECURITY_ATTRIBUTES為NULL,表明進程C的進程對象和線程對象在進程A的句柄表中是不可被繼承的。

4.創建C的時候CreateProcess設置了bInheritHandles為TRUE,此時進程C將繼承進程A中的所有可繼承內核對象。這里有之前創建的進程B的進程內核對象,但不包含進程B的主線程內核對象。

代碼如下。

// Prepare a STARTUPINFO structure for spawning process.STARTUPINFO si = { sizeof(si) };SECURITY_ATTRIBUTES saProcess, saThread;PROCESS_INFORMATION piProcessB, piProcessC;TCHAR szPath[MAX_PATH];// Prepare to spawn Process B from Process A.// The handle identifying the new process// object should be inheritable.saProcess.nLength = sizeof(saProcess);saProcess.lpSecurityDescriptor = NULL;saProcess.bInheritHandle = TRUE;// The handle identifying the new thread// object should NOT be inheritable.saThread.nLength = sizeof(saThread);saThread.lpSecurityDescriptor = NULL;saThread.bInheritHandle = FALSE;// Spawn Process B._tcscpy_s(szPath, _countof(szPath), TEXT("ProcessB"));CreateProcess(NULL, szPath, &saProcess, &saThread,FALSE, 0, NULL, NULL, &si, &piProcessB);// The pi structure contains two handles// relative to Process A:// hProcess, which identifies Process B's process// object and is inheritable, and hTrhead, which identifies// Process B's primary thread object and is NOT inheritable.// Prepare to spawn Process C from Process A.// Since NULL is passed for the psaProcess and psaThread// parameters, the handle to Process's process and// primary thread objects default to "noninheritable."// If Process A were to spawn another process, this new// process would NOT inherit handles to Process's process// and thread object.// Because TRUE is passed for the bInheritHandles parameter,// Process C will inherit the handle that identifies Process// B's process object but will not inherit a handle to// Process B's primary thread object._tcscpy_s(szPath, _countof(szPath), TEXT("ProcessC"));CreateProcess(NULL, szPath, NULL, NULL,TRUE, 0, NULL, NULL, &si, &piProcessC);


4.2.3 fdwCreate參數

fdwCreate參數影響了創建新進程創建方式的flag


DEBUG_PROCESS 標志父進程希望調試子進程以及子進程將來創建的所有進程。(父進程現在的身份是調試器)


DEBUG_ONLY_THIS_PROCESS 最近創建的一個進程會通知父進程。而其創建的子進程將不會通知父進程。


這兩項通常用來寫調試器用。參考MSDN

http://www.cppblog.com/cxl82116/archive/2007/06/05/25535.html


CREATE_SUSPENDED 讓系統創建新進程的同時掛起子進程的主線程。這樣父進程就可以修改子進程地址空間中的內存,更改子進程主線程的優先級,或者在進程執行任何代碼以前將次進程添加到一個作業中。父進程修改好子進程,可以調用ResumeThread函數來允許子進程執行代碼。


DETACHED_PROCESS 標志阻止一個基于CUI的子進程訪問其父進程的控制臺窗口,并告訴系統它的輸出發送到一個新的控制臺窗口。

通常一個一個CUI進程創建了一個新的CUI子進程,那么默認情況下新進程也會使用父進程的控制臺。如果指定了此標志那么子進程必須調用AllocConsole來創建自己的控制臺。

一個例子

main.cpp

#include <tchar.h> #include <windows.h>int _tmain(int argc, TCHAR* argv[], TCHAR * env[]) {TCHAR szPath[MAX_PATH] = TEXT("SubProcess");PROCESS_INFORMATION piProcess;STARTUPINFO si = { sizeof(si) };_tprintf(TEXT("Start to Create Sub Process\n"));CreateProcess(NULL, szPath, NULL, NULL,TRUE, 0, NULL, NULL, &si, &piProcess);return 0; }
SubProcess.cpp

#include <tchar.h> #include <windows.h>int _tmain(int argc, TCHAR* argv[], TCHAR * env[]) {_tprintf(TEXT("Print from the Sub Process\n"));return 0; }
運行主進程main.exe 查看控制臺打印的字符



如果使用了DETACH_PROCESS子進程除了必須AllocConsole以后還需要重定向stdout

main.cpp

#include <tchar.h> #include <windows.h>int _tmain(int argc, TCHAR* argv[], TCHAR * env[]) {TCHAR szPath[MAX_PATH] = TEXT("SubProcess");PROCESS_INFORMATION piProcess;STARTUPINFO si = { sizeof(si) };_tprintf(TEXT("Start to Create Sub Process\n"));DWORD dwCreationFlag = DETACHED_PROCESS;CreateProcess(NULL, szPath, NULL, NULL,TRUE, dwCreationFlag, NULL, NULL, &si, &piProcess);return 0; }Subprocess.cpp

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <tchar.h> #include <windows.h>int _tmain(int argc, TCHAR* argv[], TCHAR * env[]) {if (AllocConsole()){freopen("CONOUT$", "w", stdout);_tprintf(TEXT("Print from the Sub Process\n"));}else{}system("pause");return 0; }
運行結果


CREATE_NEW_CONSOLE,會自動為子進程創建自己的CONSOLE不能和DETACHED_PROCESS一起使用。

例子

main.cpp

#include <tchar.h> #include <windows.h>int _tmain(int argc, TCHAR* argv[], TCHAR * env[]) {TCHAR szPath[MAX_PATH] = TEXT("SubProcess");PROCESS_INFORMATION piProcess;STARTUPINFO si = { sizeof(si) };_tprintf(TEXT("Start to Create Sub Process\n"));DWORD dwCreationFlag = CREATE_NEW_CONSOLE;CreateProcess(NULL, szPath, NULL, NULL,TRUE, dwCreationFlag, NULL, NULL, &si, &piProcess);return 0; }

Subprocess.cpp

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <tchar.h> #include <windows.h>int _tmain(int argc, TCHAR* argv[], TCHAR * env[]) {if (AllocConsole()){freopen("CONOUT$", "w", stdout);_tprintf(TEXT("Print from the Sub Process\n"));}else{_tprintf(TEXT("Sub Process has owned a console!\n"));_tprintf(TEXT("Print from the Sub Process\n"));}system("pause");return 0; }
運行結果



CREATE_NO_WINDOW ?標志應用程序不要為子進程創建任何控制臺,用于執行沒有用戶界面的程序。


CREATE_NEW_PROCESS_GROUP 對CUI程序而言的。用于創建新進程組。在同一組中的所有進程,當按下Ctrl+C中斷當前操作時,系統會向這個組的進程發出通知。


CREATE_DEFAULT_ERROR_MODE 新進程不會繼承父進程的錯誤模式


CREATE_SEPARATE_WOW_VDM 表明創建一個16位windows程序(Virtual DOS Machine) 默認創建的16位進程會共享一個VDM(因為創建一個會消耗較多的資源)


CREATE_SHARED_WOW_VDM 在運行16位應用程序才有用, 可以修改注冊表 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\WOW\

DefaultSeparateVDM 設置為yes

可以調用IsWow64Process 來判斷檢測進程句柄是不是32位進程(在64bit系統下)


CREATE_UNICODE_ENVIRONMENT 告訴系統子進程的環境塊包含UNICODE字符,進程環境塊默認包含ANSI字符


CREATE_FORCEDOS 強制系統運行嵌入一個在16位OS中的MS-DOS程序


CREATE_BREAKAWAY_FROM_JOB?允許一個作業中的進程生成一個和作業無關的進程


EXTENDED_STARTUPINFO_PRESENT ?告知操作系統傳給psiStartInfo參數的是STARTUPINFOEX結構


fdwCreate還運行給進程分配優先級。但是大部分應用沒這個必要。

IDLE_PRIORITY_CLASS

低于標準BELOW_NORMAL_PRIORITY_CLASS

標準NORMAL_PRIORITY_CLASS

高于標準ABOVE_NORMAL_PRIORITY_CLASS

HIGH_PRIORITY_CLASS

實時REALTIME_PRIORITY_CLASS


4.2.4 pvEnvironment參數

pvEnvironment參數指向一塊內存,其中包含新進程要使用的環境字符串。大多數時候這個參數傳入NULL,

子進程會繼承其父進程使用的一組環境字符串。

還可以通過GetEnvironmentString函數獲得父進程的環境塊的串地址,傳遞給CreateProcess用于創建子進程。 該功能和傳入NULL的行為一致。

不使用環境字符串的時候調用FreeEnvironmentStrings釋放其空間。


4.2.5 pszCurDir參數

允許父進程設置當前進程的當前驅動器和目錄。如果為NULL則默認和主進程一致。如果非NULL

pszCurDir必須執行一個以'\0'結尾的字符串,其中包含了工作驅動器和目錄。


4.2.6 psiStartInfo參數

指向一個STARTUPINFO 或者 STARTUPINFOEX的結構體

typedef struct _STARTUPINFOW {DWORD cb;LPWSTR lpReserved;LPWSTR lpDesktop;LPWSTR lpTitle;DWORD dwX;DWORD dwY;DWORD dwXSize;DWORD dwYSize;DWORD dwXCountChars;DWORD dwYCountChars;DWORD dwFillAttribute;DWORD dwFlags;WORD wShowWindow;WORD cbReserved2;LPBYTE lpReserved2;HANDLE hStdInput;HANDLE hStdOutput;HANDLE hStdError; } STARTUPINFOW, *LPSTARTUPINFOW; typedef struct _STARTUPINFOEXW {STARTUPINFOW StartupInfo;LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList; } STARTUPINFOEXW, *LPSTARTUPINFOEXW;


默認值只要生成次結構

STARTUPINFO si = {sizeof(si)};

CreateProcess(..., &si, ...);

如果沒有把結構內容清0,則成員將包含線程棧上的垃圾數據。會造成CreateProcess 未知運行結果。






關于dwFlags成員,包含一組標志用于修改子進程的創建方式。



另外還有兩個標志

STARTF_FORCEONFEEDBACKCreateProcess會監控進程的初始化過程,并根據結果更改光標的形狀。一旦子進程調用了GetMessage(表明初始化完畢)CreateProcess則停止監控。

START_FORCEOFFFEEDBACK 改為等待圖標

在啟動進程時候控制鼠標指針。CreateProcess臨時將系統的護鏢指針改為 加載等待 圖片


wShowWindow會作為參數傳遞給子進程WinMain函數中的nCmdShow參數。 ?可能值是SW_SHOWNORMAL, SW_SHOWMINNOACTIVE 和 SW_SHOWDEFAULT

也可以在應用程序的快捷方式中修改此值。



Microsoft為了避免創建多個CreateProcess版本,僅僅是通過擴展STARTUPINFOEX結果來升級新的feature。

STARTUPIINFOEX保護一個字段 lpAttributeList用于傳遞額外屬性。

PROC_THREAD_ATTRIBUTE_HANDLE_LIST 該屬性告知CreateProcess進程究竟應該繼承哪一些內核對象的句柄。這些對象句柄必須在創建時指定可繼承(SECURIT_ATTRIBUTES結構中保護設置為TRUE的bInheritHandle字段)。

使用這個屬性,子進程只能繼承一組選定的句柄,而不是繼承所有可繼承的句柄。

Note??if you use this attribute, pass in a value of TRUE for the?bInheritHandles?parameter of the?CreateProcessfunction.


PROC_THREAD_ATTRIBUTE_PARENT_PROCESS? 自行指定進程成為當前要創建的進程的父進程。 但是不改變調試關系。調用CreateProcess的進程仍然能收到調試通知。

調用以下函數兩次,才能創建一個空白的屬性列表。

WINBASEAPI _Success_(return != FALSE) BOOL WINAPI InitializeProcThreadAttributeList(_Out_writes_bytes_to_opt_(*lpSize, *lpSize) LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,_In_ DWORD dwAttributeCount,_Reserved_ DWORD dwFlags,_When_(lpAttributeList == nullptr, _Out_) _When_(lpAttributeList != nullptr, _Inout_) PSIZE_T lpSize);dwFlags參數是保留的始終傳入0.第一次調用是獲得保存屬性的內存塊大小

SIZE_T cbAttributeListSize = 0;BOOL bReturn = InitializeProcThreadAttributeList(NULL, 1, 0, &cbAttributeListSize);//bReturn is FALSE but GetLastError() return ERROR_INSUFFICIENT_BUFFERPPROC_THREAD_ATTRIBUTE_LIST pAttributeList =(PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, cbAttributeListSize);bReturn = InitializeProcThreadAttributeList(pAttributeList, 1, 0, &cbAttributeListSize);
接下來可以根據需要用下面函數添加鍵值對。

WINBASEAPI BOOL WINAPI UpdateProcThreadAttribute(_Inout_ LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,_In_ DWORD dwFlags,_In_ DWORD_PTR Attribute,_In_reads_bytes_opt_(cbSize) PVOID lpValue,_In_ SIZE_T cbSize,_Out_writes_bytes_opt_(cbSize) PVOID lpPreviousValue,_In_opt_ PSIZE_T lpReturnSize);
lpAttributeList是之前初始化的attribute列表, 他接受 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS ?或者 ? PROC_THREAD_ATTRIBUTE_HANDLE_LIST的值。

如果是前者pValue 必須執行一個變量的地址,包含了新的父進程句柄,而cbSize應該使用sizeof(HANDLE)作為其值。

如果是后者pValue必須執行一個數組的起始地址,包含了運行進程訪問的,可繼承的內核對象句柄, cbSize = sizeof(HANDLE)*句柄數。

dwFlags, PreviousValue和pReturnSize是保留參數,必須設定為0,NULL和NULL

一個初始化Attributelist 并使用STARTUPINFOEX創建進程的例子

創建了一個mutex 并設定AttributeList讓子進程繼承此Mutex

// 1.Create a mutex.SECURITY_ATTRIBUTES sa;sa.nLength = sizeof(sa);sa.lpSecurityDescriptor = NULL;sa.bInheritHandle = TRUE; // Make the returned handle inheritable. HANDLE hMutex = CreateMutex(&sa, FALSE, NULL);// 2. Create the attributelist and fill the attributeSIZE_T cbAttributeListSize = 0;BOOL bReturn = InitializeProcThreadAttributeList(NULL, 1, 0, &cbAttributeListSize);//bReturn is FALSE but GetLastError() return ERROR_INSUFFICIENT_BUFFERPPROC_THREAD_ATTRIBUTE_LIST pAttributeList =(PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, cbAttributeListSize);bReturn = InitializeProcThreadAttributeList(pAttributeList, 1, 0, &cbAttributeListSize);UpdateProcThreadAttribute(pAttributeList,0,PROC_THREAD_ATTRIBUTE_HANDLE_LIST,(PVOID)&(hMutex), // pointer to the inheritable mutex handle.sizeof(HANDLE),NULL, NULL);// 3. prepare the attribute for the process.PROCESS_INFORMATION processInfo = { 0 };STARTUPINFOEX esi = { sizeof(STARTUPINFOEX) };esi.lpAttributeList = pAttributeList;TCHAR szPath[] = TEXT("SubProcess");DWORD dwCreationFlag = EXTENDED_STARTUPINFO_PRESENT;// you must set the bInheritable = TRUE ,otherwise the createProcess will be failed.// if you just specified the parent handle. it would be OK.bReturn = CreateProcess(NULL, szPath,NULL, NULL, TRUE,dwCreationFlag, NULL, NULL,(LPSTARTUPINFO)&esi, &processInfo); //...do something.// free the attributelistDeleteProcThreadAttributeList(pAttributeList);
最后應用程序可以調用以下函數獲得 STARTUPINFO結構的一個副本。此結構是由父進程初始化的。子進程可以檢查這個結構并根據成員值來修改其行為。

VOID GetStartupInfo(LPSTARTUPINFO pStartupInfo);

StartupInfo中的一些句柄值在父進程中創建的,其地址是父進程的地址空間。


4.2.7 ppiProcInfo參數

指向一個PROCESS_INFORMATION的結構

typedef struct _PROCESS_INFORMATION {HANDLE hProcess;HANDLE hThread;DWORD dwProcessId;DWORD dwThreadId; } PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
CreateProcess返回的與當前進程相關的 進程內核對象句柄 和 線程內核對象句柄。引用計數器為1. 而CreateProcess內部打開這些對象引用計數器又會加1

因此若要讓子進程和子進程的主線程在父進程退出之前關閉,子進程必須先終止(引用計數器-1)必須在父進程中調用CloseHandle將其引用計數器再-1.?

釋放子進程的主線程內核對象同理。

注意:關閉句柄并不會真正關閉子進程和子線程(只改變其引用計數器)!


進程會被操作系統分配一個獨一無二的ProcessID, PID=0 是System Idle Process 其線程數等于CPU數量


PID主要供一些系統工具辨識進程使用。

PID由操作系統管理會被回收分配和重用。所以要特備注意。

GetCurrentProcessId獲得PID

GetCurrentThreadId 獲得當前正在運行線程的ID

根據指定句柄獲得PID ?GetProcessId

根據指定句柄獲得線程ID ?GetThreadId

獲得當前線程所在進程的PID GetProcessIdOfThread


ToolHelp函數允許進程通過PROCESSENTRY32 結構查詢其父進程的PID。

但是由于PID具有實效性,可能不準確。最好使用內核對象,窗口句柄等來定位一個進程的父進程。

如果一定要使用PID,唯一的辦法就是保證進程或線程的內核對象不被銷毀。(例如將父進程的內核對象繼承給子進程) 在不需要使用以后調用CloseHandle


4.3 終止進程

4種方式可以終止進程:

1)主線程的入口點函數返回(強烈推薦)

2)進程中有一個線程調用ExitProcess函數(要避免這種方式)

3)另一個進程中的線程調用TerminateProcess函數(要避免這種方式)

4)進程中所有線程都“自然死亡”(這種情況幾乎不會發生)


4.3.1 主進程的入口點函數返回

應該確保只有在主線程的入口點函數返回之后,這個應用程序才終止。這樣主線程的所以資源才能被正確清理。確保以下操作會被執行

1)該線程創建的任何C++對象都將由這些對象的析構函數正確銷毀。

2)操作系統將正確釋放線程棧使用的內存

3)系統將進程的退出代碼(在進程的內核對象中維護)設為入口點函數的返回值

4)系統遞減內核對象的使用計數器


4.3.2 ExitProcess函數

WINBASEAPI DECLSPEC_NORETURN VOID WINAPI ExitProcess(_In_ UINT uExitCode);
該函數將終止進程,并忽略其后的所有代碼。

C運行庫在Main函數返回后將清理所有C運行時資源,最后調用ExitProcess

crt0dat.c中最終調用ExitProcess退出

void __cdecl __crtExitProcess (int status) { #if defined (_CRT_APP) && !defined (_KERNELX)(status);__crtExitProcessWinRT(); #else /* defined (_CRT_APP) && !defined (_KERNELX) */#if !defined (_KERNELX)__crtCorExitProcess(status); #endif /* !defined (_KERNELX) *//** Either mscoree.dll isn't loaded,* or CorExitProcess isn't exported from mscoree.dll,* or CorExitProcess returned (should never happen).* Just call ExitProcess.*/ExitProcess(status); #endif /* defined (_CRT_APP) && !defined (_KERNELX) */ }
SDK中指出,一個進程在其所有線程都終止以后才會終止。 C/C++運行庫采用了一個不同的策略:

不管進程中是否還有其他線程在運行,只要英語程序的主線程從他的入口函數返回,C/C++就會調用ExitProcess來終止進程。

如果在入口函數中調用的是ExitThread那么只會終止主線程,其他線程繼續運行,進程就不會終止。


注意:ExirProcess或ExitThread會導致進程或線程直接終止運行再也不會返回當前函數的調用。對操作系統而言這樣沒什么問題。(進程或線程的資源會被清理)

但C/C++應用程序應該避免這樣調用,因為C/C++運行庫也許不能執行正確的清理

看一下例子

#include <tchar.h> #include <windows.h> #include <stdio.h>class CSomeObj{ public:CSomeObj() { printf("Constructor \n"); }~CSomeObj() { printf("Destructor \n"); } };CSomeObj g_GlobalObj;int _tmain(int argc, TCHAR* argv[], TCHAR * env[]) {CSomeObj LocalObj;ExitProcess(0);return 0; }

全局對象和局部對象都沒有調用析構函數,C++對象沒有被正確析構,因為ExitProcess造成進程當場終止運行。C/C++運行庫沒有機會執行清理工作。

只需要主線程的入口函數返回,C/C++運行庫就能執行其清理工作。

所以不要顯示調用ExitProcess和ExitThread


4.3.3 TerminateProcess函數

調用TerminateProcess也可以終止一個進程

WINBASEAPI BOOL WINAPI TerminateProcess(_In_ HANDLE hProcess,_In_ UINT uExitCode);
任何線程都可以調用TerminateProcess來終止另一個進程或者自己的進程。 hProcess指定了要終止的進程的句柄。其退出代碼的值就是傳給uExitCode的值。

被終止的進程得不到自己要被終止的通知--應用程序不能正確清理,也不能阻止它自己被強行終止。例如這種情況下進程不能將它在內存中的信息flush到磁盤上。

操作系統在進程終止以后會進行徹底清理,保證不會泄露任何操作系統資源。在進程終止后絕對不會泄漏任何東西

TerminateProcess函數是異步的,并不等到進程完全終止了才返回。為了確定進程是否終止需要使用WaitForSingleObject


4.3.4 當進程中的所有線程終止時

當進程的所有線程終止時,操作系統認為沒有任何理由再保持進程的地址空間。并會終止這個進程。進程的退出代碼會被設置為最后一個終止的哪個線程的退出代碼。


4.3.5 當進程終止運行時

一個進程終止時,系統回依次執行以下操作。

1) 終止進程中駐留的任何線程

2)釋放進程分配的所有用戶對象和GDI對象,關閉所有內核對象(如果沒有其他進程打開這些內核對象的句柄,那么他們會被銷毀。否則引用計數器-1)

3)進程的退出代碼從STILL_ACTIVE變為傳給ExitProcess或TerminateProcess函數的代碼

4)進程內核對象的狀態變為已觸發狀態。

5)進程內核對象引用計數器-1


當父進程忘記關閉子進程句柄時候,子進程即使結束。其進程內核對象句柄仍然不會被銷毀。此時可以獲得一些統計信息,例如GetExitCodeProcess來獲得一個已經終止的進程的退出代碼。

WINBASEAPI BOOL WINAPI GetExitCodeProcess(_In_ HANDLE hProcess,_Out_ LPDWORD lpExitCode);
如果被調用的進程未終止,lpExitCode會返回 STILL_ACTIVE(0x103)


重點,如果對進程的統計數據不再感興趣應該調用CloseHandle來遞減內核對象的使用計數器,并釋放它。


4.4 子進程

為了執行復雜的任務而不讓當前線程一直等待,可以創建一個新的進程來完成工作。父進程和子進程之間可以進行一些數據共享。

(DDE dynamic Data Exchange) OLE, 管道, 郵件槽等。共享數據最方便的方式就是使用內存映像文件(CreateFileMapping)


以阻塞方式運行子進程

以下代碼創建了一個子進程,并等待子進程完成相應的工作正常結束以后,再繼續當前線程的執行。

PROCESS_INFORMATION pi;DWORD dwExitCode;//Spawn the child process.BOOL fSuccess = CreateProcess(..., &pi);if (fSuccess) {// Close the thread handle as soon as it is no longer needed!CloseHandle(pi.hThread);// Suspend our execution until the child has terminated.WaitForSingleObject(pi.hProcess, INFINITE);// The child process terminated; get its exit code.GetExitCodeProcess(pi.hProcess, &dwExitCode);// Close the process handle as soon as it is no longer needed.CloseHandle(pi.hProcess);}
上面的例子使用了WaitForSingleObject函數,該函數會一直等待直到hObject參數所表示的對象變為已觸發。 進程對象在終止的時候會變為已觸發。

WaitForSingleObject將會掛起當前線程,直到子進程終止。


一個良好的編程習慣:在不需要使用子進程的相關內核對象應該立即CloseHandle。 否則假定子進程生成了另外一個新的子進程,而自己的主線程已經退出。由于調用CreateProcess的當前進程未釋放子進程的進程對象,因此自進程的內核對象不會被操作系統釋放。


運行獨立的子進程

大多數時候應用程序將另一個進程作為獨立的進程(detached process)來啟動。這就意味著一旦子進程創建,父進程就不再與其通信,或者不必等他它完成工作之后再繼續自己的工作(當前進程不必掛起等待)。這時候只需要調用CloseHandle關閉子進程的進程句柄和主線程句柄。

一個例子

PROCESS_INFORMATION pi;//Spawn the child process.BOOL fSuccess = CreateProcess(..., &pi);if (fSuccess) {// Allow the system to destroy the process & thread kernel// objects as soon as the child process terminates.// Close the thread handle as soon as it is no longer needed!CloseHandle(pi.hThread);CloseHandle(pi.hProcess);}

4.5 管理員以標準用戶權限運行時。

每個用戶登錄windows以后會有一個安全令牌(Security token)其后該用戶啟動的進程都擁有此令牌的權限。

由于許多windows用戶使用Administrator登錄,此用戶的權限太高(可以修改系統文件)可能導致操作系統以高權限執行而已軟件而破壞操作系統。


在Vista以上用戶以Administrator登錄出來具有高特權的安全令牌,還會具有一個篩選過的令牌(filtered token) 只有普通的標準用戶權限(standard user)。

之后從第一個進程開始所有啟動的進程都和篩選過的令牌相關聯。因此默認運行的應用程序將無法訪問受限資源。


可以在進程啟動之前(進程邊界, 進程已經啟動以后會與篩選過的令牌相關聯并且運行時不可修改)讓操作系統提示用戶取得提升權限的同意。也可以在快捷菜單中選擇以管理員身份運行。


可以在自己的應用程序上顯示一個盾牌圖,會彈出一個權限提升對話框。


Windows只允許進程邊界上進行權限提升(未啟動以前)。 可以用一個未提升權限的進程來生成另一個提升了權限的進程,后者將包含一個com服務器,這個新進程將保持活動狀態。這樣未提升權限的進程就可以向已經提升權限的進程發出IPC調用,而不必啟動一個新的實例再終止它自身。


4.5.1 自動提升進程的權限

windows每次啟動應用程序都將自動彈框詢問并提升應用程序的權限(比如安裝程序)

在可執行文件中嵌入資源(RT_MANIFEST) 系統會檢查<trustInfo>段

一個例子 參考blog :?http://blog.csdn.net/sesiria/article/details/51939231

<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"> <security> <requestedPrivileges> <requestedExecutionLevel level="requireAdministrator"/> </requestedPrivileges> </security> </trustInfo>
level 屬性可能有3個不同的值



也可以保存成一個 ?應用程序.exe.manifest的文件的外部清單

如果exe本身內嵌了一個清單,則外部清單會被忽略。

也可以自行設定應用程序默認以管理員權限運行



4.5.2 手動提升進程的權限

CreateProcess函數沒有提供提升應用程序權限的功能。 可以調用ShellExecuteEx函數

SHSTDAPI_(BOOL) ShellExecuteExW(_Inout_ SHELLEXECUTEINFOW *pExecInfo);typedef struct _SHELLEXECUTEINFOW {DWORD cbSize; // in, required, sizeof of this structureULONG fMask; // in, SEE_MASK_XXX valuesHWND hwnd; // in, optionalLPCWSTR lpVerb; // in, optional when unspecified the default verb is choosenLPCWSTR lpFile; // in, either this value or lpIDList must be specifiedLPCWSTR lpParameters; // in, optionalLPCWSTR lpDirectory; // in, optionalint nShow; // in, requiredHINSTANCE hInstApp; // out when SEE_MASK_NOCLOSEPROCESS is specifiedvoid *lpIDList; // in, valid when SEE_MASK_IDLIST is specified, PCIDLIST_ABSOLUTE, for use with SEE_MASK_IDLIST & SEE_MASK_INVOKEIDLISTLPCWSTR lpClass; // in, valid when SEE_MASK_CLASSNAME is specifiedHKEY hkeyClass; // in, valid when SEE_MASK_CLASSKEY is specifiedDWORD dwHotKey; // in, valid when SEE_MASK_HOTKEY is specifiedunion { HANDLE hIcon; // not used #if (NTDDI_VERSION >= NTDDI_WIN2K)HANDLE hMonitor; // in, valid when SEE_MASK_HMONITOR specified #endif // (NTDDI_VERSION >= NTDDI_WIN2K)} DUMMYUNIONNAME; HANDLE hProcess; // out, valid when SEE_MASK_NOCLOSEPROCESS specified } SHELLEXECUTEINFOW, *LPSHELLEXECUTEINFOW;
SHELLEXECUTEINFO結構中 lpVerb 和lpFile 為主要關注的參數。前者設定為 “runas” 后者包含可執行文件的路徑。

代碼如下所示

// Initialize the structure.SHELLEXECUTEINFO sei = { sizeof(SHELLEXECUTEINFO) };// Ask for privileges elevation.sei.lpVerb = TEXT("runas");// Create a Command Prompt from which you will be able to start// other elevated applications.sei.lpFile = TEXT("cmd.exe");// Don't forget this parameter; otherwise, the window will e hidden.sei.nShow = SW_SHOWNORMAL;if (!ShellExecuteEx(&sei)) {DWORD dwStatus = GetLastError();if (dwStatus == ERROR_CANCELLED) {// the user refused to allow privileges elevation.}else if (dwStatus == ERROR_FILE_NOT_FOUND) {// The file defined by lpFile was not found and// an error message popped up.}}


但一個進程提升權限以后,每次他再調用 CreateProcess來生成另一個子進程都會獲得和它的父進程一樣的提升后的權限。

某些任務需要高權限的時候應該在啟動該任務的界面元素盤顯示一個盾牌圖標。

由于任務管理器由另一個進程或者一個進程中的COM服務來執行,所以應該需要將需要管理員權限的所有任務集中到另一個應用程序中,并通過ShellExecuteEx(lpVerb 傳送runas)來提升他的權限。 具體要執行的特權操作采用命令行參數傳遞。(SHELLEXECUTEINFO的lpParameters字段)


4.5.3 何為權限上下文

如何判斷應用程序是以管理與身份運行還是以篩選令牌權限運行的

以下代碼的函數GetProcessElevation返回一個提升類型并指出進程是否以管理員身份運行的BOOL值。

BOOL GetProcessElevation(TOKEN_ELEVATION_TYPE * pElevationType, BOOL * pIsAdmin) {HANDLE hToken = NULL;DWORD dwSize;// Get current process tokenif (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))return FALSE;BOOL bResult = FALSE;// Retrive elevation type informationif (GetTokenInformation(hToken, TokenElevationType,pElevationType, sizeof(TOKEN_ELEVATION_TYPE), &dwSize)) {// Create the SID corresponding to the Administrators groupBYTE adminSID[SECURITY_MAX_SID_SIZE];dwSize = sizeof(adminSID);CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL,&adminSID, &dwSize);if (*pElevationType == TokenElevationTypeLimited) {// Get handle to linked token (will have one if we are lua)HANDLE hUnfilteredToken = NULL;GetTokenInformation(hToken, TokenLinkedToken, (VOID*)&hUnfilteredToken, sizeof(HANDLE), &dwSize);// Check if this original token contains admin SIDif (CheckTokenMembership(hUnfilteredToken, &adminSID, pIsAdmin)){bResult = TRUE;}// Don't forget to close the unfiltered tokenCloseHandle(hUnfilteredToken);}else {*pIsAdmin = IsUserAnAdmin();bResult = TRUE;}}// Don't forget to close the process tokenCloseHandle(hToken);return bResult; }
TOKEN_ELEVATION_TYPE枚舉類型定義


首先獲取這些值并判斷使用的令牌是否被篩選過。

接下來判斷用戶是否是管理員。

如果沒有被篩選過,直接調用IsUserAnAdmin()返回

如果被篩選過,首先獲取未篩選的令牌把TokenLinkedToken傳給GetTokenInformation)然后判斷其是否包含管理員的Sid。(借助于CreateWellKnownSid和CheckTokenMembership)

該函數可以用來獲取當前進程的令牌和權限用以控制是否顯示盾牌圖標。


4.5.4 枚舉系統正在運行的進程

Windows在注冊表中維護一個性能數據庫,包含海量信息。例如RegQueryValueEx把注冊表的根目錄設為KEY_PERFORMANCE_DATA

但是該數據庫在Win95和98上不可用,?

沒有自己的函數,使用注冊表函數

數據庫信息布局非常復雜


為了更方便訪問此數據庫借助Performance Data Helper(PDH.dll)

在(Win9X中 使用ToolHelp API的Process32First 和Process32Next函數)

在WinNT中使用EnumProcess函數。

在Win2000開始Tool Help函數支持2K以上的NT內核系統。


4.5.5 Process Information示例程序 (該示例涉及較多的后續章節的知識 還用到了WinDbg)

運行結果


除了安全描述符(SID)和訪問控制列表(access control list, ACL). 系統還通過在系統訪問控制列表(SACL)中新增一個名為強制標簽的訪問控制項(Access control entry)來為受保護的資源分配一個所謂的完整性級別(integrity level)

信任級別


利用Process Explorer工具可以查看應用程序的完整性級別(integrity level)。請選擇Select Columns -> Process Image->Integrity Level



windows還利用完整性級別來拒絕低完整性級別的進程訪問高完整性級別的用戶界面。這個機制成為用戶界面特權隔離(User Interface Privilege Isolation,UIPI)

為了防止完整性低的進程獲取另一個完整性高的進程的信息或進行虛假輸入,Windows阻止完整性低的進程通過PostMessage/SendMessage向完整性高的進程發送Windows消息. 也阻止通過掛鉤子來攔截完整性較高的進程的windows消息。

可以利用WindowDump來做實驗或者spy++


總結

以上是生活随笔為你收集整理的《Windows核心编程》读书笔记四 进程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。