生活的天平本不平衡,只有通过努力改变其偏向。

Windows驱动编程基础教程 第十一部分

2008-06-07

6.3 使用事件通知

一些读者可能熟悉“事件驱动”编程技术。但是这里的“事件”与之不同。内核中的事件是一个数据结构。这个结构的指针可以当作一个参数传入一个等待函数中。 如果这个事件不被“设置”,则这个等待函数不会返回,这个线程被阻塞。如果这个事件被“设置”,则等待结束,可以继续下去。
这常常用于多个线程之间的同步。如果一个线程需要等待另一个线程完成某事后才能做某事,则可以使用事件等待。另一个线程完成后设置事件即可。
这个数据结构是KEVENT。读者没有必要去了解其内部结构。这个结构总是用KeInitlizeEvent初始化。这个函数原型如下:

C++代码
  1. VOID
  2. KeInitializeEvent(
  3. IN PRKEVENT  Event,
  4. IN EVENT_TYPE  Type,
  5. IN BOOLEAN State
  6. );

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

C++代码
  1. LONG KeSetEvent(
  2. IN PRKEVENT  Event,
  3. IN KPRIORITY  Increment,
  4. IN BOOLEAN Wait
  5. );

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

C++代码
  1. // 定义一个事件
  2. KEVENT event;
  3. // 事件初始化
  4. KeInitializeEvent(&event,SynchronizationEvent,TRUE);
  5. ……
  6. // 事件初始化之后就可以使用了。在一个函数中,你可以等待某
  7. // 个事件。如果这个事件没有被人设置,那就会阻塞在这里继续
  8. // 等待。
  9. KeWaitForSingleObject(&event,Executive,KernelMode,0,0);
  10. ……
  11. // 这是另一个地方,有人设置这个事件。只要一设置这个事件,
  12. // 前面等待的地方,将继续执行。
  13. KeSetEvent(&event);

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

C++代码
  1. LONG
  2. KeResetEvent(
  3. IN PRKEVENT  Event
  4. );

如果这个事件初始化的时候是SynchronizationEvent事件,那么只有一个线程的KeWaitForSingleObject可以通过。通 过之后被自动重设。那么其他的线程就只能继续等待了。这可以起到一个同步作用。所以叫做同步事件。不能起到同步作用的是通知事件 (NotificationEvent)。请注意不能用手工设置通知事件的方法来取代同步事件。请读者思考一下这是为什么。
回忆前面的1.6.1 “使用线程”的最后的例子。在那里曾经有一个需求:就是等待线程中的函数KdPrint结束之后,外面生成线程的函数再返回。    这可以通过一个事件来实现:线程中打印结束之后,设置事件。外面的函数再返回。为了编码简单我使用了一个静态变量做事件。这种方法在线程同步中用得极多, 请务必熟练掌握:

C++代码
  1. static KEVENT s_event;
  2. // 我的线程函数。传入一个参数,这个参数是一个字符串。
  3. VOID MyThreadProc(PVOID context)
  4. {
  5. PUNICODE_STRING str = (PUNICODE_STRING)context;
  6. KdPrint((“PrintInMyThread:%wZ\r\n”,str));
  7. KeSetEvent(&s_event);        // 在这里设置事件。
  8. PsTerminateSystemThread(STATUS_SUCCESS);
  9. }
  10. // 生成线程的函数:
  11. VOID MyFunction()
  12. {
  13. UNICODE_STRING str = RTL_CONSTANT_STRING(L“Hello!”);
  14. HANDLE thread = NULL;
  15. NTSTATUS status;
  16. KeInitializeEvent(&event,SynchronizationEvent,TRUE);     // 初始化事件
  17. status = PsCreateSystemThread(
  18. &thread,0L,NULL,NULL,NULL,MyThreadProc,(PVOID)&str);
  19. if(!NT_SUCCESS(status))
  20. {
  21. // 错误处理。
  22. }
  23. ZwClose(thread);
  24. // 等待事件结束再返回:
  25. KeWaitForSingleObject(&s_event,Executive,KernelMode,0,0);
  26. }

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

作者:lonkil | 分类目录:编程开发 | 标签:

一条评论

  1. 曹洋 说道:

    支持。。。

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>