内核线程之间的同步,R3线程和内核线程的同步

作用

当线程1需要等待线程2处理一些事情的时候就用到这种线程同步机制

知识点

1、事件,信号灯,互斥体都是线程之间通讯的方式,事件的通讯能满足大部分需求。
2、休眠函数

	LARGE_INTEGER sleeptime = {
    
    0};//定义一个休眠的时间

	sleeptime.QuadPart = -100 * 10 * 100 * 3;//这里是3秒时间

	while (1)
	{
    
    
		//这个函数相当于R3程序的 Sleep 让线程休眠
		KeDelayExecutionThread(KernelMode, FALSE, &sleeptime);//线程休眠3秒钟
	}

3、内核线程例程 需要添加

PsTerminateSystemThread(0);

否则即使代码全部运行完毕,这个内核线程也不会结束。

4、如果一个事件被设置为通知事件NotificationEvent

KeInitializeEvent(&gkevent,NotificationEvent,FALSE);

,那么当这个事件被设置成激发态以后,如果还需要用到这个事件进行同步,那么需要开发人员手动设置为不激发状态!设置的代码KeResetEvent(&gkevent);,一般只使用一次就用这种

5、如果某个事件设置为同步事件SynchronizationEvent
那么当这个事件遇到KeWaite等待通过然后系统会自动将事件重置为未激发态

内核层线程与内核层的同步

实际就是线程1等待线程2处理完一些事情,线程1在继续做其它事情

全局定义:

KEVENT gkevent = {
    
     0 };//线程同步用的数据结构,同步就靠这个结构,这个很重要

BYTE mmcode[10] = {
    
    0};//这个是演示内存线程之间同步数据用的

线程1:
这个线程从入口函数启动,这个函数会启动线程二

在这里插入代码片

/// <summary>
/// 内核线程1
/// </summary>
/// <param name="context"></param>
VOID kernelThread1(PVOID context)
{
    
    
	//&gkevent  线程同步用的数据结构的指针
	//参数2 事件 NotificationEvent是一种通知事件,只执行一次,需要手动恢复;SynchronizationEvent是同步事件,系统自动恢复通知
	//参数3 是否为激发态 
	//KeInitializeEvent(&gkevent,NotificationEvent,FALSE);
	KeInitializeEvent(&gkevent, SynchronizationEvent, FALSE);//这种系统会自动重置未激发态
	
	HANDLE hthread = NULL;

	//创建一个系统线程
	//参数1 &hthread是句柄指针
	//参数2 是访问控制码,一般是设置0 ,是全部权限
	//参数3 NULL 是描述符
	//参数4是我们这个线程所属的进程句柄
	//参数5结果指针
	//参数6 kernelThread2 需要创建的线程进程含函数,这个函数是自己写的
	//最后一个参数是 ,参数6函数 的参数
	NTSTATUS status = PsCreateSystemThread(&hthread, 0, NULL, NULL, NULL, kernelThread2, (PVOID)&gkevent);

	ZwClose(hthread); //关闭句柄

	while (1)
	{
    
    
		//参数1 &gkevent要等待的内核对象事件
		//参数2 Executive 一般默认这个
		//参数3 KernelMode等待的环境
		//参数4 FALSE 我们这个等待线程是否休眠
		KeWaitForSingleObject(&gkevent, Executive, KernelMode, FALSE, NULL);
		//到这里kernelThread2 进程就执行了,处于激发状态
		//KeResetEvent(&gkevent);//等待kernelThread2 重置为未激发态,设置为未激态,否则 kernelThread2只能执行一次,NotificationEvent这种通知需要加这个才能重置
		
		for (int i = 0 ; i < 10 ; i++)
		{
    
    
			DbgPrint("Mmcode <%x>\n", mmcode[i]);
		}

		//DbgPrint("Event Has be seted\n");
	}

	PsTerminateSystemThread(0);
}

线程2:
线程1要等待这个线程处理一些事情

/// <summary>
/// 内核线程2
/// </summary>
/// <param name="context"></param>
VOID kernelThread2(PVOID context)
{
    
    
	LARGE_INTEGER sleeptime = {
    
    0};//定义一个休眠的时间

	PKEVENT pevent = (PKEVENT)context;

	sleeptime.QuadPart = -1000 * 10 * 100 * 3;//这里是3秒时间

	PVOID apiddr = NULL;

	UNICODE_STRING apiname = {
    
     0 };

	RtlInitUnicodeString(&apiname, L"NtCreateFile");

	apiddr = MmGetSystemRoutineAddress(&apiname);

	while (1)
	{
    
    
		//这个函数相当于R3程序的 Sleep 让线程休眠
		KeDelayExecutionThread(KernelMode, FALSE, &sleeptime);//线程休眠3秒钟,就是每隔三秒设置线程为激发态
		
		DbgPrint("Set Event\n");

		//---要做的事情开始
		RtlZeroMemory(mmcode,10);//清零内存空间10个字节
		
		RtlCopyMemory(mmcode,apiddr,10);//拷贝apiddr  10个字节

		//***要做的事情结束
		

		//pevent 指针
		//IO_NETWORK_INCREMENT 是否增加这个内核对象的IO引用计数 这里NO不增加
		//FALSE 是否等待结果
		KeSetEvent(pevent, IO_NO_INCREMENT, FALSE);//设置线程为激发态
	}

	PsTerminateSystemThread(0);

}

入口函数启动线程1代码:

	//-----创建一个内核线程进程开始----
	HANDLE hthread = NULL;
	
	 status = PsCreateSystemThread(&hthread, 0, NULL, NULL, NULL, kernelThread1, (PVOID)&gkevent);

	//********创建一个内核线程进程结束*******

内核层线程与应用层线程的同步

应用场景

内核层与应用层通过一个文件,或注册表来交互数据

执行流程:

在应用层打开驱动设备,然后创建一个事件,再把这个事件的句柄通过DeviceIoControl(hdevice, IOCTL_MUL, &a,4,&b,4,&bread,NULL);传给内核层驱动,当驱动收到这个请求后,把这个句柄转为对象,并且马上创建一个内核线程,在线程里面无限循环的等待 这个事件被设置为激发态,当设置为激发态后会做一个简单的操作交换打印出DbgPrint(“This Requset come from R3 Routine\n”);,打印完后又继续进行等待,当应用层把事件传入后就创建一个线程,应用层这个线程每隔1.5秒设置一下事件SetEvent(hevent); //设置事件为激发态

应用层R3代码

R3应用层主程序里面:

 //参数2FALSE 决定是一个同步事件件
    HANDLE hevent = CreateEvent(NULL, FALSE, FALSE, NULL);//创建一个事件

    if (hevent == INVALID_HANDLE_VALUE)
    {
    
    
        //如果是无效句柄 就退出
        printf("Create Event Faile\n");
        CloseHandle(hdevice);//关闭设备句柄
        system("pause");
        return 0;

    }

    DeviceIoControl(hdevice, IOCTL_MUL, &hevent, sizeof(HANDLE), &b, 4, &bread, NULL);//把句柄传到内核

    HANDLE thread =(HANDLE)_beginthread(TestThread, 0, &hevent);//执行线程

R3应用层 线程函数

/// <summary>
/// 与内核通讯的线程函数
/// </summary>
/// <param name="context"></param>
void TestThread(PVOID context)
{
    
    
    HANDLE hevent = *(HANDLE*)context;

    while (1)
    {
    
    
        printf("Set Kernel Event Handle %x\n",hevent);

        //
        //需要和内核交互数据填好在这个位置
        //数据可以填入与内核共享的文件、或注册表位置等

        
        SetEvent(hevent); //设置事件为激发态, 在内核会等待这个事件,系统会自动把这个事件重置为无信号

        Sleep(1500);//每隔1.5秒发起一个设置请求

    }

}

内核层需要的全局变量:

PKEVENT pkernelevent = NULL;// 应用层句柄 转化为对象后保存的位置

内核函数执行线程的代码:
通过自定义的派遣函数,来执行内核线程与应用层程序通信

/*
MyControl 自定义控制 函数

*/
NTSTATUS MyControl(PDEVICE_OBJECT pdevice, PIRP pirp)
{
    
    
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device has be Controled\n");

	
	PIO_STACK_LOCATION pstack = IoGetCurrentIrpStackLocation(pirp);

	//获取控制码功,就是从应用层传过来的一窜数字,用这个数字来判断需要执行下面的哪个内核功能
	ULONG iocode = pstack->Parameters.DeviceIoControl.IoControlCode;

	//输入的长度
	ULONG inlen = pstack->Parameters.DeviceIoControl.InputBufferLength;

	//输出的长度
	ULONG outlen = pstack->Parameters.DeviceIoControl.OutputBufferLength;

	//操作信息

	ULONG ioinfo = 0;

	DbgPrint("--kernel ControlCode %d--\n", iocode);
	switch (iocode)
	{
    
    
	
	case IOCTL_MUL:
	{
    
    
		//自定义的乘法
		
		//取出数据
		DWORD indata = *(PDWORD)pirp->AssociatedIrp.SystemBuffer;

		DbgPrint("--Kernel Indata %d--\n",indata);

		HANDLE hevent = (HANDLE)indata;
		
		//ObReferenceObjectByHandle它可以通过一个句柄查找到一个内核对象,可以把句柄转化为内核对象
		//参数1 hevent 句柄
		//参数2 EVENT_MODIFY_STATE 访问码
		//参数3 *ExEventObjectType 对象类型
		//&pkernelevent是一个全局变量, 转化为对象后保存的位置
		status = ObReferenceObjectByHandle(hevent,EVENT_MODIFY_STATE,*ExEventObjectType,KernelMode,&pkernelevent,NULL);

		if (NT_SUCCESS(status))
		{
    
    
			//ObReferenceObjectByHandle 执行成功后 引用计数会加1 ,当引用计数为0的时候内核对象会销毁
			ObDereferenceObject(pkernelevent);//销毁引用完成的内核对象

			HANDLE hthread = NULL;

			status = PsCreateSystemThread(&hthread, 0, NULL, NULL, NULL, kernelThread3,NULL);//线程创建


		}

		indata = indata * 5;
		
		*(PDWORD)pirp->AssociatedIrp.SystemBuffer = indata;

		ioinfo = 666;
		break;
		
		
		
	}

	default:
		//如果不是我们要的操作符合,就判断为失败
		status = STATUS_UNSUCCESSFUL;
		ioinfo = 0;
		break;
	}

	pirp->IoStatus.Status = status; //请求成功
	pirp->IoStatus.Information = ioinfo;//信息处理了0字节
	IoCompleteRequest(pirp, IO_NO_INCREMENT);//完成请求
	return STATUS_SUCCESS;

}

内核层线程函数:

/// <summary>
/// 与应用层同步的线程
/// </summary>
/// <param name="context"></param>
VOID kernelThread3(PVOID context)
{
    
    
	LARGE_INTEGER timeout = {
    
     0 };

	timeout.QuadPart = -1000 * 10 * 1000 * 15;

	NTSTATUS status = STATUS_SUCCESS;

	while (1)
	{
    
    
		//最后一个参数 &timeout 是等待超时时间,就是等前台应用30秒还没有响应 ,就不等待了,例如前台应用关闭了就不可能接到信息
		status=KeWaitForSingleObject(pkernelevent,Executive,KernelMode,FALSE,&timeout);//到这里就是执行前台应用设置的线程
		if (status==STATUS_TIMEOUT) {
    
    
			//超时后就退出
			DbgPrint("Time Out\n");
			break;
		}

		//
		//在这个位置 取出R3的数据,然后进行读写,再把数据写回,共享文件、或注册表
		//
		DbgPrint("This Requset come from R3 Routine\n");
	}

	PsTerminateSystemThread(0);
}

效果内核和应用层同步打印信息

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/lygl35/article/details/112918779