事件KEVENT

一些读者可能熟悉“事件驱动”编程技术。但是这里的“事件”与之不同。内核中的事件是一个数据结构。这个结构的指针可以当作一个参数传入一个等待函数中。如果这个事件不被“设置”,则这个等待函数不会返回,这个线程被阻塞。如果这个事件被“设置”,则等待结束,可以继续下去。

这常常用于多个线程之间的同步。如果一个线程需要等待另一个线程完成某事后才能做某事,则可以使用事件等待。另一个线程完成后设置事件即可。

这个数据结构是KEVENT。读者没有必要去了解其内部结构。这个结构总是用KeInitlizeEvent初始化。这个函数原型如下:

    VOID 
    KeInitializeEvent(
        IN PRKEVENT  Event,
        IN EVENT_TYPE  Type,
        IN BOOLEAN  State
    );

第一个参数是要初始化的事件。第二个参数是事件类型,这个详见于后面的解释。第三个参数是初始化状态。一般的说设置为FALSE。也就是未设状态。这样等待者需要等待设置之后才能通过。

事件不需要销毁。

设置事件使用函数KeSetEvent。这个函数原型如下:

LONG 
    KeSetEvent(
         IN PRKEVENT  Event,
         IN KPRIORITY  Increment,
         IN BOOLEAN  Wait
     );

Event是要设置的事件。Increment用于提升优先权。目前设置为0即可。Wait表示是否后面马上紧接着一个KeWaitSingleObject来等待这个事件。一般设置为TRUE。(事件初始化之后,一般就要开始等待了。)

使用事件的简单代码如下:

// 定义一个事件
KEVENT event;
// 事件初始化
KeInitializeEvent(&event,SynchronizationEvent,TRUE);
……

// 事件初始化之后就可以使用了。在一个函数中,你可以等待某
// 个事件。如果这个事件没有被人设置,那就会阻塞在这里继续
// 等待。
KeWaitForSingleObject(&event,Executive,KernelMode,0,0);
……

// 这是另一个地方,有人设置这个事件。只要一设置这个事件,
// 前面等待的地方,将继续执行。
KeSetEvent(&event);

由于在KeInitializeEvent中使用了SynchronizationEvent,导致这个事件成为所谓的“自动重设”事件。一个事件如果被设置,那么所有KeWaitForSingleObject等待这个事件的地方都会通过。如果要能继续重复使用这个时间,必须重设(Reset)这个事件。当KeInitializeEvent中第二个参数被设置为NotificationEvent的时候,这个事件必须要手动重设才能使用。手动重设使用函数KeResetEvent。

LONG 
    KeResetEvent(
        IN PRKEVENT  Event
    );

如果这个事件初始化的时候是SynchronizationEvent事件,那么只有一个线程的KeWaitForSingleObject可以通过。通过之后被自动重设。那么其他的线程就只能继续等待了。这可以起到一个同步作用。所以叫做同步事件。不能起到同步作用的是通知事件(NotificationEvent)。请注意不能用手工设置通知事件的方法来取代同步事件。请读者思考一下这是为什么。

回忆前面的6.1 “使用线程”的最后的例子。在那里曾经有一个需求:就是等待线程中的函数KdPrint结束之后,外面生成线程的函数再返回。 这可以通过一个事件来实现:线程中打印结束之后,设置事件。外面的函数再返回。为了编码简单我使用了一个静态变量做事件。这种方法在线程同步中用得极多,请务必熟练掌握:

static KEVENT s_event;

// 我的线程函数。传入一个参数,这个参数是一个字符串。
VOID MyThreadProc(PVOID context)
{        
    PUNICODE_STRING str = (PUNICODE_STRING)context;
    KdPrint((“PrintInMyThread:%wZ\r\n”,str));
    KeSetEvent(&s_event);        // 在这里设置事件。
    PsTerminateSystemThread(STATUS_SUCCESS);
} 

// 生成线程的函数:
VOID MyFunction()
{
    UNICODE_STRING str = RTL_CONSTANT_STRING(L“Hello!”);
    HANDLE thread = NULL;
    NTSTATUS status;

    KeInitializeEvent(&event,SynchronizationEvent,TRUE);     // 初始化事件
    status = PsCreateSystemThread(
        &thread,0L,NULL,NULL,NULL,MyThreadProc,(PVOID)&str);
    if(!NT_SUCCESS(status))
    {
        // 错误处理。
        …
    }
    ZwClose(thread);
    // 等待事件结束再返回:
    KeWaitForSingleObject(&s_event,Executive,KernelMode,0,0);
}

实际上等待线程结束并不一定要用事件。线程本身也可以当作一个事件来等待。但是这里为了演示事件的用法而使用了事件。以上的方法调用线程则不必担心str的内存空间会无效了。因为这个函数在线程执行完KdPrint之后才返回。缺点是这个函数不能起到并发执行的作用。

取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

Powered by bytekits.com,汇天下文字,成非凡梦想!!!