内核同步机制

190次阅读

内核同步机制

Windows内核提供了多种同步机制来处理并发。

从使用场景来进行划分比较简单,这些同步机制可以分为:

  • 无锁实现,适用高IRQL无法wait对象,竞争不激烈:自旋锁

  • 同一时间只能一个线程执行:互斥量(Mutex)

  • 单写多读:执行体,获取独占锁时使用时使用临界区禁用用户和普通内核APC

  • 固定个数线程执行:信号量(Semaphore)

  • 通知所有线程/单个线程:事件(Event)

  • 简单原子操作:互锁函数,CPU内联函数(特殊CPU指令)实现

自旋锁

自旋锁原理就是通过循环CPU提供的原子指令compare and swap实现的无锁机制。

当处于IRQL 2或者更高时,无法使用KeWaitForSingleObject,这时就可以使用自旋锁。

// 提升IRQL到DPC(2)并获得自旋锁
KIRQL oldIrql = KeAcquireSpinLockRaiseToDpc(&KSPIN_LOCK);
// 释放自旋锁,并降低IRQL
KeReleaseSpinLock(&KSPIN_LOCK,oldIrql);

除了这一组外,还有针对其它IRQL的申请和释放函数。

这里有个问题是,为什么要提升IRQL,因为自旋锁主要依赖的是CPU的compare and swap的原子指令,不提升IRQL也是能正常使用的。

为什么要提升IRQL

假设我们有两个处理器,处理器A和处理器B,且它们都在执行内核代码。现在有两个线程,线程1在处理器A上运行,线程2在处理器B上运行。它们都需要访问一个共享资源(比如一个链表),这个资源由一个SpinLock来保护。

  1. 线程1(处理器A)获取锁

    • 线程1获取了SpinLock,并开始操作共享资源。
    • 由于我们不提升IRQL,因此处理器A仍然可以被中断。
  2. 线程1被中断

    • 在操作共享资源的过程中,处理器A上发生了一个中断(例如,一个定时器中断),操作系统将线程1挂起,转而去处理这个中断。
    • 由于线程1持有了SpinLock,这个锁还没有被释放。
  3. 线程2(处理器B)尝试获取锁

    • 同时,线程2在处理器B上试图获取相同的SpinLock。
    • 由于SpinLock已经被线程1持有,线程2进入了自旋状态,等待线程1释放锁。
  4. 线程1的中断服务程序(ISR)尝试获取锁

    • 处理器A上的中断服务程序(ISR)可能会尝试获取同一个SpinLock来访问共享资源(例如,链表中的某个节点)。
    • 由于锁被线程1持有且线程1已被挂起,这将导致中断服务程序陷入等待,从而阻塞了中断处理。
  5. 优先级反转与死锁

    • 处理器A无法继续,因为它在处理中断,而中断服务程序也在等待锁。
    • 处理器B也无法继续,因为它的线程2在等待线程1释放锁。
    • 这就可能导致死锁,整个系统的这部分功能变得无法响应,可能会导致系统崩溃或某些功能失效。

从上面例子可以看出,这里的提升IRQL的自旋锁主要是用于执行在不同IRQL上的线程之间的同步,如果说竞争自旋锁保护资源的线程都处于同一IRQL(比如都处于IRQL 0),就可以不提升,因为不会存在上述情况。

互斥量(Mutex)/快速互斥量(Fast_Mutex)

// 普通互斥量
ExInitializeMutex
KeWaitForSingleObject
KeReleaseMutex

// 快速互斥量
ExInitializeFastMutex
ExAcquireFastMutex
ExReleaseFastMutex

快速互斥量比普通互斥量性能更高、会提升IRQL到APC,且只能无限等待,不能指定时间等待。

执行体

互斥量只允许有一个线程存取共享资源,这对于单写多读的场景并不怎么高效。

// 执行体
ExInitializeResourceLite
// 读
ExAcquireResourceSharedLite/ExReleaseResourceLite
// 写
ExEnterCriticalRegionAndAcquireResourceExclusive/ExReleaseResourceAndLeaveCriticalRegion

写操作时,需要调用ExEnterCriticalRegion禁用内核APC,所以这两个操作总是一起。

信号量

// 信号量
KeInitializeSemaphore
KeWaitForSingleObject

信号量很简单,当内部值大于0时,KeWaitForSingleObject将返回,信号量-1。如果值到0,则信号量变为无信号,直到其它线程调用KeReleaseSemaphore释放信号量了,让信号量增加。

事件

// 事件
KeInitializeEvent
KeWaitXxx
KeSetEvent
KeResetEvent/KeClearEvent

事件很简单,要么真要么假,即要么有信号,要么无信号。

在初始化时有两种类别:

  • 通知事件:释放所有在该事件上等待的线程
  • 同步事件:释放最多一个线程后事件变为无信号状态

总结

综合场景使用不同的同步机制,对于内核开发来说,除了信号量,其它的都挺常用的。

本文使用 markdown.com.cn 排版

正文完
 0
liushui
版权声明:本站原创文章,由 liushui 2024-08-01发表,共计2064字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。