驱动开发(12)内核中的多线程和同步对象

时间:2022-08-15 15:46:29

本博文由CSDN博主zuishikonghuan所作,版权归zuishikonghuan所有,转载请注明出处:http://blog.csdn.net/zuishikonghuan/article/details/51301896

在内核模式下创建线程

在驱动程序中创建线程的方法是调用 PsCreateSystemThread 内核函数,此函数即可以创建系统线程,也可以创建用户线程。此函数的原型如下:

NTSTATUS PsCreateSystemThread(
  _Out_     PHANDLE            ThreadHandle,
  _In_      ULONG              DesiredAccess,
  _In_opt_  POBJECT_ATTRIBUTES ObjectAttributes,
  _In_opt_  HANDLE             ProcessHandle,
  _Out_opt_ PCLIENT_ID         ClientId,
  _In_      PKSTART_ROUTINE    StartRoutine,
  _In_opt_  PVOID              StartContext
);
  1. 参数1:一个 HANDLE 类型的指针,用于输出创建的线程句柄。此句柄必须在不需要使用时通过 ZwClose 释放。
  2. 参数2:创建线程的权限,一般设置为0。
  3. 参数3:指向指定对象的属性的结构,一般设置为 NULL 。
  4. 参数4:如果为 NULL ,则线程属于系统。如果指定一个进程句柄,则线程属于该进程,通过 NtCurrentProcess 宏可以得到当前进程句柄。
  5. 参数5:用于接收客户端ID,一般为 NULL 。
  6. 参数6:线程的启动函数指针。
  7. 参数7:传递给启动函数的数据。

线程启动函数原型:

KSTART_ROUTINE ThreadStart;

VOID ThreadStart(
  _In_ PVOID StartContext
)
{ ... }

需要注意,和用户模式线程不同,线程必须调用 PsTerminateSystemThread (或系统关机)才会退出,而不是函数返回即退出。

内核模式下的等待

驱动程序进行等待可以通过 KeWaitForSingleObject 和 KeWaitForMultipleObjects 进行等待,其实用户模式下的 WaitForSingleObject 和 WaitForMultipleObjects 就是调用了这两个函数,先来看看函数原型:

NTSTATUS KeWaitForSingleObject(
  _In_     PVOID           Object,
  _In_     KWAIT_REASON    WaitReason,
  _In_     KPROCESSOR_MODE WaitMode,
  _In_     BOOLEAN         Alertable,
  _In_opt_ PLARGE_INTEGER  Timeout
);

NTSTATUS  KeWaitForMultipleObjects(
  _In_      ULONG           Count,
  _In_      PVOID           Object[],
  _In_      WAIT_TYPE       WaitType,
  _In_      KWAIT_REASON    WaitReason,
  _In_      KPROCESSOR_MODE WaitMode,
  _In_      BOOLEAN         Alertable,
  _In_opt_  PLARGE_INTEGER  Timeout,
  _Out_opt_ PKWAIT_BLOCK    WaitBlockArray
);

解释一下 KeWaitForSingleObject :

  1. 参数1:同步对象的指针。(不是句柄)
  2. 参数2:指定等待的原因。驱动程序应将该值设置为 Executive ,除非是做代表用户的工作,并在用户线程的上下文中,在这种情况下,应该将该值设置为运行 UserRequest 。
  3. 参数3:说明是在内核模式等待还是在用户模式等待(KernelMode or UserMode)一般设置为 KernelMode 。
  4. 参数4:设置等待是否“警惕”,一般为 FALSE 。
  5. 参数5:超时时间,若为 NULL ,则为无限等待(还记得上一篇中说过 IRQL 不小于 DISPATCH_LEVEL 的线程不能进行没有超时的等待操作吗)指向64位整数指针时,若是正数,则为从 January 1, 1601 ,若为负数,则从现在开始计时。

内核模式下的同步对象

和用户模式下的同步对象大致一样,因为用户模式同步对象就是通过调用内核同步对象函数创建的。

在应用程序中我们只能得到同步对象的指针,而在驱动程序中我们可以直接使用事件对象的指针!

对于各函数我想我不必一个个解释了,MSDN说的很清楚了,这里只起一个引导和区分作用。

1。事件

创建事件:KeInitializeEvent

创建命名事件:IoCreateNotificationEvent 和 IoCreateSynchronizationEvent

命名的事件对象的好处是,可以方便在不同驱动中找到事件对象的指针。

NotificationEvent(通知事件)和 SynchronizationEvent(同步事件)的区别:前者由未激发变为激发时,需要手动变成未激发,而后者激发后调用 KeWaitFor… 函数就会自动变为未激发。

2。自旋锁

获取自旋锁:KeAcquireSpinLock

释放自旋锁:KeReleaseSpinLock

自旋锁有“锁住”和“解锁”状态,当线程获取自旋锁时,如果处于解锁状态就可以被获取,获取会自动锁住自旋锁,必须释放后才能解锁;如果获取时处于锁住状态,那么线程就会不停地“自旋”,即线程不停地常数获取自旋锁,直到获取到为止。自旋锁不能工作在 IRQL DISPATCH_LEVEL 中断请求级。

特别需要注意:线程如果等待一个“事件”对象,那么线程就会休眠,操作系统会去调度其他线程,而获取自旋锁不同,线程会一直“自旋”,会浪费宝贵的 CPU 时间,因此获取自旋锁后到释放之前的时间不要过长。

3。互斥体/快速互斥体

创建互斥体:KeInitializeMutex

释放互斥体:KeReleaseMutex

创建快速互斥体:ExInitializeFastMutex

释放快速互斥体:ExReleaseFastMutex

快速互斥体不能被线程递归获取。

只有一个线程可以占用互斥体,获取互斥体的线程如果不释放互斥体,其他线程就不能得到互斥体,线程获取到一个互斥体时,互斥体变为激发,释放一个互斥体时,互斥体变为未激发。互斥体未激发时,等待互斥体就可以获取他,激发时则会使线程休眠,直到指定互斥体被释放后,才会有且仅有一个等待该互斥体的线程获取该互斥体。

4。信号量

创建信号量:KeInitializeSemaphore

释放信号量: KeReleaseSemaphore

信号量同样有激发和未激发两种状态,他的特殊之处是内部维护一个计数器,只要计数器不小于1就是激发状态,否则是未激发状态。

相关文章