1. 进程
进程:运行的程序,可执行文件在内存中的样子。
包含:
- 虚拟空间
- exe + dll
- 进程内核对象
- 至少一个运行的线程
- 。。。
创建进程
CreateProcess()
WinExec()
ShellExecute()
system()
OpenProcess()
ExitProcess()
TerminateProcess()
#define ExePath L"E:\\Dbgview.exe"
int main()
{
//窗口属性
STARTUPINFO si = {};
si.cb = sizeof(si);
//进程信息
PROCESS_INFORMATION pi = {};
//创建进程
BOOL bRet = CreateProcess(
ExePath, //exe的路径
NULL, //命令行参数
NULL, //进程安全属性
NULL, //线程安全属性
FALSE, //是否继承父进程句柄
NULL, //创建标志
NULL, //环境变量
NULL, //当前运行目录
&si, //窗口特性
&pi
);
if (bRet)
{
MessageBox(NULL, L"创建成功", NULL, NULL);
}
else {
MessageBox(NULL, L"创建失败", NULL, NULL);
}
return 0;
}
结束进程
最常见和最理想的结束方式是,主线程的入口函数返回。
void KillProcess(DWORD dwPid)
{
//1.获取进程句柄
HANDLE hProcess = OpenProcess(
PROCESS_TERMINATE, //打开句柄拥有的权限
FALSE, //句柄是否能继承
dwPid //进程PID
);
if (hProcess != INVALID_HANDLE_VALUE)
{
//2.结束进程
TerminateProcess(hProcess, 0);
//3.关闭句柄
CloseHandle(hProcess);
}
}
快照与遍历进程
快照可以用来遍历进程。
快照api包含在<TIHelpp32.h>
步骤:
- 创建进程快照
- 第一次遍历进程
- 循环遍历进程Next
- 关闭句柄
#include <iostream>
#include <windows.h>
#include <TlHelp32.h>
void EnumProcess()
{
//1.创建进程快照
HANDLE hSnap = CreateToolhelp32Snapshot(
TH32CS_SNAPPROCESS, //遍历进程快照1
0); //进程PID
//2.第一次遍历进程
PROCESSENTRY32 stcPe = {sizeof(stcPe)};
if (Process32First(hSnap, &stcPe))
{
//3.循环遍历进程Next
do {
wprintf(L"pid :%d %S\n", stcPe.th32ProcessID, stcPe.szExeFile);
//获取其他信息
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, stcPe.th32ProcessID);
if (hProcess != INVALID_HANDLE_VALUE)
{
GetPriorityClass(hProcess);
CloseHandle(hProcess);
}
} while (Process32Next(hSnap, &stcPe));
}
//4.关闭句柄
CloseHandle(hSnap);
}
int main()
{
EnumProcess();
return 0;
}
2. 快照遍历模块
仅32位。
void EnumModule(DWORD dwPid)
{
//1.创建模块快照
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid);
//2.第一次遍历模块
MODULEENTRY32 stcMd = { sizeof(stcMd) };
if (Module32First(hSnap, &stcMd))
{
//3.循环遍历模块Next
do
{
wprintf(L"size:%d path:%S\n", stcMd.dwSize, stcMd.szExePath);
} while (Module32Next(hSnap, &stcMd));
}
CloseHandle(hSnap);
}
遍历模块的正确姿势
BOOL EnumModules96(DWORD dwPid, std::list<MYMODULEINFO>& list)
{
HANDLE hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE,
dwPid);
HMODULE* hModules = new HMODULE[0x2000]();
DWORD dwSize = 0, dwModuleCount = 0;
MYMODULEINFO modinfo;
if (hProcess == NULL)
{
MessageBoxW(NULL, L"EnumModules96() ERROR", L"ERROR", 0);
return FALSE;
}
EnumProcessModulesEx(hProcess, hModules, sizeof(HMODULE) * 0x2000,
&dwSize, LIST_MODULES_ALL);
dwModuleCount = dwSize / sizeof(HMODULE);
size_t i = 0;
//while(hModules[i])
for (; i < dwModuleCount; ++i)
{
ZeroMemory(modinfo.wszFileName, 0x1000 * sizeof(WCHAR));
GetModuleFileNameEx(hProcess, hModules[i],
modinfo.wszFileName, 0x1000);
GetModuleInformation(hProcess, hModules[i],
&modinfo.stcModinfo, sizeof(MODULEINFO));
//wprintf(L"%ls\n", wszFileName);
//wprintf(L" EP: %p\n", (WCHAR*)modinfo.EntryPoint);
//wprintf(L" BaseOfDll: %p\n", (WCHAR*)modinfo.lpBaseOfDll);
//wprintf(L" SizeOfImage:%u\n", modinfo.SizeOfImage);
list.push_back(modinfo);
}
delete[] hModules;
return TRUE;
}
3. 线程
主函数其实也可以配置属性,指入口函数:
#pragma comment(linker, "/entry:\"MyFun\"")
可以打开线程窗口调试。
需要创建线程的情况:
- 输入的同时也有输出;
- 一个逻辑依赖于另一个逻辑的实时反馈,如进度条;
- 复杂但不紧急的任务;
- 复杂的磁盘操作;
- 网络程序;
- 多个需要长时间等待的逻辑;
- 密集计算;
- 远程注入。
总结就是:会阻塞当前线程,但当前线程不能被阻塞的情况。
线程是内核对象。
主线程结束,其它线程也结束。
创建线程
#include <windows.h>
#include <stdio.h>
DWORD WINAPI ThreadProc(LPVOID lParam)
{
int i = 10;
while (i--)
{
wprintf(L"child thread\n");
Sleep(1000);
}
return 0;
}
int main()
{
HANDLE hThread = CreateThread(NULL,
NULL,
ThreadProc,
NULL,NULL,NULL);
WaitForSingleObject(
hThread,
5000
);
int i = 10;
while (i--)
{
wprintf(L"parent thread\n");
Sleep(1000);
}
return 0;
}
遍历线程
#include <windows.h>
#include <stdio.h>
#include<TlHelp32.h>
void EnumThreads(DWORD dwPid)
{
HANDLE hSnap = INVALID_HANDLE_VALUE;
THREADENTRY32 te32;
hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnap == INVALID_HANDLE_VALUE)
{
return;
}
te32.dwSize = sizeof(THREADENTRY32);
if (Thread32First(hSnap, &te32))
{
do
{
if (te32.th32OwnerProcessID == dwPid)
{
printf("TID:%d\n", te32.th32ThreadID);
}
} while (Thread32Next(hSnap,&te32));
}
CloseHandle(hSnap);
}
int main()
{
EnumThreads(GetCurrentProcessId());
return 0;
}
伪句柄
HANDLE GetCurrentProcess();
HANDLE GetCurrentThread();
UINT GetCurrentProcessiD();
UINT GetCurrentThreadId();
前两个伪句柄仅能在本地使用,不能在其它进程/线程中使用。
每个进程/线程都有自己的伪句柄,指向真正的句柄。
#include <iostream>
#include<Windows.h>
//线程回调函数
DWORD WINAPI ChildThread(PVOID pParam) {
//观察主线程时间 发现主线程时间不一致,说明这个句柄不是主线程的,它只是伪句柄
HANDLE hThreadParent = (HANDLE)pParam;
FILETIME stcCreationTime, stcExitTime;
FILETIME stcKernelTime, stcUserTime;
GetThreadTimes(hThreadParent, &stcCreationTime,
&stcExitTime, &stcKernelTime, &stcUserTime);
//输出创建时间
printf("main: h = %d L = %d\n",
stcCreationTime.dwHighDateTime, stcCreationTime.dwLowDateTime);
return 0;
}
int main()
{
//获取当前线程句柄
HANDLE hThreadParent = GetCurrentThread();
//显示当前线程时间
FILETIME stcCreationTime, stcExitTime;
FILETIME stcKernelTime, stcUserTime;
GetThreadTimes(hThreadParent, &stcCreationTime,
&stcExitTime, &stcKernelTime, &stcUserTime);
//输出创建时间
printf("main: h = %d L = %d\n",
stcCreationTime.dwHighDateTime, stcCreationTime.dwLowDateTime);
//创建线程,将当前主线程句柄传递
HANDLE hThread = CreateThread(NULL, 0, ChildThread,
(PVOID)hThreadParent, 0, NULL);
//等待线程结束
WaitForSingleObject(hThread, -1);
}
复制句柄
#include <iostream>
#include<Windows.h>
//线程回调函数
DWORD WINAPI ChildThread(PVOID pParam) {
//观察主线程时间
HANDLE hThreadParent = (HANDLE)pParam;
FILETIME stcCreationTime, stcExitTime;
FILETIME stcKernelTime, stcUserTime;
GetThreadTimes(hThreadParent, &stcCreationTime,
&stcExitTime, &stcKernelTime, &stcUserTime);
//输出创建时间
printf("main: h = %d L = %d\n",
stcCreationTime.dwHighDateTime, stcCreationTime.dwLowDateTime);;
return 0;
}
int main()
{
//获取当前线程句柄
HANDLE hThreadParent = GetCurrentThread();
//复制句柄,变成真句柄
DuplicateHandle(
GetCurrentProcess(), // 拥有源句柄的进程句柄
GetCurrentThread(), // 指定对象的现有句柄(伪句柄)
GetCurrentProcess(), // 拥有新对象句柄的进程句柄
&hThreadParent, // 用于保存新句柄
0, // 安全访问级别
false, // 是否可以被子进程继承
DUPLICATE_SAME_ACCESS); // 转换选项
//显示当前线程时间
FILETIME stcCreationTime, stcExitTime;
FILETIME stcKernelTime, stcUserTime;
GetThreadTimes(hThreadParent, &stcCreationTime,
&stcExitTime, &stcKernelTime, &stcUserTime);
//输出创建时间
printf("main: h = %d L = %d\n",
stcCreationTime.dwHighDateTime, stcCreationTime.dwLowDateTime);;
//创建线程,将当前主线程句柄传递
HANDLE hThread = CreateThread(NULL, 0, ChildThread,
(PVOID)hThreadParent, 0, NULL);
//等待线程结束
WaitForSingleObject(hThread, -1);
}
线程退出
ExitThread()
会结束主函数,但不会析构c++对象,因此建议使用_endthreadex()
TerminateThread()
只会结束指定线程,也不会析构对象,不建议。
线程睡眠
windows是分时系统,不是实时系统,所以Sleep()
有20ms左右的误差。
Sleep(0)则放弃剩余时间片。
线程内核对象
暂停次数:创建线程初始化时,暂停次数为1,不会分配时间片,如果有SUSPEND标志,则初始化为0,
线程环境(context):线程让出cpu后,要保存执行环境,再次获得时间片时,再把环境加载到cpu中。
信号:WaitForSingleObject()
会阻塞,线程结束收到信号后才返回。
优先级
32个优先级。
CreateThread()没有提供设置优先级的参数:
windows不允许我们直接操作线程优先级,而是os通过进程优先级与相对线程优先级得出线程优先级,这个数值叫做基本优先级。
操作:
- CreateProcess()的fdwCreate传入优先级;
- SetPriorityClass()改变优先级;
- GetPriorityClass();
- SetThreadPriority();
- GetThreadPriority();
动态优先级
线程同步
等待函数
先介绍一下等待函数:WaitForMultipleObjects()
或WaitForSingleObject()
。它们等待可以等待的内核对象,这些对象有激发态和非激发态两个状态。调用后线程会进入等待状态。
特定内核对象变为激发态时,会触发等待函数返回。
返回值:
WAIT_ABANDONED
:用于互斥体WAIT_OBJECT_0
:内核对象变为激发态WAIT_TIMEOUT
:超时WAIT_FAILED
:失败
当一个对象的状态改变时,称之为成功等待的副作用。
具体同步措施,可以查看我的另一篇博客。