在操作系统的最小执行单元就是线程,在内核中线程的概念更加容易看出来。比如说有的时候需要使用线程来完成一些任务,可是这些任务的工作量过大的时候系统处理这些任务就必须停下来等待。而等待的过程就大大的浪费了cup宝贵的时间,所以这个时候利用多线程去处理是最好的方法。
在驱动里生成的线程一般是系统线程。PsCreateSystemThread 函数默认情况下创建的却是一个系统进程,它属于进程名为System,PID=4。同时PsCreateSystemThread 也可以创建用户线程,这取决于它的第四个参数ProcessHandle,如果它为空,则创建系统线程;如果它是一个进程句柄,则创建的是属于该指定进程的用户线程。
第六个参数StartRoutine是新线程的运行地址;第七个参数StartContext是新线程接收的参数。
线程函数是一个非常重要的部分,它决定了该线程具有什么样的功能。线程函数必须按照如下规范声明:
VOID ThreadProc(IN PVOIDcontext);
线程有个非常重要的问题:同步问题,虽然多线程并不是真正的并发运行,但由于 CPU分配的时间片很短,看起来它们就像是并发运行的一样。所以我们要保证线程的同步。这里我们使用简单的KEVENT事件对象进行同步。在使用 KEVENT事件对象前,需要首先调用内核函数 KeInitializeEvent对其初始化
VOID KeInitializeEvent(
IN PRKEVENT Event,
IN EVENT_TYPE Type,
IN BOOLEAN State);
第一个参数 Event是初始化事件对象的指针;第二个参数Type表明事件的类型。事件分两种类型:一类是“通知事件”,对应参数为 NotificationEvent,另一类是“同步事件”,对应参数为 SynchronizationEvent;第三个参数State 如果为TRUE,则事件对象的初始化状态为激发状态,否则为未激发状态。如果创建的事件对象是“通知事件”,当事件对象变为激发态时,需要我们手动将其改回未激发态。如果创建的事件对象是“同步事件”,当事件对象为激发态时,如果遇到相应的 KeWaitFor****等内核函数,事件对象会自动变回到未激发态。
设置事件的函数是 KeSetEvent,可通过该函数修改事件对象的状态。
下面用一段简单的代码来说明问题。
VOID
MyCreateThread()
{
HANDLE hThread;
NTSTATUS status;
UNICODE_STRING ustrTest;
// 初始化事件
KeInitializeEvent(&kEvent, SynchronizationEvent, TRUE);
RtlInitUnicodeString(&ustrTest, L"Just for test!");
// 创建线程
status = PsCreateSystemThread(&hThread, 0, NULL, NULL, NULL, MyThreadFunc, (PVOID)(&ustrTest));
if (!NT_SUCCESS(status))
{
KdPrint(("[Test] CreateThread Failed!"));
}
ZwClose(hThread);
// 等待事件
KeWaitForSingleObject(&kEvent, Executive, KernelMode, FALSE, 0);
}
VOID
MyThreadFunc(
IN PVOID context
)
{
PUNICODE_STRING str = (PUNICODE_STRING)context;
KdPrint(("[Test] %d : %wZ", (int)PsGetCurrentProcessId(), str));
// 设置事件对象
KeSetEvent(&kEvent, 0, FALSE);
// 结束线程
PsTerminateSystemThread(STATUS_SUCCESS);
}
程序运行如图: