本篇及下篇教程我们将讲述内核同步对象。同步是一个涉及面非常广的主题,系统提供了多种同步对象,因此两篇文章也仅能让您对其有个大致的了解。
10.1 同步对象
迄今为止,我们都不需要独占访问某个数据,因为我们仅有一个线程在工作。当有两个或多个线程都需要访问同一个资源时,就需要引入同步机制,否则此资源的状态就无法预知了。比如当两个线程同时访问(在一个多处理器的系统中这种情况很常见)一个保存在共亨内存中的变量。常见的解决方法就是后面的线程等待前一个线程完成数据访问。
为解决此类问题,操作系统提供了一些同步对象的机制:事件(Event)、互斥(Mutex)--在内核中被称为突变体(Mutant)、信号灯(Semaphore)等等。这些同步机制在用户模式下也存在,而且使用方法也大同小异。
所有的同步对象结构的第一个字段均为一个DISPATCHER_HEADER结构用以描述此对象所期望的操作。下面是本章将要用到的两个结构:定时器对象(又叫watchdog)和线程对象。
_KTIMER = packed record Header: DISPATCHER_HEADER; …… end; KTHREAD = packed record Header: DISPATCHER_HEADER; . . . KTHREAD ENDS end; |
从逻辑上讲,每个对象与其同胞对象均有不同之处,这个很容易理解,在这里我也不打算讲太多。在这里假设您使用过用户模式下的相关同步机制,我仅强调一下每个同步对象均有两种状态:释放(信号态)或者忙碌(非信号态)。
内核模式与用户模式在同步的管理上并无大的差异,但还是有几个地方需要注意:首先也是最重要的一点就是同步对象的IRQL要比DISPATCH_LEVLE低,也就是说执行在高于或等于DISPATCH_LEVEL级上的代码不能阻塞线程。这个规则表明你只能在DriverEntry函数、AddDevice函数,或驱动程序的分派函数中阻塞当前线程。因为这些函数都执行在PASSIVE_LEVEL级上。没有必要在DriverEntry或AddDevice函数中阻塞当前线程,因为这些函数的工作仅仅是初始化一些数据结构。其次在内核模式下是通过指向同步对象的指针访问该对象的,而在用户模式下则是通过对象句柄访问。
调用KeWaitForSingleObject或KeWaitForMultipleObjects函数可以使代码(以及背景线程)在一个或多个同步对象上等待,等待它们进入信号态。内核为初始化和控制这些对象的状态提供了例程。
10.2 教程源码
unit TimerWorks; interface uses nt_status, ntoskrnl, hal, native, fcall, macros; function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall; implementation var g_pkThread: PVOID; {PTR KTHREAD} g_fStop: Boolean; g_usDeviceName, g_usSymbolicLinkName: UNICODE_STRING; function ThreadProc(StartContext: PVOID): NTSTATUS; var dwCounter: DWORD; pkThread: PVOID; {PKTHREAD} _kTimer: KTIMER; liDueTime: LARGE_INTEGER; iPriority: KPRIORITY; begin dwCounter := 0; DbgPrint(#13#10'TimerWorks: Entering ThreadProc'#13#10); DbgPrint('TimerWorks: IRQL = %d'#13#10, KeGetCurrentIrql); pkThread := KeGetCurrentThread; iPriority := KeQueryPriorityThread(pkThread); DbgPrint('TimerWorks: Thread Priority = %d'#13#10, iPriority); Inc(iPriority, 2); KeSetPriorityThread(pkTh |