在计算机操作系统中,进程是正在运行中的程序的实例。进程是操作系统进行资源分配和管理的基本单位,包括内存、文件句柄、系统状态等。每个进程都有自己的独立内存空间和运行状态,因此它们不会互相干扰,也不会互相影响。多个进程可以在操作系统上同时运行,每个进程都在自己的空间里执行自己的代码
在Windows X86环境下, 进程的内存空间通常被划分为以下三个区域
分区 | 描述 | 地址范围 |
---|---|---|
用户模式区 | 用户模式区占据了进程地址空间的大部分,通常是4GB大小。它包含了程序代码、堆、栈、数据等,是进程中大部分数据的存放位置 | 0x00000000 ~ 0x7FFFFFFF (2GB) |
系统保留区 | 由于内核区和用户模式区之间存在一定的限制和隔离,这个区域可以用来共享一些必要的数据,以便内核和用户模式的进程之间能够进行通信和数据共享 | 0x7FFE0000 - 0x7FFFFFFF (128KB) |
内核区 | 内核模式区只占据了进程地址空间的一小部分,它通常包含了内核数据结构、操作系统代码和其他内核相关的资源。内核模式区对于应用程序来说是不可访问的,只能通过系统调用等方式来访问其中的数据和功能 | 0x80000000 ~ 0xFFFFFFFF (2GB) |
首先有一点要清楚, 任何进程都是别的进程创建的, 而第一个进程是系统启动时由操作系统内核创建的, 它的进程ID为0, 称为系统空闲进程或系统进程
在Windows中,进程的创建可以使用CreateProcess
函数,该函数会返回一个指向新进程的句柄
1.为进程分配内存空间,加载EXE文件, 并将其映射到内存
2.创建进程内核对象EPROCESS
EPROCESS是Windows操作系统内核中的一种数据结构,它代表了一个进程对象。EPROCESS结构体包含了进程的许多信息,例如进程的PID、进程的线程列表、虚拟内存映射、访问权限、I/O访问权限等等
3.映射系统dll(ntdll.dll)至内存
4.创建线程内核对象ETHREAD
创建进程的时候系统会默认创建一个线程, 也就是说每个进程都至少有一个线程
5.系统启动线程
线程启动之前, 还需要映射运行可执行文件所需的其他dll文件, 随后线程才开始执行
CreateProcess函数用于创建一个新的进程并返回进程句柄, 其原型如下
BOOL CreateProcess(LPCTSTR lpApplicationName, //需要运行的可执行文件名LPTSTR lpCommandLine, //命令行参数字符串LPSECURITY_ATTRIBUTES lpProcessAttributes, //进程的安全属性LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程的安全属性BOOL bInheritHandles, //是否继承父进程的句柄DWORD dwCreationFlags, //进程标志 LPVOID lpEnvironment, //新进程的环境变量,如果为NULL,则将使用当前进程的环境变量LPCTSTR lpCurrentDirectory, //新进程的当前目录LPSTARTUPINFO lpStartupInfo, //指向STARTUPINFO结构的指针,包含了启动进程时的窗口状态和标志等信息LPPROCESS_INFORMATION lpProcessInformation //指向PROCESS_INFORMATION结构的指针,用于返回新进程的进程句柄和主线程句柄
);
如下是PROCESS_INFORMATION结构的成员
typedef struct _PROCESS_INFORMATION {HANDLE hProcess; //进程句柄 HANDLE hThread; //线程句柄DWORD dwProcessId; //进程IDDWORD dwThreadId; //线程ID
}
如下是STARTUPINFO结构的成员
typedef struct _STARTUPINFOW {DWORD cb; //结构体大小LPWSTR lpReserved; //保留,置为NULLLPWSTR 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; //标准错误设备的句柄
}
如下代码是一个创建进程的简单实例, 用于启动计算器(calc.exe)
include
include //创建子进程函数,传递两个参数,分别是应用程序名字和程序命令行参数,若子进程创建成功则返回子进程句柄
DWORD CreateChildProcess(TCHAR ApplicationName[],TCHAR CommandLine[]=NULL) {STARTUPINFO si; //进程启动信息PROCESS_INFORMATION pi; //进程信息ZeroMemory(&si, sizeof(si)); //将结构体si的所有成员都初始化为0ZeroMemory(&pi, sizeof(pi)); //将结构体pi的所有成员都初始化为0si.cb = sizeof(si); //结构体大小if (!CreateProcess(ApplicationName, //要执行的应用程序名称(包含路径)NULL, //命令行参数NULL, //进程句柄不可被继承NULL, //线程句柄不可被继承FALSE, //不继承句柄0, //标志位为0NULL, //使用父进程的环境变量NULL, //使用父进程的工作目录&si, //传递启动信息&pi) //传递进程信息) {printf("CreateProcess failed (%d).\n", GetLastError()); //打印错误信息return 0;}return (DWORD)pi.hProcess; //返回进程句柄//return (DWORD)pi.dwProcessId; //返回进程ID//释放进程句柄和线程句柄CloseHandle(pi.hProcess);CloseHandle(pi.hThread);
}int main()
{TCHAR ApplicationName[] = TEXT("E:\\calc.exe");DWORD ProcessHandle = CreateChildProcess(ApplicationName);return 0;
}
若需要以挂起的形式创建进程, 可将CreateProcess
函数的第六个参数设置为CREARE_SUSPEND
, 这样创建的进程一开始并不会自动启动线程, 而是需要自己手动执行ResumeThread
函数恢复线程后才会启动
include
include int main()
{STARTUPINFO si; //进程启动信息PROCESS_INFORMATION pi; //进程信息ZeroMemory(&si, sizeof(si)); //将结构体si的所有成员都初始化为0ZeroMemory(&pi, sizeof(pi)); //将结构体pi的所有成员都初始化为0si.cb = sizeof(si); //结构体大小TCHAR ApplicationName[] = TEXT("E://test.exe"); //test.exe是一个只输出"进程执行"的文件if (!CreateProcess(ApplicationName, //要执行的应用程序名称(包含路径)NULL, //命令行参数NULL, //进程句柄不可被继承NULL, //线程句柄不可被继承FALSE, //不继承句柄CREATE_SUSPENDED, //以挂起的形式创建进程NULL, //使用父进程的环境变量NULL, //使用父进程的工作目录&si, //传递启动信息&pi) //传递进程信息) {printf("CreateProcess failed (%d).\n", GetLastError()); //打印错误信息return 0;}for (int i = 0; i < 5; i++){printf("#######\n");Sleep(1000);}ResumeThread(pi.hThread);//释放进程句柄和线程句柄CloseHandle(pi.hProcess);CloseHandle(pi.hThread);
}
如上代码所示, 线程会在for循环打印代码执行结束后才会启动线程, 执行结果如下
OpenProcess
函数用于打开一个已存在的进程对象,以便对该进程执行操作,例如向该进程发送信号或从该进程读取内存。此函数的调用者必须具有足够的权限来打开目标进程
如果函数执行成功,返回打开进程的句柄,否则返回NULL,并可通过调用GetLastError
函数获取错误码
要注意的是, 使用CloseHandle
函数释放句柄后, 就不能再使用OpenProcess
函数来打开这个进程了, 因为CloseHandle
函数会将句柄从进程的句柄表中移除,并且在所有引用计数都归零之后释放内存资源
OpenProcess函数的语法如下:
HANDLE OpenProcess(DWORD dwDesiredAccess, // 指定进程的访问权限,可取值为PROCESS_ALL_ACCESS或其他指定的进程访问权限常量BOOL bInheritHandle, // 指定句柄是否可被子进程继承,TRUE表示可继承,FALSE表示不可继承DWORD dwProcessId // 指定要打开的进程的进程ID
);
以下是dwDesireAccess参数的可取值:
TerminateProcess
函数是Windows操作系统提供的函数之一,用于终止指定进程, 当调用TerminateProcess
函数时,会向指定进程发送一个中断信号,强制其终止。
这个过程是非常暴力的,会直接终止进程的所有线程,不会给进程和线程任何清理资源的机会,因此使用该函数需要非常慎重
函数定义如下:
BOOL TerminateProcess(HANDLE hProcess, //进程句柄,用于标识被终止的进程UINT uExitCode //进程的退出代码,表示进程退出的原因,可以随意填写
);
GetModuleFileName函数用于获取指定模块的完整路径名。通常情况下,可以通过指定NULL作为参数hModule,来获取当前应用程序的完整路径名,该函数的声明如下:
DWORD GetModuleFileName(HMODULE hModule, // 模块句柄,指定NULL表示获取当前应用程序的路径名LPTSTR lpFilename, // 接收完整路径名的缓冲区DWORD nSize // 缓冲区大小
);
GetCurrentDirectory函数用于获取当前进程的工作目录。其函数原型为
DWORD GetCurrentDirectory(DWORD nBufferLength, // 缓冲区大小,单位为字节LPTSTR lpBuffer // 存储路径的缓冲区
);
这个函数的路径是指当前进程的工作目录,而不是当前模块的目录。如果需要获取当前模块的目录,需要使用GetModuleFileName函数来获取模块文件的路径
include
include int main()
{ //获取当前模块的完整路径char str1[256];GetModuleFileName(NULL, str1, 256);printf("当前应用程序路径名:%s\n", str1);//获取当前应用程序的工作目录char str2[256];GetCurrentDirectory(256, str2);printf("当前程序工作目录:%s", str2);return 0;
}
GetStartupInfo函数用于检索当前进程的启动信息,它的主要功能是获取STARTUPINFO结构体,其中包含了进程的启动信息,如命令行参数、标准输入输出句柄、窗口显示方式等
调用GetStartupInfo函数需传递一个指向STARTUPINFO类型的指针
int main()
{
// 定义一个 STARTUPINFO 结构体变量 si
STARTUPINFO si;// 使用 ZeroMemory 函数将 si 清零
ZeroMemory(&si, sizeof(si));// 设置 si 的 cb 字段
si.cb = sizeof(si);// 使用 GetStartupInfo 函数获取启动信息
GetStartupInfo(&si);// 输出当前进程窗口状态
printf("Show window command: %d\n", si.dwFlags);// 返回 0,表示程序执行成功
return 0;
}
GetCurrentProcessID函数是Windows API中的一部分,它返回当前进程的进程ID(Process ID, 其返回值是一个无符号长整型
其语法格式如下:
GetProcessId(_In_ HANDLE Process
);
GetCurrentProcess函数是Windows API提供的一个函数,用于获取当前进程的句柄。该函数没有任何参数,调用后将返回一个类型为HANDLE的句柄,该句柄指向当前进程
GetCurrentProcess 获取当前进程的一个伪句柄GetCurrentProcess 总是返回-1(即0xFFFFFFFF),代表当前进程。这个句柄不在句柄表中,不是真正的句柄,所以叫伪句柄
EnumProcesses函数是Windows API中的一个函数,用于列举当前正在运行的进程的ID号,通常用于获取系统中所有进程的ID号列表。若函数执行成功则返回TRUE, 否则返回FLASE, 它的声明如下:
BOOL EnumProcesses(DWORD *pProcessIds, // 接收进程ID的缓冲区DWORD cb, // 缓冲区大小(以字节为单位)DWORD *pBytesReturned // 实际写入缓冲区的字节数
);
如果包含了Windows.h头文件, 还提示"EnumProcesses"未定义标识符, 请检查是否正确链接了psapi.lib库文件。
可以通过在Visual Studio中转到“项目”菜单,然后选择“属性”来查看和配置链接器选项。在属性页面的左侧选择“链接器”,然后选择“输入”。在“附加依赖项”字段中添加“Psapi.lib”,然后包含头文件:include "psapi.h"
如下实例枚举当前系统运行的所有进程ID, 并打印至控制台
include
include
include "psapi.h"define ARRAY_SIZE 1024int main()
{DWORD aProcesses[ARRAY_SIZE], cbNeeded, cProcesses;if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)){printf("EnumProcesses failed: %d\n", GetLastError());return 1;}// 计算枚举到的进程数cProcesses = cbNeeded / sizeof(DWORD);// 打印进程IDfor (DWORD i = 0; i < cProcesses; i++){if (aProcesses[i] != 0){printf("Process ID: %u\n", aProcesses[i]);}}return 0;
}
CreateToolhelp32Snapshot函数是Windows系统提供的一个快照函数,可以获取系统中当前正在运行的进程和线程的快照。该函数可以通过枚举系统中所有进程和线程来帮助实现进程和线程的监控和管理。在调用该函数时,需要指定快照类型,如进程快照、线程快照等。函数会返回一个句柄,该句柄可以作为参数传递给其他Tool Help函数,以获取有关系统中进程和线程的详细信息。在使用完成后,需要调用CloseHandle函数关闭句柄
如下实例使用CreateToolhelp32Snapshot函数枚举系统所有进程:
include
include
include
include int main()
{HANDLE hProcessSnap;PROCESSENTRY32 pe32;// 获取系统进程快照hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);if (hProcessSnap == INVALID_HANDLE_VALUE) {std::cout << "CreateToolhelp32Snapshot failed: " << GetLastError() << std::endl;return 1;}// 设置pe32结构体的大小,否则Process32First/Next函数会失败pe32.dwSize = sizeof(PROCESSENTRY32);// 获取第一个进程的信息if (!Process32First(hProcessSnap, &pe32)) {std::cout << "Process32First failed: " << GetLastError() << std::endl;CloseHandle(hProcessSnap);return 1;}// 遍历进程列表,输出每个进程的PID和名称do {_tprintf(TEXT("PID=%d, Name=%s\n"), pe32.th32ProcessID, pe32.szExeFile);} while (Process32Next(hProcessSnap, &pe32));// 关闭进程快照句柄CloseHandle(hProcessSnap);return 0;
}