内核下各种同步处理方法(自旋锁、信号灯、互斥体…)
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级别。这时,申请和释放自旋锁可以简单的使用KeAcquireSpinLockAtDpcLevel 和KeReleaseSpinLockFromDpcLevel内核函数。
...
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() 显式解锁。
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级别。这时,申请和释放自旋锁可以简单的使用KeAcquireSpinLockAtDpcLevel 和KeReleaseSpinLockFromDpcLevel内核函数。
...
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() 显式解锁。
相关文章推荐
- 如何重装TCP/IP协议
- Windows 8 官方高清壁纸欣赏与下载
- 谁是桌面王者?Win PK Linux三大镇山之宝
- 对《大家都在点赞 Windows Terminal,我决定给你泼一盆冷水》一文的商榷
- Windows Clang开发环境备忘
- 将Windows Server 2016 打造成好用的办公系统
- 从Windows系统下访问Linux分区相关软件
- 对《大家都在点赞 Windows Terminal,我决定给你泼一盆冷水》一文的商榷
- Windows下搭建本地SVN服务器
- 使用Windows原生命令一键清空剪贴板
- windows用windeployqt发布qt quick application程序
- 利用开源软件打造自己的全功能远程工具
- Windows 8虚拟机不能全屏的解决方法
- 虚拟化基础架构Windows 2008篇之1-虚拟化基础服务概述
- 虚拟化基础架构Windows 2008篇之5-安装Windows部署服务
- 虚拟化基础架构Windows 2008篇之7-添加其他操作系统的安装镜像
- 虚拟化基础架构Windows 2008篇之9-配置Windows部署服务
- 虚拟化基础架构Windows 2008篇之12-WSUS工作站端配置