本节书摘来自异步社区出版社《C++多线程编程实战》一书中的第2章,第2.4节,作者: 【黑山*】Milos Ljumovic(米洛斯 留莫维奇),更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.4 进程的实现
在现代的多任务系统中,进程控制块(Process Control Block,PCB)储存了高效管理进程所需的许多不同数据项。PCB是操作系统为了管理进程,在内核中设置的一种的数据结构。操作系统中的进程用PCB来表示。虽然这种数据结构的细节因系统而异,但是常见的部分大致可分为三大类:
进程标识数据;
进程状态数据;
进程控制数据。
图2.6
PCB是管理进程的中心。绝大多数操作系统程序(包括那些与调度、内存、I/O资源访问和性能监控相关的程序)都要访问和修改它。通常,要根据PCB为进程构建数据。例如,某PCB内指向其他PCB的指针以不同的调度状态(就绪、阻塞等)创建进程队列。
操作系统必须代表进程来管理资源。它必须不断地关注每个进程的状态、系统资源和内部值。下面的程序示例演示了如何获得一个进程基本信息结构地址,其中的一个特征就是PCB的地址。另一个特征是唯一的进程ID。为简化示例,我们只输出从对象中读取的进程ID。
准备就绪
确定安装并运行了Visual Studio。
操作步骤
我们再来创建一个操控进程的程序。这次,我们从进程基本信息结构中获取进程ID。请执行以下步骤。
1. 创建一个新的默认C++控制台应用程序,名为NtProcessDemo
。
2. 打开NtProcessDemo.cpp
。
3. 添加下面的代码:
#include "stdafx.h"
#include <Windows.h>
#include <Winternl.h>
#include <iostream>
using namespace std;
typedef NTSTATUS(NTAPI* QEURYINFORMATIONPROCESS)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
int _tmain(int argc, _TCHAR* argv[])
{
STARTUPINFO startupInfo = { 0 };
PROCESS_INFORMATION processInformation = { 0 };
BOOL bSuccess = CreateProcess(
TEXT("C:\\Windows\\notepad.exe"), NULL, NULL,
NULL, FALSE, NULL, NULL, NULL, &startupInfo,
&processInformation);
if (bSuccess)
{
cout << "Process started." << endl << "Process ID:\t"
<< processInformation.dwProcessId << endl;
PROCESS_BASIC_INFORMATION pbi;
ULONG uLength = 0;
HMODULE hDll = LoadLibrary(
TEXT("C:\\Windows\\System32\\ntdll.dll"));
if (hDll)
{
QEURYINFORMATIONPROCESS QueryInformationProcess =
(QEURYINFORMATIONPROCESS)GetProcAddress(
hDll, "NtQueryInformationProcess");
if (QueryInformationProcess)
{
NTSTATUS ntStatus = QueryInformationProcess(
processInformation.hProcess,
PROCESSINFOCLASS::ProcessBasicInformation,
&pbi, sizeof(pbi), &uLength);
if (NT_SUCCESS(ntStatus))
{
cout << "Process ID (from PCB):\t"
<< pbi.UniqueProcessId << endl;
}
else
{
cout << "Cannot open PCB!" << endl
<< "Error code:\t" << GetLastError()
<< endl;
}
}
else
{
cout << "Cannot get "
<< "NtQueryInformationProcess function!"
<< endl << "Error code:\t"
<< GetLastError() << endl;
}
FreeLibrary(hDll);
}
else
{
cout << "Cannot load ntdll.dll!" << endl
<< "Error code:\t" << GetLastError() << endl;
}
}
else
{
cout << "Cannot start process!" << endl
<< "Error code:\t" << GetLastError() << endl;
}
return 0;
}```
示例分析
该例中,我们使用了一些其他头文件:`Winternl.h`和`Windows.h`。`Winternl.h`头文件包含了大部分Windows内部函数的原型和数据表示,例如`PROCESS_BASIC_INFORMATION`结构的定义:
typedef struct _PROCESS_BASIC_INFORMATION {
PVOID Reserved1;
PPEB PebBaseAddress;
PVOID Reserved2[2];
ULONG_PTR UniqueProcessId;
PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;`
操作系统在调用内核态和用户态之间的子例程时会用到该结构。
结合PROCESSINFOCLASS
::ProcessBasicInformation
枚举,我们通过UniqueProcessId
特征获取进程标识符,如上面的代码所示。
首先,定义QEURYINFORMATIONPROCESS
,这是从ntdll.dll
中加载的NtQueryInformationProcess
函数的别名。当通过GetProcAddressWin32 API
获得该函数的地址时,就可以询问PROCESS_BASIC_INFORMATION
对象了。注意PROCESS_BASIC_INFORMATION
结构的PebBaseAddress
字段是一个指针,指向新创建进程的PCB。如果还想进一步研究PCB,检查新创建的进程,则必须在运行时使用ReadProcessMemory
例程。因为PebBaseAddress
指向属于新创建进程的内存。