多线程编程
一丶多线程安全.
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.多线程的同步与互斥
多线程同步:
同步就是两个线程协同做一件事情.
多线程互斥
多个线程是排他性的.排着队访问一个资源.
比如我们上面的Ring3的例子就是互斥的. 每个线程必须互斥的访问.
当进入线程的时候.你没有锁的时候就要等那个线程对全局资源访问完毕你的线程才能执行你的代码.
同步就是我操作完变量.我发一个信号.告诉另一个线程可以进行操作那个变量了.
如以下代码:
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
主线程等待子线程创建完毕.
在Ring3我们可以通过WaitForsingObject来等待.
在内核中可以使用 KeWaitForSingleObject()来等待.
但是注意,keWaitForSingleObject只是等待一个Object对象.
而不像跟Ring3一样.直接把线程句柄拿过来等待.
所以我们还需要一组函数.
ObReferenceObjectByHandle(); //根据HANDLE.传出Object对象
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;
}
2.同步与互斥,以及等待函数.
互斥
在内核中有三种互斥锁. 互斥就是AB只能有一个人访问相同的资源.
自旋锁 KSPIN_LOCK
资源执行体锁 ERESOURCE
快速互斥 FAST_MUTEX ,也有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**
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函数来关闭.