进程
进程的概念
进程是计算机系统中正在运行的程序的实例。在操作系统中进程是为其它程序提供使用资源的。
每个进程都由一个或多个线程组成,线程是进程内的执行单元。
每一个进程至少有一个线程。
每一个进程在操作系统中都有一个虚拟的空间。
任何进程都是由其它进程创建的。
在Windows操作系统中,初始进程的名称是System,它是由Windows内核创建的。
创建进程需要使用函数createprocess:
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
);
lpApplicationName:可执行文件的路径或名称。
lpCommandLine:命令行参数,用于指定传递给新进程的命令行参数。
lpProcessAttributes 和 lpThreadAttributes:进程和线程的安全属性,通常设置为 NULL。
bInheritHandles:指示新进程是否继承当前进程的句柄。
dwCreationFlags:创建标志,用于指定进程的创建方式,如是否创建一个新的控制台窗口等。
lpEnvironment:新进程的环境变量,通常设置为 NULL,表示继承当前进程的环境变量。
lpCurrentDirectory:新进程的当前工作目录。
lpStartupInfo:启动信息结构体,包含了新进程的一些属性,如窗口显示方式等。
lpProcessInformation:进程信息结构体,用于接收新进程的句柄和标识符。
这里有一个简单的创建一个打开浏览器的进程。关于win32的函数可以查找MSDN和chart-gpt。
#include <windows.h>
#include <string>
int main()
{
// 定义变量
STARTUPINFO si;
PROCESS_INFORMATION pi;
// 清空结构体
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
// 设置启动信息结构体
si.cb = sizeof(si);
// 转换字符串类型
// 创建新进程
if (CreateProcess(
TEXT("C:\\Program Files\\Internet Explorer\\iexplore.exe"), // 浏览器可执行文件路径或名称
NULL, // 宽字符字符串参数
NULL, // 进程安全属性
NULL, // 线程安全属性
FALSE, // 句柄不可继承
0, // 无特殊标志
NULL, // 继承当前进程环境变量
NULL, // 当前目录
&si, // 启动信息结构体
&pi // 进程信息结构体
))
{
// 新进程创建成功
// 在此可以对新进程进行操作,如等待新进程结束等
// 关闭进程和线程句柄
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
else
{
// 新进程创建失败
// 可以通过调用 GetLastError() 函数获取错误代码以确定失败的原因
}
return 0;
}
进程创建的过程
在我们创建一个进程时,会按照以下顺序。
- 映射exe文件。
将文件映射到内存中,这样程序才能执行起来 - 创建内核对象
我们在使用程序时,需要内核帮助,但是又不能完全将内核暴露在用户面前,所以在内核中会生成一个结构体,这个结构体称为内核对象,便于我们控制程序 - 映射系统ntdll.dll
NTDLL 是 Windows NT 内核的一部分,它提供了操作系统的核心功能,包括进程管理、线程管理、内存管理、对象管理、安全性、文件系统访问、系统调用接口等。 - 创建线程内核对象
每一个进程都至少有一个线程 - 系统启动线程,映射dll,线程开始执行
如果是以挂起的方式执行,则会程序被挂起,等待执行。
句柄表
内核对象
内核对象是进程,线程,文件,互斥体等在在内核中对应的结构体
我们所使用的应用都是基于操作系统的,属于应用层,应用层程序是在内核层的支持下运行的,它们依赖于内核提供的系统服务和功能。但是内核层对于应用是不可见的,在使用时会产生一个对应结构体在内核中,
这种内核层对应用层的隔离和保护是为了确保系统的安全性和稳定性。内核层的实现细节对于应用程序来说是不可见的,应用程序只需使用系统调用接口来访问内核提供的功能
每次创建一个进程,事件,线程,文件等,都是在内核中产生了一个内核对象
管理内核对象
内核层对于应用来说是不可见的,但是应用需要对其生成的内核对象进行管理,而这个工作就是靠句柄表来完成。
应用层的一个进程,创建了四个内核对象,这个进程会有一个句柄表,这个句柄表里面包含了三个信息:
该句柄是否可以被继承,句柄的编号,对应句柄的内核地址。
图中进程要使用内核对象A时,会传入A的句柄到句柄表,然后在表中找到对应地址,然后得到A。
当内核中使用了错误地址时,系统会发生崩溃,蓝屏等情况
共享句柄表
每一个内核对象的句柄表都是私有的,当我们要使用其它的内核对象的句柄表时,我们可以使用openprocess函数
openprocess:用于打开一个已存在的进程,并返回该进程的句柄。
HANDLE OpenProcess(
DWORD dwDesiredAccess,
//指定对进程的访问权限和操作类型。常见的权限常量有
//PROCESS_ALL_ACCESS(完全访问权限)和 PROCESS_QUERY_INFORMATION(查询进程信息权限)等。
BOOL bInheritHandle,
//指定返回的进程句柄是否可以被子进程继承。通常设置为 FALSE。
DWORD dwProcessId
//要打开的目标进程的进程标识符(PID)
);
除了使用openprocess函数,创建的子进程可以通过继承来获得进程句柄。
关闭内核对象
我们在创建完一个内核对象时,会存在一个计数器,当其它程序再次使用openprocess函数打开这个句柄表时,计数器会加一。
closehandle:关闭该内核对象对句柄的引用,计数器会减一
BOOL CloseHandle(
HANDLE hObject //要关闭的内核对象的句柄。
);
当我们要关闭这个对象时,需要使用TerminateThread函数。
在关闭前应该确保计数器为0.
BOOL TerminateThread(
HANDLE hThread, //要终止的目标线程的句柄。
DWORD dwExitCode //指定线程退出的代码或状态值。 0正常终止
);
PID和句柄的区别
- 按对象来说:
句柄表:是某个内核对象的私有表。
PID:是由操作系统生成的,所有进程都可见。 - 按作用来说:
句柄表:用于管理和跟踪进程所打开的句柄
PID:用于唯一标识一个正在运行的进程的数字标识符。它用于在操作系统中识别和操作进程,可以用于查找和管理进程的状态、资源、权限等
进程相关的API
- createprocess
- closehandle
- terminateprocess
- getcurrentprocessid
DWORD GetCurrentProcessId();
//返回当前进程PID
- getcurrentprocess
HANDLE GetCurrentProcess();
//获取当前进程的句柄
- getcommandline
LPWSTR GetCommandLine();
//获取当前进程的命令行参数
- getstartupinfo
void GetStartupInfo(LPSTARTUPINFO lpStartupInfo);
//获取当前进程的启动信息
- enumprocesss
BOOL EnumProcesses(
DWORD *pProcessIds,
DWORD cb,
DWORD *pBytesReturned
);
//枚举系统中的进程标识符PID
- createtoolhelp32snapshot
HANDLE CreateToolhelp32Snapshot(
DWORD dwFlags,
DWORD th32ProcessID
);
//获取系统中当前运行的进程、线程、模块等相关信息