64位内核开发第八讲.多线程开发.同步和互斥简介
多线程编程
一丶多线程安全.
1.什么是多线程
现在的程序基本是很多个线程.不想以前一样.而进程和线程的关系就是
一对多的关系.
进程做外键放到线程中. 数据关系是这样的.
简单理解为 进程就是一个 容体. 里面的线程就是它进存储的任务.
一个线程只能做一个事情. 多个线程可以做多个事情.
2.超线程
超线程是一个硬件的CPU技术.是用来模拟双核的.inter所研发的.
以前是用软件模拟的. 英文是HT技术.全名就是 Hyper-Threading.
超线程的意思就是在同一个时刻.应用层可以使用CPU的不同部分的.
3.多线程引入的问题.
同步问题.
当线程访问全局变量.以及资源的时候就会出现问题.
比如我们要对一个变量++ .正常来说会 1 + 1 = 2就是等于2.
而多线程就会出现数字不一样的情况.
如下: ring3代码演示.
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
int g_Value = 0;
DWORD MyThread(LPVOID lPparame)
{
for (DWORD i = 0; i < 10000000; i++)
{
g_Value++
}
return 0;
}
int main()
{
HANDLE hThread[2];
hThread[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThread, NULL, 0, NULL);
hThread[1]= CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThread, NULL, 0, NULL);
WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
printf("%d", g_Value);
system("pause");
return 0;
}
结果:
数值每次都是随机的.
所以产生了错误.
解决方法就是使用同步函数.
如下:
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
__int64 g_Value = 0;
int g_Count = 0;
HANDLE g_hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
DWORD MyThread(LPVOID lPparame)
{
WaitForSingleObject(g_hEvent, INFINITE);
for (DWORD i = 0; i < 10000000; i++)
{
g_Value++;
}
//
SetEvent(g_hEvent);
return 0;
}
int main()
{
HANDLE hThread[2];
hThread[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThread, NULL, 0, NULL);
hThread[1]= CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThread, NULL, 0, NULL);
WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
printf("%d", g_Value);
system("pause");
return 0;
}
使用同步之后就不会出现这类问题了.
如果你的线程使用的是局部变量. 你的程序是多线程安全的.就是各个线程
都有自己的局部变量. 不会影响.
但是如果你使用全局资源.就是多线程不安全的.必须使用同步函数(加锁)
这样才会保证你的程序是安全的.
4.多线程的同步与互斥
同步的概念:
同步就是两个线程协同做一件事情.
我在A线程执行完毕功能了,此时我要告诉B线程我完成了,B线程收到后就开始执行,B线程执行完毕之后就告诉A线程我也执行完了。此时A线程继续干事情。
为什么要同步: 举个例子 假设A线程 在写文件,文件里面设置一个标记为 1 设置完毕之后就告诉B线程 我设置完了,而B线程收到后就会检查 A设置文件的标记,如果为1 执行xxx功能,而B线程执行完毕xxx功能,要给文件写一个 3标记(3代表设置成功) 继续告诉A我设置完了,当A接收到信息之后就会检查文件,是否有3标记,如果有就设置为2 继续通知B线程,B线程拿到2了继续执行xxx功能。 这就是一个很简单的同步功能,我完成一件事我告诉你你等待我,等待我了之后就开始进行功能执行。
互斥
互斥就是多个线程都要访问一个资源,这个资源可以是一个文件 可以是一个全局变量等等。
多个线程是排他性的.排着队访问一个资源.
同步就是我操作完变量.我发一个信号.告诉另一个线程可以工作了。下面是同步来模拟互斥的场景。
如以下代码:
DWORD MyThread(LPVOID lPparame)
{
for (DWORD i = 0; i < 200; i++)
{
WaitForSingleObject(g_hEvent, INFINITE);
g_Value++;
SetEvent(g_hEvent); //告诉另一个线程可以操作了.
}
//
return 0;
}
这样两个线程都可以同时操作这个变量. 你操作完毕我告诉另一个线程你能操作了.
二丶内核线程
内核中创建线程很简单.
PsCreateSystemThread进行创建的.
跟ring3的CreateThread类似.
如下演示:
PsCreateSystemThread(&hThread, 0, NULL, (HANDLE)0, NULL, MyProc, MyProcParam);
具体查询MSDN
我们创建线程要注意IRQL
级别.
如果作为一个普通线程.运行在的是 PASSIVE
级别.
如果当前线程IRQL抬高.可以降到PASSIVE
级别.
如下:
if (KeGetCurrentIrql() != PASSIVE_LEVEL)
{
KfRaiseIrql(PASSIVE_LEVEL);
}
KfRaiseIrql.代表降低级别.
KeLowerIrql(); 则是恢复IRQL
请注意 微软并不让我们直接恢复IRQL 而是你要先提升IRQL 然后在恢复IRQL 。上面的代码大家请谨慎运行。不建议使用。
主线程等待子线程创建完毕.
在Ring3我们可以通过WaitForsingObject来等待.
在内核中可以使用 KeWaitForSingleObject() 来等待.
但是注意,keWaitForSingleObject只是等待一个Object对象.
而不像跟Ring3一样.直接把线程句柄拿过来等待.
所以我们还需要一组函数.
ObReferenceObjectByHandle(); //根据HANDLE.传出Object对象
ObReferenceObjectByName();// 未公开请查询网络文档使用
ObDereference();//取消对象引用.
完整代码如下:
#include <ntddk.h>
#include <ntstrsafe.h>
DRIVER_UNLOAD DriverUnLoad;
KSTART_ROUTINE MyThredProc;
void DriverUnLoad(PDRIVER_OBJECT pDriverObject)
{
KdPrint(("驱动卸载"));
}
void MyThredProc(
PVOID StartContext
)
{
DWORD dwCount = 0;
while ((dwCount++) <= 10)
{
KdPrint(("内核线程输出中.第%d次", dwCount));
}
}
NTSTATUS InitRun()
{
//创建线程
HANDLE hThread;
PVOID ppObject = NULL;
NTSTATUS ntSttus;
ntSttus = PsCreateSystemThread(&hThread,
0,
NULL,
(HANDLE)0,
NULL,
MyThredProc, //你创建函数的回调地址
NULL); //给你创建函数传递的参数值
//等待线程创建完毕.
if (!NT_SUCCESS(ntSttus))
return ntSttus;
//判断IRQL 降低权限 请不要直接使用 测试程序使用
if (KeGetCurrentIrql() != PASSIVE_LEVEL)
ntSttus = KfRaiseIrql(PASSIVE_LEVEL);
ntSttus = ObReferenceObjectByHandle(&hThread,
THREAD_ALL_ACCESS,
NULL,
KernelMode,
&ppObject,
NULL);
if (!NT_SUCCESS(ntSttus))
return ntSttus;
//等待对象
KeWaitForSingleObject(ppObject,
Executive,
KernelMode,
FALSE,
NULL);
//因为使用了 ObRefrenceObjceByHandle.所以引用对象+1了.现在需要引用对象-1
ObDereferenceObject(ppObject);
//关闭线程句柄
ZwClose(hThread);
return ntSttus;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegPath)
{
NTSTATUS ntStatus = 0;
pDriverObject->DriverUnload = DriverUnLoad;
ntStatus = InitRun();
return ntStatus;
}
降低IRQL 还有文章 请查询MSDN
C28111 警告 - |驱动程序微软文档 (microsoft.com)
void driver_utility()
{
// running at APC level
KFLOATING_SAVE FloatBuf;
if (KeSaveFloatingPointState(&FloatBuf))
{
KeLowerIrql(PASSIVE_LEVEL);
...
KeRaiseIrql(APC_LEVEL, &old);
KeRestoreFloatingPointState(&FloatBuf);
}
}
2.同步与互斥,以及等待函数.
互斥
在内核中有以下几种种互斥锁. 互斥就是AB只能有一个人访问相同的资源.
-
自旋锁
KSPIN_LOCK
-
资源执行体锁
ERESOURCE
-
快速互斥
FAST_MUTEX
效率太低微软放弃 -
KGUARDED_MUTEX
受保护的互斥锁 一般使用这个
同步:
A 跟 B 协作执行. A做一件事告诉B. B去做另一个.
-
KEVENT
事件 -
KSEMAPHORE
信号量KMUTEX
上面是可等待对象.都可以使用函数来等待.
KeWaitForSingleObject
等待的对象还包括
KPROCESS KQUEUE KMUTANT KSEMAPHORE KTHREAD KTIMER ...
FileObject DriverObject 是不可以转换的. 凡是能等待的内核对象.内核头部都会带有 Dispatcher Header结构的
如下:
typedef struct _KEVENT {
DISPATCHER_HEADER Header;
} KEVENT, *PKEVENT, *PRKEVENT;
KeWaitForSingleObject 最后一个参数是等待时间. 但是注意一点.他是 负数. 而不是跟ring3一样给个秒数就行. 0不等待. NULL 无线等待.
等待多个对象则使用
KeWaitForMutiipleObject
2.1.1 使用 KSPIN_LOCK 例子
void KstartRoutine_spinlock1(PVOID StartContext)
{
KIRQL oldIrql;
KeAcquireSpinLock(&g_spinlock, &oldIrql);
ULONG uindex = 0;
for (uindex = 0; uindex < 100000000; uindex++)
{
g_textNumber++;
}
KeReleaseSpinLock(&g_spinlock, oldIrql);
PsTerminateSystemThread(STATUS_SUCCESS);
}
void KstartRoutine_spinlock2(PVOID StartContext)
{
KIRQL oldIrql;
KeAcquireSpinLock(&g_spinlock, &oldIrql);
ULONG uindex = 0;
for (uindex = 0; uindex < 100000000; uindex++)
{
g_textNumber++;
}
KeReleaseSpinLock(&g_spinlock, oldIrql);
PsTerminateSystemThread(STATUS_SUCCESS);
}
// Thread and Event syn
VOID ThreadManger_spinlock()
{
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
HANDLE hThreadHandle1 = NULL;
HANDLE hThreadHandle2 = NULL;
UNICODE_STRING uStrTest = {0};
RtlInitUnicodeString(&uStrTest, L"Hello World");
PKTHREAD pThread1 = NULL;
PKTHREAD pThread2 = NULL;
ntStatus = PsCreateSystemThread(
&hThreadHandle1,
0,
NULL,
NULL,
NULL,
KstartRoutine_spinlock1,
NULL);
ntStatus = PsCreateSystemThread(
&hThreadHandle2,
0,
NULL,
NULL,
NULL,
KstartRoutine_spinlock2,
NULL);
if (!NT_SUCCESS(ntStatus))
{
}
ObReferenceObjectByHandle(hThreadHandle1, GENERIC_ALL, *PsThreadType, KernelMode, (PVOID *)&pThread1, NULL);
ObReferenceObjectByHandle(hThreadHandle2, GENERIC_ALL, *PsThreadType, KernelMode, (PVOID *)&pThread2, NULL);
ObDereferenceObject(pThread1);
ObDereferenceObject(pThread2);
KeWaitForSingleObject(pThread1, Executive, KernelMode, FALSE, NULL);
KeWaitForSingleObject(pThread2, Executive, KernelMode, FALSE, NULL);
ZwClose(hThreadHandle1);
ZwClose(hThreadHandle2);
}
直接在DriverEntry中调用 ThreadManger_spinlock
即可。DbgPrint
输出变量即可看到两个线程操作同一个变量是同步执行的。
2.2 内核中使用Event示例
void KstartRoutine_event1(PVOID StartContext)
{
KeWaitForSingleObject(&g_MyEvent, Executive, KernelMode, FALSE, NULL);
ULONG uindex = 0;
for (uindex = 0; uindex < 100000000; uindex++)
{
g_textNumber++;
}
KeSetEvent(&g_MyEvent, IO_NO_INCREMENT, FALSE);
PsTerminateSystemThread(STATUS_SUCCESS);
}
void KstartRoutine_event2(PVOID StartContext)
{
KeWaitForSingleObject(&g_MyEvent, Executive, KernelMode, FALSE, NULL);
ULONG uindex = 0;
for (uindex = 0; uindex < 100000000; uindex++)
{
g_textNumber++;
}
KeSetEvent(&g_MyEvent, IO_NO_INCREMENT, FALSE);
PsTerminateSystemThread(STATUS_SUCCESS);
}
// Thread and Event syn
VOID ThreadManger_Event()
{
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
HANDLE hThreadHandle1 = NULL;
HANDLE hThreadHandle2 = NULL;
UNICODE_STRING uStrTest = {0};
RtlInitUnicodeString(&uStrTest, L"Hello World");
PKTHREAD pThread1 = NULL;
PKTHREAD pThread2 = NULL;
ntStatus = PsCreateSystemThread(
&hThreadHandle1,
0,
NULL,
NULL,
NULL,
KstartRoutine_event1,
NULL);
ntStatus = PsCreateSystemThread(
&hThreadHandle2,
0,
NULL,
NULL,
NULL,
KstartRoutine_event2,
NULL);
if (!NT_SUCCESS(ntStatus))
{
}
// wait thread
ObReferenceObjectByHandle(hThreadHandle1, GENERIC_ALL, *PsThreadType, KernelMode, (PVOID *)&pThread1, NULL);
ObReferenceObjectByHandle(hThreadHandle2, GENERIC_ALL, *PsThreadType, KernelMode, (PVOID *)&pThread2, NULL);
ObDereferenceObject(pThread1);
ObDereferenceObject(pThread2);
KeWaitForSingleObject(pThread1, Executive, KernelMode, FALSE, NULL);
KeWaitForSingleObject(pThread2, Executive, KernelMode, FALSE, NULL);
KeWaitForSingleObject(&g_MyEvent, Executive, KernelMode, FALSE, NULL);
// wait event
ZwClose(hThreadHandle1);
ZwClose(hThreadHandle2);
}
3.内核中使用等待函数
在Ring3的时候.我们可以直接使用Sleep等函数等待. 现在内核中也有提供
函数原型
KeDelayExecutionThread(KPROCESSOR_MOE waitModel,BOOLEAN Alertable, PLARGE_INTEGER interval)
三个参数的意思分别是 等待的模式 是否允许线程报警,用于重新唤起线程. 以及等待时间
关于前两个.如果内核模式下. 分别填写为 KernelModel FALSE即可.
最后一个是等待时间. 内部会自动进行转化.
所以等待时间我们 要给负数 负数 * 1000就是你要等待的时间
如下:
LARGE_INTEGER sec;
sec.Quadpart = -10 * 1000;
KeDelayExecutionThread(KernelModel,FALSE,&sec); //等待10秒.
4. 线程的结束
内核中使用的线程并不会自己结束. 必须在线程内部自己调用 PsTerminateSystemThread来结束. 句柄也必须由 ZwClose函数来关闭.
三丶 互斥 使用 (简单说明)
3.1 互斥的使用
-
快速互斥体
ExInitializeFastMutex() //初始化快速互斥体 ExAcquireFastMutex() //获取锁 ExTryToAcquireFastMutex() //尝试获取锁而不暂停此线程 ExReleaseFastMutex() //释放锁
例子:
void KstartRoutine_FastMutex(PVOID StartContext)
{
ULONG uindex = 0;
for (uindex = 0; uindex < 100000000; uindex++)
{
ExAcquireFastMutex(&g_fastMutex);
g_textNumber++;
ExReleaseFastMutex(&g_fastMutex);
}
PsTerminateSystemThread(STATUS_SUCCESS);
}
void Thread_FastMutex()
{
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
HANDLE hThreadHandle1 = NULL;
HANDLE hThreadHandle2 = NULL;
UNICODE_STRING uStrTest = {0};
RtlInitUnicodeString(&uStrTest, L"Hello World");
PKTHREAD pThread1 = NULL;
PKTHREAD pThread2 = NULL;
ntStatus = PsCreateSystemThread(
&hThreadHandle1,
0,
NULL,
NULL,
NULL,
KstartRoutine_FastMutex,
NULL);
ntStatus = PsCreateSystemThread(
&hThreadHandle2,
0,
NULL,
NULL,
NULL,
KstartRoutine_FastMutex,
NULL);
if (!NT_SUCCESS(ntStatus))
{
}
// wait thread
ObReferenceObjectByHandle(hThreadHandle1, GENERIC_ALL, *PsThreadType, KernelMode, (PVOID *)&pThread1, NULL);
ObReferenceObjectByHandle(hThreadHandle2, GENERIC_ALL, *PsThreadType, KernelMode, (PVOID *)&pThread2, NULL);
KeWaitForSingleObject(pThread1, Executive, KernelMode, FALSE, NULL);
KeWaitForSingleObject(pThread2, Executive, KernelMode, FALSE, NULL);
ObDereferenceObject(pThread1);
ObDereferenceObject(pThread2);
// wait event
ZwClose(hThreadHandle1);
ZwClose(hThreadHandle2);
}
-
受保护的互斥体
KeInitializeGuardedMutex() //同快速互斥注释一样 KeAcquireGuardedMutex() KeTryToAcquireGuardedMutex() KeReleaseGuardedMutex()
如果代码在APC_LEVEL 级别运行 还可以使用如下方法
KeAcquireGuardedMutexUnsafe()
KeReleaseGuardedMutexUnsafe()
坚持两字,简单,轻便,但是真正的执行起来确实需要很长很长时间.当你把坚持两字当做你要走的路,那么你总会成功. 想学习,有问题请加群.群号:725864912(收费)群名称: 逆向学习小分队 群里有大量学习资源. 以及定期直播答疑.有一个良好的学习氛围. 涉及到外挂反外挂病毒 司法取证加解密 驱动过保护 VT 等技术,期待你的进入。
详情请点击链接查看置顶博客 https://www.cnblogs.com/iBinary/p/7572603.html
本文来自博客园,作者:iBinary,未经允许禁止转载 转载前可联系本人.对于爬虫人员来说如果发现保留起诉权力.https://www.cnblogs.com/iBinary/p/11026655.html
欢迎大家关注我的微信公众号.不定期的更新文章.更新技术. 关注公众号后请大家养成 不白嫖的习惯.欢迎大家赞赏. 也希望在看完公众号文章之后 不忘 点击 收藏 转发 以及点击在看功能. QQ群: