windows的进程和线程

一、认识进程和线程

内核对象的特性:

  • 所有的内核对象都属于操作系统内核,不同的进程可以访问同一个对象。所以,这时候就会涉及到进程的同步,进程间的数据共享。
  • 每一个内核对象都有一个引用计数,如果有一个进程创建或者打开了此对象,这个引用计数会加一。如果引用计数为0,那么此对象会被销毁。
  • 内核对象都有一个安全描述符,所以我们可以通过判断传入的参数,有没有安全描述符,来判断这个对象是不是内核对象。
  • 内核对象的句柄是和进程相关的,同一个对象在不同的进程中间,句柄不一定相同。这就涉及一个问题,不同的进程可以访问同一个对象,但是在不同对象中句柄不相同,这时候该怎么办呢?有三个解决办法:1、由父进程继承给子进程。2、使用名称或者ID作为标识,打开一个内核对象。3、使用DuplicateHandle函数,将一个句柄从一个进程传递给另一个进程。
  • 每一个进程对象,都有一个句柄表,用于记录本进程所打开的所有内核对象。类似于一个数组。

然后我们可以通过这两篇文章来看线程和进程是什么,有什么区别:
https://blog.csdn.net/m0_56673710/article/details/131268291
https://zhuanlan.zhihu.com/p/114453309

以下展示了如何使用代码来创建进程

#include<Windows.h>

int main()
{
	//STARTUPINFOW是一个结构体,用于指定新进程的主窗口如何显示。它包括窗口的大小、位置、显示状态等信息。
	//在这个例子中,使用{ 0 }将所有成员初始化为0。这意味着新进程将使用默认的设置来显示其主窗口。
	STARTUPINFOW sw{ 0 };

	//PROCESS_INFORMATION是一个结构体,用于接收新进程和主线程的标识符。
	PROCESS_INFORMATION pinfo{ 0 };
	CreateProcessW(
		L"E:\\11.exe",
		NULL,
		NULL,
		NULL,
		TRUE,
		0,
		NULL,
		NULL,
		&sw,
		&pinfo
	);
	CloseHandle(pinfo.hThread);
	CloseHandle(pinfo.hProcess);
	//CreateProcessW函数在成功时会在PROCESS_INFORMATION结构体中返回新进程的进程句柄和主线程的线程句柄。这些句柄是系统资源,需要在不再需要时释放。CloseHandle函数用于关闭一个打开的句柄。在这个例子中,CloseHandle(pinfo.hThread);关闭了主线程的句柄,而CloseHandle(pinfo.hProcess);关闭了进程的句柄。这是避免资源泄漏的重要步骤。
}

二、进程的相关操作

在上一章我们展示了CreateProcessW函数,现在我们来展示其他的函数。
首先我们介绍OpenProcess和TerminateProcess函数,分别用来打开进程和结束指定进程。

#include<Windows.h>

int main()
{
	//第一个参数是这个进程的权限,第二个是是否可以继承,第三个是打开进程的id,可以通过任务管理器查看
	HANDLE hprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 17112);

	//第一个参数是返回的句柄,第二个参数是进程的终止代码。操作系统使用这个代码来确定进程的退出状态。
	//如果进程正常退出,可以设置为0,表示成功;否则,可以设置为非零值,表示出现了错误或特定的退出原因。
	TerminateProcess(hprocess, 0);
}

然后是CreateToolhelp32Snapshot:

#include<Windows.h>
#include<TlHelp32.h>
#include<iostream>

int main()
{
	//第一个参数是这个进程的权限,第二个是是否可以继承,第三个是打开进程的id,可以通过任务管理器查看
	HANDLE hprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 17112);

	//第一个参数是返回的句柄,第二个参数是进程的终止代码。操作系统使用这个代码来确定进程的退出状态。
	//如果进程正常退出,可以设置为0,表示成功;否则,可以设置为非零值,表示出现了错误或特定的退出原因。
	TerminateProcess(hprocess, 0);

	//这行代码调用CreateToolhelp32Snapshot函数来创建一个包含系统上所有进程的快照。
	//TH32CS_SNAPPROCESS标志指定快照应该包含进程信息。第二个参数0表示不需要过滤任何进程(即获取所有进程)。
	//第二个参数只有在遍历堆或者模块的时候需要指定进程id
	HANDLE hsnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

	//这行代码初始化了一个PROCESSENTRY32W结构体,该结构体用于接收Process32FirstW和Process32NextW函数检索到的进程信息。结构体的第一个成员被设置为结构体的大小,这是函数要求的。
	PROCESSENTRY32W processentry = { sizeof(PROCESSENTRY32W) };
	BOOL bsuccess = Process32FirstW(hsnap, &processentry);
	if (bsuccess)
	{
		//遍历所有得到的进程
		do
		{
			printf("%d %ls\n", processentry.th32ProcessID, processentry.szExeFile);
		} while (Process32NextW(hsnap, &processentry));
	}
}

如果遍历模块的话:

#include<Windows.h>
#include<TlHelp32.h>
#include<iostream>

int main()
{
	//第一个参数是这个进程的权限,第二个是是否可以继承,第三个是打开进程的id,可以通过任务管理器查看
	HANDLE hprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 17112);

	//第一个参数是返回的句柄,第二个参数是进程的终止代码。操作系统使用这个代码来确定进程的退出状态。
	//如果进程正常退出,可以设置为0,表示成功;否则,可以设置为非零值,表示出现了错误或特定的退出原因。
	TerminateProcess(hprocess, 0);

	//这行代码调用CreateToolhelp32Snapshot函数来创建一个包含系统上所有进程的快照。
	//TH32CS_SNAPPROCESS标志指定快照应该包含进程信息。第二个参数0表示不需要过滤任何进程(即获取所有进程)。
	//第二个参数只有在变量堆或者模块的时候需要指定进程id,什么意思呢?
	//例如我们要遍历模块,我们可以指定一个进程id,然后我们可以遍历这个id的进程所加载的模块
	HANDLE hsnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 25124);

	//这行代码初始化了一个PROCESSENTRY32W结构体,该结构体用于接收Process32FirstW和Process32NextW函数检索到的进程信息。结构体的第一个成员被设置为结构体的大小,这是函数要求的。
	MODULEENTRY32W processentry = { sizeof(MODULEENTRY32W) };
	BOOL bsuccess = Module32FirstW(hsnap, &processentry);
	if (bsuccess)
	{
		//遍历所有得到的进程
		do
		{
			printf("%d %ls\n", processentry.th32ModuleID, processentry.szExePath);
		} while (Module32NextW(hsnap, &processentry));
	}
}

三、文件操作

这一章节只放操作文件句柄的注意事项,具体操作文件的API忘了直接问文心一言就行了。
CloseHandle和FindClose是Windows API中用于管理句柄的两个不同函数,它们各自有不同的用途和调用时机。
CloseHandle

用途:
CloseHandle函数用于关闭一个内核对象的句柄。这里的内核对象可以是文件、文件映射、进程、线程、事件、互斥量、信号量、命名管道等多种类型的对象。当不再需要访问某个内核对象时,应调用CloseHandle来关闭与该对象关联的句柄,以释放系统资源。

调用时机:

在完成对某个内核对象的所有操作后,应调用CloseHandle来关闭其句柄。
如果在创建对象时使用的是CreateFile、CreateProcess、CreateThread等函数,则应在这些对象不再需要时调用CloseHandle。

注意事项:

如果重复关闭同一个句柄,CloseHandle会失败,并返回FALSE。但是,这种操作通常不会导致程序崩溃,因为Windows内核会处理这种情况。
在多线程环境中,需要确保在合适的时机调用CloseHandle,以避免句柄泄露或竞争条件。

FindClose

用途:
FindClose函数专门用于关闭由FindFirstFile、FindFirstFileEx等文件搜索函数打开的搜索句柄。这些函数用于搜索与指定文件名匹配的文件或目录,并返回一个搜索句柄,用于后续的FindNextFile等调用。完成搜索后,应调用FindClose来关闭搜索句柄。

调用时机:

在使用FindFirstFile或FindFirstFileEx等函数开始搜索后,当完成搜索或需要中断搜索时,应调用FindClose来关闭搜索句柄。

注意事项:

不要尝试使用CloseHandle来关闭由FindFirstFile等函数返回的搜索句柄,因为CloseHandle并不专门处理这种类型的句柄,而FindClose是专门为此设计的。

总结

函数名 用途 调用时机 注意事项
CloseHandle 关闭内核对象的句柄 完成对内核对象的所有操作后 不要重复关闭同一个句柄,注意多线程环境下的调用时机
FindClose 关闭文件搜索句柄 完成文件搜索或需要中断搜索时 不要使用CloseHandle来关闭搜索句柄

在实际编程中,应根据需要操作的对象类型和当前的状态来选择合适的函数。如果处理的是内核对象(如文件、进程、线程等),则使用CloseHandle;如果处理的是文件搜索句柄,则使用FindClose。

四、进程间通信

先介绍第一种方式:COPY_DATA:
其实这种方式,就是向某一个窗口程序发送消息,要发送的消息包装在结构体里面。这个方式的缺点就是,程序不一定都有窗口,我们不能向一个没有窗口的程序发送消息吧。
发送消息的代码:

#include<Windows.h>

int main()
{
	HWND hwnd = FindWindow(NULL, L"Dialog");
	COPYDATASTRUCT data{ 0 };
	data.lpData = (LPVOID)L"12345";
	data.cbData = sizeof(L"12345");
	SendMessage(hwnd, WM_COPYDATA, 0, (LPARAM) & data);
	return 0;
}

接受消息的代码(使用这个程序前,先在资源文件创建一个窗口):

#include<Windows.h>
#include<TlHelp32.h>
#include<iostream>
#include"resource.h"

INT_PTR CALLBACK WinProc(
	HWND hwnd,
	UINT umsg,
	WPARAM wParam,
	LPARAM lParam
)
{
	switch (umsg)
	{
		case WM_COPYDATA:
		{
			PCOPYDATASTRUCT pCopyData = (PCOPYDATASTRUCT)lParam;
			MessageBox(hwnd, (LPCWSTR)pCopyData->lpData, L"Atten", MB_OK);
			break;
		}
		case WM_CLOSE:
		{
			EndDialog(hwnd, 0);
			break;
		}
		default:
		{
			return FALSE;
		}	
	}
}

int WINAPI WinMain(
	HINSTANCE hInstance,
	HINSTANCE hPreInstance,
	LPSTR	  lpCmdLine,
	int		  nShowCmd
)
{
	DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, WinProc);
}

第二种方式,就是使用邮槽(就是家门口的那个邮箱)。一个进程可以创建一个邮槽,其他进程给这个邮槽发消息。要注意邮槽的使用是单向的,只能创建该邮槽的进程可以读取,其他进程只能给这个邮槽写入,消息被写入以后是以队列的方式保存的。

发送消息代码:

#include<Windows.h>

int main()
{
	HANDLE hMail = CreateFile(L"\\\\.\\mailslot\\study", GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hMail == INVALID_HANDLE_VALUE)
	{
		MessageBox(0, L"Open Fail!", L"Atten", MB_OK);
		return 0;
	}
	WCHAR buff[] = L"study";
	DWORD sz;
	WriteFile(hMail, buff, sizeof(L"study"), &sz, NULL);
	CloseHandle(hMail);
	return 0;
}

邮槽接收消息代码:

#include<Windows.h>
#include<TlHelp32.h>
#include<iostream>
#include"resource.h"

int main()
{
	HANDLE hMail = CreateMailslot(L"\\\\.\\mailslot\\study", 100, MAILSLOT_WAIT_FOREVER, NULL);
	if (hMail==INVALID_HANDLE_VALUE)
	{
		MessageBox(0, L"Create Fail!", L"Atten", MB_OK);
		return 0;
	}
	WCHAR buff[100];
	DWORD readsize;
	//我们可以通过ReadFile的返回值,判断还有没有文件可以读,以此判断我们是否已经访问完邮槽的所有文件
	ReadFile(hMail, buff, 100, &readsize, NULL);
	MessageBox(0, buff, L"Atten", MB_OK);
	CloseHandle(hMail);
}

五、线程

我们可以这样理解线程:进程是一个分配资源的一个单位,进程里面有内存,内存里面有数据,有代码。那么,谁来帮我们执行这些代码,去处理这些数据呢?是的,就是线程。

线程的代码可以看这两篇文章:
https://www.cnblogs.com/yyjy-edu/p/15038869.html
https://zhuanlan.zhihu.com/p/704395844

关于线程的暂停计数问题:
https://www.cnblogs.com/fangyukuan/archive/2010/09/02/1816239.html

同时,对于使用函数CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,0),第二个参数只能指定为0,就是获取所有的线程信息。如果要过滤某个线程,只能自己写循环去过滤。

对于GetCurrentProcess和GetCurrentThread函数,获得的句柄,都是伪句柄。也就是说,如果我们把父线程的句柄当作参数传给子线程,然后在子线程里面使用参数和GetCurrentThread函数获取父线程的信息,这样是不可行的。原因和解决办法看这个:
https://blog.csdn.net/cyg0810/article/details/8243658

然后,我们需要解决线程同步的问题。
我们举一个例子来说明线程同步问题是什么。
假设有一个全局变量global,现在有两个线程,两个线程都对global执行global++的操作,那么,对应的汇编代码就是类似这个样子(实际上肯定不是这个汇编代码,这里只是举例说明):

mov rax global;
add rax 1;
mov global rax;

这样会出现什么情况呢?假设1号线程执行了前两条汇编语句,就因为CPU轮转等原因被中断了,但是2号线程还在执行,2号线程可能完整执行完三条汇编语句,这时候1号线程又开始执行了,1号线程把自己的rax值赋值给了global。由于global是全局变量,1号线程把它修改了,但这并不是在2号线程的结果上再加一,而是用1号线程之前的值把2号线程的计算结果给覆盖掉了,这样子就会导致多线程并不会按照我们预想的情况输出结果。我们要解决的线程同步问题就是这个。
解决的方法和代码看这两个:
https://www.jb51.net/article/267248.htm
https://www.cnblogs.com/hlxs/archive/2013/03/31/2991752.html#top

我们可以使用信号量,来实现限制应用多开。具体思路是,我们打开程序,第一步就使用OpenSemaphore函数打开线程,如果没有的话那就创建线程。然后等到信号量的数量为0了,还要被释放的话,那就直接退出程序。

六、异步IO

关于异步IO的知识,直接看这个吧:
https://blog.csdn.net/smallfish10086/article/details/136478188

  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值