第二部分,工作机制
第4章 进程
概述:进程是应用程序的一个运行实例,由内核对象和虚拟地址空间组成。Windows通过进程来隔离应用程序,每个进程拥有独立的地址空间和资源。理解进程的创建、终止以及进程间的差异是Windows系统编程的基础。
什么是进程
进程是应用程序的一个运行实例,它由两部分组成:一个是进程内核对象(操作系统用来管理进程的数据结构),另一个是虚拟地址空间(包含可执行代码、数据、DLL和动态分配的内存)。进程本身并不执行代码,它只是线程的容器,线程才是CPU调度的基本单位。每个进程至少有一个主线程,可以拥有多个线程。进程为线程提供资源上下文,包括虚拟地址空间、句柄表、环境变量和当前目录等。
HANDLE GetCurrentProcess();
HANDLE GetCurrentProcessId();
DWORD GetCurrentProcessId();
HANDLE OpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId );
typedef struct _PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; } PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
typedef struct _STARTUPINFO { DWORD cb; LPTSTR lpReserved; LPTSTR lpDesktop; LPTSTR 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; } STARTUPINFO, *LPSTARTUPINFO;
|
创建进程:CreateProcess函数
CreateProcess是Windows中创建新进程的核心函数。它执行时会依次完成以下操作:创建进程内核对象、创建虚拟地址空间、将可执行文件和DLL映射到地址空间、创建主线程内核对象、启动C/C++运行时启动代码,最终调用主线程的入口点函数。CreateProcess有10个参数,提供了精细的控制能力,包括安全性、继承性、创建标志、环境变量、工作目录和启动信息等。
BOOL CreateProcess( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation );
BOOL CreateNewProcess(LPCTSTR pszAppName, LPCTSTR pszCmdLine) { STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; BOOL bSuccess = CreateProcess( pszAppName, pszCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ); if (bSuccess) { CloseHandle(pi.hThread); CloseHandle(pi.hProcess); return TRUE; } DWORD dwError = GetLastError(); return FALSE; }
void RunNotepad() { TCHAR szCmdLine[] = TEXT("notepad.exe C:\\test.txt"); CreateNewProcess(NULL, szCmdLine); }
|
进程创建标志和优先级类
dwCreationFlags参数控制进程的创建方式和初始状态。可以组合使用多个标志(用OR运算),还可以指定优先级类。常用的创建标志包括:CREATE_SUSPENDED(挂起主线程,允许父进程修改子进程内存后再运行)、CREATE_NEW_CONSOLE(为新进程创建新控制台窗口)、DETACHED_PROCESS(阻止CUI进程访问父进程控制台)、CREATE_UNICODE_ENVIRONMENT(环境块使用Unicode)等。优先级类决定了进程内线程的基准调度优先级。
#define DEBUG_PROCESS 0x00000001 #define DEBUG_ONLY_THIS_PROCESS 0x00000002 #define CREATE_SUSPENDED 0x00000004 #define DETACHED_PROCESS 0x00000008 #define CREATE_NEW_CONSOLE 0x00000010 #define CREATE_NEW_PROCESS_GROUP 0x00000200 #define CREATE_UNICODE_ENVIRONMENT 0x00000400 #define CREATE_SEPARATE_WOW_VDM 0x00000800 #define CREATE_SHARED_WOW_VDM 0x00001000 #define CREATE_FORCEDOS 0x00002000 #define CREATE_DEFAULT_ERROR_MODE 0x04000000 #define CREATE_NO_WINDOW 0x08000000
#define IDLE_PRIORITY_CLASS 0x00000040 #define BELOW_NORMAL_PRIORITY_CLASS 0x00004000 #define NORMAL_PRIORITY_CLASS 0x00000020 #define ABOVE_NORMAL_PRIORITY_CLASS 0x00008000 #define HIGH_PRIORITY_CLASS 0x00000080 #define REALTIME_PRIORITY_CLASS 0x00000100
BOOL CreateSuspendedProcess(LPCTSTR pszCmdLine, LPPROCESS_INFORMATION ppi) { STARTUPINFO si = { sizeof(si) }; BOOL bSuccess = CreateProcess( NULL, pszCmdLine, NULL, NULL, FALSE, CREATE_SUSPENDED | NORMAL_PRIORITY_CLASS, NULL, NULL, &si, ppi ); if (bSuccess) { ResumeThread(ppi->hThread); } return bSuccess; }
void CreateProcessWithNewConsole() { STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; TCHAR szCmdLine[] = TEXT("cmd.exe /k echo Hello"); CreateProcess(NULL, szCmdLine, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); }
|
进程实例句柄和命令行参数
每个加载到进程地址空间的可执行文件或DLL都被赋予一个唯一的实例句柄(HINSTANCE或HMODULE)。对于可执行文件,实例句柄实际上就是该模块在虚拟地址空间中的基地址。WinMain函数的hInstance参数就是进程主执行文件的实例句柄。GetModuleHandle函数可以获取已加载模块的句柄。命令行参数通过WinMain的lpCmdLine或GetCommandLine函数获取,需要手动解析或使用CommandLineToArgvW转换为参数数组。
HINSTANCE GetModuleHandle( LPCTSTR lpModuleName );
LPTSTR GetCommandLine();
LPWSTR* CommandLineToArgvW( LPCWSTR lpCmdLine, int* pNumArgs );
void ProcessCommandLine() { LPTSTR lpCmdLine = GetCommandLine(); int nArgs; LPWSTR* szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs); if (szArglist != NULL) { for (int i = 0; i < nArgs; i++) { wprintf(L"参数 %d: %s\n", i, szArglist[i]); } LocalFree(szArglist); } }
void GetModuleInfo() { HMODULE hMod = GetModuleHandle(NULL); printf("主模块基地址: 0x%p\n", hMod); HMODULE hKernel32 = GetModuleHandle(TEXT("kernel32.dll")); printf("kernel32.dll基地址: 0x%p\n", hKernel32); TCHAR szPath[MAX_PATH]; GetModuleFileName(hMod, szPath, MAX_PATH); printf("模块路径: %s\n", szPath); }
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow );
int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow );
|
终止进程
进程有四种终止方式:主线程入口函数返回(最优雅的方式)、进程中的线程调用ExitProcess(应避免)、另一个进程调用TerminateProcess(强制终止,应避免)、以及所有线程自然终止(几乎不会发生)。主线程返回时,C/C++运行时库会清理全局对象并调用ExitProcess。ExitProcess会终止整个进程,包括所有线程,并设置退出代码。TerminateProcess是异步的,被终止的进程无法执行清理操作,可能导致资源泄漏或数据损坏。
VOID ExitProcess( UINT uExitCode );
BOOL TerminateProcess( HANDLE hProcess, UINT uExitCode );
BOOL GetExitCodeProcess( HANDLE hProcess, LPDWORD lpExitCode );
void ExitGracefully(int nExitCode) { ExitProcess(nExitCode); }
DWORD WaitForProcessExit(HANDLE hProcess, DWORD dwTimeout) { DWORD dwResult = WaitForSingleObject(hProcess, dwTimeout); if (dwResult == WAIT_OBJECT_0) { DWORD dwExitCode; if (GetExitCodeProcess(hProcess, &dwExitCode)) { return dwExitCode; } } return (DWORD)-1; }
BOOL ForceTerminateProcess(DWORD dwProcessId) { HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, dwProcessId); if (hProcess == NULL) { return FALSE; } BOOL bResult = TerminateProcess(hProcess, 1); CloseHandle(hProcess); return bResult; }
|
子进程与父进程的关系
通过CreateProcess创建的进程之间存在父子关系,但这种关系仅限于创建时刻。子进程会继承父进程的某些属性:环境变量、当前目录、控制台(除非指定DETACHED_PROCESS或CREATE_NEW_CONSOLE)、以及错误模式等。父进程可以通过PROCESS_INFORMATION结构获取子进程的句柄和ID,从而监控或控制子进程。但子进程没有内置机制获取父进程信息,Toolhelp函数可以查询进程列表和父子关系,但进程ID可能被重用,不能依赖父子关系进行长期通信。
HANDLE CreateToolhelp32Snapshot( DWORD dwFlags, DWORD th32ProcessID );
typedef struct tagPROCESSENTRY32 { DWORD dwSize; DWORD cntUsage; DWORD th32ProcessID; ULONG_PTR th32DefaultHeapID; DWORD th32ModuleID; DWORD cntThreads; DWORD th32ParentProcessID; LONG pcPriClassBase; DWORD dwFlags; TCHAR szExeFile[MAX_PATH]; } PROCESSENTRY32, *LPPROCESSENTRY32;
BOOL Process32First(HANDLE hSnapshot, LPPROCESSENTRY32 lppe); BOOL Process32Next(HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
void EnumerateProcesses() { HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) { return; } PROCESSENTRY32 pe; pe.dwSize = sizeof(pe); if (Process32First(hSnapshot, &pe)) { do { printf("进程名: %s\n", pe.szExeFile); printf(" PID: %u\n", pe.th32ProcessID); printf(" 父PID: %u\n", pe.th32ParentProcessID); printf(" 线程数: %u\n\n", pe.cntThreads); } while (Process32Next(hSnapshot, &pe)); } CloseHandle(hSnapshot); }
DWORD GetParentProcessId(DWORD dwProcessId) { DWORD dwParentId = 0; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot != INVALID_HANDLE_VALUE) { PROCESSENTRY32 pe; pe.dwSize = sizeof(pe); if (Process32First(hSnapshot, &pe)) { do { if (pe.th32ProcessID == dwProcessId) { dwParentId = pe.th32ParentProcessID; break; } } while (Process32Next(hSnapshot, &pe)); } CloseHandle(hSnapshot); } return dwParentId; }
void WaitForChildProcessReady(LPCTSTR pszCmdLine) { STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; if (CreateProcess(NULL, pszCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { WaitForInputIdle(pi.hProcess, INFINITE); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); } }
|
总结:
进程是Windows系统中程序运行的基本隔离单元,由内核对象和虚拟地址空间组成。CreateProcess函数提供了创建新进程的完整功能,包括安全性控制、句柄继承、环境设置和启动状态控制。进程实例句柄标识了加载到地址空间中的可执行模块,命令行参数需要正确解析。终止进程时应优先让主线程正常返回,避免使用强制终止。父子进程关系仅在创建时存在,不应依赖其进行长期通信。理解进程的生命周期和管理机制是进行Windows系统编程和进程间通信的基础。