您的位置:首页 > 其它

内核下各种同步处理方法(自旋锁、信号灯、互斥体…)

2017-02-08 10:11 295 查看
转自:http://www.blogfshare.com/kernel-synchronization.html

1.在支持多线程的操作系统下,有些函数会出现不可重入的现象。所谓“可重入”是指函数的执行结果不和执行顺序有关。反之如果执行结果和执行顺序有关,则称这个函数是“不可重入”的。

 

2.Windows将中断的概念进行了扩展,提出一个中断请求级(IRQL)的概念。其中规定了32个中断请求级别,分别是0~2级别为软件中断,3~31级为硬件中断,其中数字从0~31,优先级别逐次递增。





在内核模式下,可以通过调用KeGetCurrentIrql内核函数来得到当前的IRQL级别。

 

3.线程运行在PASSIVE_LEVEL,这个时候操作系统随时可能将当前线程切换到别的线程,但是如果提升IRQL到DISPATCH_LEVEL级别,这时候不会出现线程切换。这是一种很常用的同步处理机制,但这种方法只能使用于单CPU的系统。对于多CPU的系统,需要采用别的同步处理机制。

 

4.读取不在物理内存中的分页内存时,会引发一个页故障,从而执行异常的处理函数重新将磁盘的文件内容交换到物理内存中。页故障允许出现在PASSIVE_LEVEL级别的程序中,但如果在DISPATCH_LEVEL或者更高级别IRQL的程序中会带来系统崩溃。对于等于或高于DISPATCH_LEVEL级别的程序不能使用分页内存,必须使用非分页内存。驱动程序的StartIO例程、DPC例程、中断服务例程都运行在DISPATCH_LEVEL或者更高IRQL。因此,在这些例程中不能使用分页内存,否则会导致系统崩溃。

 

5.控制IRQL提升与降低:KeRaiseIrql/KeLowerIrql

 

6.自旋锁:不同于线程中的等待时间。在线程中如果等待某个事件,操作系统会使这个线程进入休眠状态,CPU会运行其他线程,而自旋锁原理则不同,他不会切换到别的线程,而是一直让这个线程“自旋”。因此,对于自旋锁占用时间不宜过长,否则会导致申请自旋锁的其他线程处于自旋,这会浪费CPU宝贵的时间。在单CPU的系统中,获取自旋锁只是将当前的IRQL从PASSVIE_LEVEL级别提升到DISPATCH_LEVEL级别。驱动程序必须在低于或者等于DISPATCH_LEVEL的IRQL级别中使用自旋锁。

注意:如果在DISPATHC_LEVEL级别申请自旋锁,那么不会改变IRQL级别。这时,申请和释放自旋锁可以简单的使用KeAcquireSpinLockAtDpcLevelKeReleaseSpinLockFromDpcLevel内核函数。

...

21.使用互锁操作进行同步

DDK提供了两类互锁操作来提供简单的同步处理。一类是InterlockedXX函数,另一类是ExInterlockedXX函数。

其中InterlockedXX函数不是通过自旋锁实现的,内部不会提升IRQL,因此既可以操作分页数据又可以操作非分页数据。而ExInterlockedXX函数是通过自旋锁实现的,在使用的时候需要我们提供一个自旋锁。内部依靠这个自旋锁实现同步,因此它不能操作分页内存。

附注:

这段代码标红的缘于这几天看MS toaster代码ToasterCleanup函数,在函数的注释部分发现一句:"Note that ToasterCleanup does not call the PAGED_CODE marco because the routine use a spin lock"。开始没明白为什么持有锁的代码不能被分页出去,后来看到网上这篇文章就明白了~最后再引用看雪上一段讨论:

引用:

最初由 lidagogo发布



KeAcquireInStackQueuedSpinLock

在这里执行了一些进程查找 远程写入内存等操作 直接蓝屏

KeReleaseInStackQueuedSpinLock

放到外面来做 怎么都没事 什么鬼?

崩溃在这行

//DbgBreakPoint();

KeA...

第一,由于自旋锁与队列自旋锁系列函数可能会把当前的 IRQL 从 PASSIVE_LEVEL 提升到 DISPATCH_LEVEL 。另一方面,你用 ZwAllocateVirtualMemory() 分配的缓冲区(pBuffer)可能随时被换出内存,因此 memcpy() 访问的时候可能已经被换出了,这会引发一个缺页异常,而相应的

page falut handler() 无法在 DISPATCH_LEVEL 下完成,因此 pBuffer 指向的内存无法被换入,造成崩溃。

解决办法是,pBuffer 应该用 ExAllocatePool/ExAllocatePoolWithTag() 来分配,可以为它的第一个参数传入
NonPagePool 表示在内核空间的非换页池中分配。这样它就确保不会被换出内存。

第二,很多 Dbg*() 系列例程要求在 PASSIVE_LEVEL 下执行。获得自旋锁后,你可以用 KeGetCurrentIrql() 查看当前 IRQL ,如果再不是 PASSIVE_LEVEL 了,就应该避免执行后续的

操作(包括调用 Dbg*());或者用 KeLowerIrql() 降低到 PASSIVE_LEVEL ,可以在一个 if 语句块中完成检测;

第三,也可能是你没有初始化自旋锁造成崩溃的,正确的使用方法如下:

使用自旋锁:

// 包含 wdm.h ,其中有 KSPIN_LOCK 等数据结构的定义

KSPIN_LOCK get_spin_lock;
KIRQL old_irql; //用于保存并恢复 IRQL

KeInitializeSpinLock(&get_spin_lock);

KeAcquireSpinLock(&get_spin_lock, &old_irql);

{....在这里完成你的操作}

KeReleaseSpinLock(&get_spin_lock, old_irql);

使用排队的自旋锁:

KSPIN_LOCK get_spin_lock;
KLOCK_QUEUE_HANDLE lock_queue_handle;

KeInitializeSpinLock(&get_spin_lock);

KeAcquireInStackQueueSpinLock(&get_spin_lock, &lock_queue_handle);

{.....完成内存复制操作,记得处理 IRQL 不为 PASSIVE_LEVEL 时的情况}

KeReleaseInStackQueueSpinLock(&lock_queue_handle);

最后,如果上述方案都失效,还可以使用下列的页面锁定函数之一,根据你的实际情况来选择:

MmProbeAndLockPages()

MmLockPagableCodeSection()

MmLockPagableDataSection()

MmLockPagableSectionByHandle()

页面锁定使用这种机制将你的 pBuffer 保留在物理内存中(不会被换出),直到使用 MmUnlockPages() 显式解锁。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  windows