48、Windows驱动程序模型笔记(六),同步

简介: 关于同步     执行在某线程上下文中的代码在任何时刻都可能被系统夺去控制权。另外,只有在多处理器的计算机上才能真正实现多线程的并发执行。Windows NT为解决一般的同步问题提供了两种方法,一个是中断请求优先级(IRQL)方案,另一个是在关键代码段周围声明和释放自旋锁。

关于同步<?xml:namespace prefix = o />

    执行在某线程上下文中的代码在任何时刻都可能被系统夺去控制权。另外,只有在多处理器的计算机上才能真正实现多线程的并发执行。Windows NT为解决一般的同步问题提供了两种方法,一个是中断请求优先级(IRQL)方案,另一个是在关键代码段周围声明和释放自旋锁。IRQL可以避免在单 CPU上的破坏性抢先,而自旋锁可以防止多CPU间的干扰。

wps_clip_image-14675

. 中断请求级

在DISPATCH_LEVEL级和PROFILE_LEVEL级之间是各种硬件中断级。通常,每个有中断能力的设备都有一个IRQL,它定义了该设备的 中断优先级别。WDM驱动程序只有在收到一个副功能码为IRP_MN_START_DEVICE的IRP_MJ_PNP请求后,才能确定其设备的 IRQL。设备的配置信息作为参数传递给该请求,而设备的IRQL就包含在这个配置信息中。

基本同步规则

遵循下面规则,你可以利用IRQL的同步效果:

    所有对共享数据的访问都应该在同一(提升的)IRQL上进行。换句话说,不论何时何地,如果你的代码访问的数据对象被其它代码共享,那么你应该使你的代码执行在高于PASSIVE_LEVEL的级上。一旦越过 PASSIVE_LEVEL级,操作系统将不允许同IRQL的活动相互抢先,从而防止了潜在的冲突。然而这个规则不足以保护多处理器机器上的数据,在多处理器机器中你还需要另外的防护措施——自旋锁(spin lock)。如果你仅关心单CPU上的操作,那么使用IRQL就可以解决所有同步问题。但事实上,所有WDM驱动程序都必须设计成能够运行在多处理器的系统上。

IRQL与线程优先级

    线程优先级是与IRQL非常不同的概念。线程优先级控制着线程调度器的调度动作,决定何时抢先运行线程以及下一次运行什么线程。然而,当IRQL级高于或等于DISPATCH_LEVEL级时线程切换停止,无论当前活动的是什么线程都将保持活动状态直到IRQL降到DISPATCH_LEVEL级之下。而此时的“优先级”仅指IRQL本身,由它控制到底哪个活动该执行,而不是该切换到哪个线程的上下文。

IRQL和分页

    执行在提升的IRQL级上的一个后果是,系统将不能处理页故障(系统在APC级处理页故障)。这意味着:

    执行在高于或等于DISPATCH_LEVEL级上的代码绝对不能造成页故障。这也意味着执行在高于或等于DISPATCH_LEVEL级上的代码必须存在于非分页内存中。此外,所有这些代码要访问的数据也必须存在于非分页内存中。最后,随着IRQL的提升,你能使用的内核模式支持例程将会越来越少。

自旋锁

    关于自旋锁有两个明显的事实。第一,如果一个已经拥有某个自旋锁的CPU想第二次获得这个自旋锁,则该CPU将死锁(deadlock)。自旋锁没有与其关联的“使用计数器”或“所有者标识”;锁或者被占用或者空闲。如果你在锁被占用时获取它,你将等待到该锁被释放。如果碰巧你的CPU已经拥有了该锁,那么用于释放锁的代码将得不到运行,因为你使CPU永远处于“测试并设置”某个内存变量的自旋状态。

    关于自旋锁的另一个事实是,CPU在等待自旋锁时不做任何有用的工作,仅仅是等待。所以,为了避免影响性能,你应该在拥有自旋锁时做尽量少的操作,因为此时某个CPU可能正在等待这个自旋锁。

关于自旋锁还存在着一个不太明显但很重要的事实:你仅能在低于或等于DISPATCH_LEVEL级上请求自旋锁,在你拥有自旋锁期间,内核将把你的代码提升到DISPATCH_LEVEL级上运行。在内部,内核能在高于DISPATCH_LEVEL的级上获取自旋锁,但你和我都做不到这一点。

内核同步对象

    Windows NT提供了五种内核同步对象(Kernel Dispatcher Object),你可以用它们控制非任意线程(普通线程)的流程。

    在任何时刻,任何对象都处于两种状态中的一种:信号态或非信号态。

对象

数据类型

描述

Event(事件)

KEVENT

阻塞一个线程直到其它线程检测到某事件发生

Semaphore(信号灯)

KSEMAPHORE

与事件对象相似,但可以满足任意数量的等待

Mutex(互斥)

KMUTEX

执行到关键代码段时,禁止其它线程执行该代码段

Timer(定时器)

KTIMER

推迟线程执行一段时期

Thread(线程)

KTHREAD

阻塞一个线程直到另一个线程结束

图示 内核同步对象

    通常,如果在线程执行时发生了软件或硬件中断,那么在内核处理中断期间,该线程仍然是“当前”线程。而内核模式代码执行时所在的上下文环境就是指这个“当前”线程的上下文。为了响应各种中断,Windows NT调度器可能会切换线程,这样,另一个线程将成为新的“当前”线程。

关于阻塞:

    当我们处理某个请求时,仅能阻塞产生该请求的线程。

    执行在高于或等于DISPATCH_LEVEL级上的代码不能阻塞线程。

一般在驱动程序的派遣函数中阻塞当前线程。

编程中的注意点

1、KeWaitForSingleObject中参数Alertable是一个布尔类型的值。它不同于WaitReason,这个参数以另一种方式影响系统行为,它决定等待是否可以提前终止以提交一个APC。如果等待发生在用户模式中,那么内存管理器就可以把线程的内核模式堆栈换出。如果驱动程序以自动变量(在堆栈中)形式创建事件对象,并且某个线程又在提升的IRQL级上调用了KeSetEvent,而此时该事件对象刚好又被换出内存,结果将产生一个bug check。所以我们应该总把alertable参数指定为FALSE,即在内核模式中等待。如果你的代码执行在DISPATCH_LEVEL级上,则必须指定0超时,因为在这个IRQL上不允许阻塞。

2、KeWaitForMultipleObjects

    如果你指定了WaitAll,则返回值STATUS_SUCCESS表示等待的所有对象都进入了信号态。如果你指定了WaitAny,则返回值在数值上等于进入信号态的对象在objects数组中的索引。如果碰巧有多个对象进入了信号态,则该值仅代表其中的一个,可能是第一个也可能是其它。你可以认为该值等于STATUS_WAIT_0加上数组索引。你可以先用NT_SUCCESS测试返回码,然后再从其中提取数组索引:

NTSTATUS status = KeWaitForMultipleObjects(...);

if (NT_SUCCESS(status))

{

ULONG iSignalled = (ULONG) status - (ULONG) STATUS_WAIT_0;

...

}

正处于自己时间片中的线程不能被阻塞。

3、KeSetEvent的第三个参数,我们总指定这个参数为FALSE。具体原因见[2],P115。

4、通知定时器允许有任意数量的等待线程。同步定时器正相反,它只允许有一个等待线程;一旦有线程在这种定时器上等待,定时器就自动进入非信号态。

    通过使用定时器的扩展设置函数,你可以请求一个周期性的超时,这种定时器在第一次倒计时时使用duetime时间,到期后再使用period值重复倒计时。

5、作为一个通用规则,你绝不要写同步响应用户模式请求的代码,仅能为确定的I/O控制请求这样做。一般说来,最好挂起长耗时的操作(从派遣例程中返回STATUS_PENDING代码)而以异步方式完成。再有,你不要一上来就调用等待原语。线程阻塞仅适合设备驱动程序中的某几个地方使用。

底线是:使用非警惕性等待,除非你知道不这样做的原因。

5、快速互斥体

内核互斥

快速互斥

可以被单线程递归获取(系统为其维护一个请求计数器)

不能被递归获取

速度慢

速度快

所有者只能收到特殊的内核APC

所有者不能收到任何APC

所有者不能被换出内存

不自动提升被阻塞线程的优先级(如果运行在大于或等于APC_LEVEL),除非你使用XxxUnsafe函数并且执行在PASSIVE_LEVEL级上

可以是多对象等待的一部分

不能作为KeWaitForMultipleObjects的参数使用

APC-asynchronous procedure call

    如果你拥有快速互斥对象你就不能发出APC,这意味着你将处于APC_LEVEL或更高的IRQL,在这一级上,线程优先级将失效,但你的代码将不受干扰地执行,除非有硬件中断发生。

6、你只能在低于或等于DISPATCH_LEVEL级上调用S链表函数。只要所有对链表的引用都使用ExInterlockedXxx函数,那么访问双链表和单链表的ExInterlockedXxx函数可以在任何IRQL上调用。这些函数没有IRQL限制的原因是因为它们在执行时都禁止了中断,这就等于把IRQL提升到最高可能的级别。一旦中断被禁止,这些函数就获取你指定的自旋锁。因为此时在同一CPU上没有其它代码能获得控制,并且其它CPU上的代码也不能获取那个自旋锁,所以你的链表是安全的。

参考

[1] 下载DbgView

http://www.sysinternals.com/

[2] Windows驱动程序模型设计

目录
相关文章
|
2月前
|
Windows
Windows下版本控制器(SVN)-验证是否安装成功+配置版本库+启动服务器端程序
Windows下版本控制器(SVN)-验证是否安装成功+配置版本库+启动服务器端程序
110 2
|
3月前
|
Windows
Windows下版本控制器(SVN)-启动服务器端程序
Windows下版本控制器(SVN)-启动服务器端程序
112 4
|
4月前
|
安全 Devops 测试技术
AppSpider 7.5.018 for Windows - Web 应用程序安全测试
AppSpider 7.5.018 for Windows - Web 应用程序安全测试
84 0
AppSpider 7.5.018 for Windows - Web 应用程序安全测试
|
4月前
|
Web App开发 人工智能 JSON
Windows版来啦!Qwen3+MCPs,用AI自动发布小红书图文/视频笔记!
上一篇用 Qwen3+MCPs实现AI自动发小红书的最佳实践 有超多小伙伴关注,同时也排队在蹲Windows版本的教程。
659 1
|
7月前
|
安全 JavaScript Java
AppSpider Pro 7.5.015 for Windows - Web 应用程序安全测试
AppSpider Pro 7.5.015 for Windows - Web 应用程序安全测试
91 12
AppSpider Pro 7.5.015 for Windows - Web 应用程序安全测试
|
6月前
|
Windows
Windows程序的数字签名证书怎么申请
Windows程序的数字签名证书申请流程包括:准备企业资料(营业执照、税务登记证等),提交申请表及企业资料。经过初审、实名认证和二审后,等待1-5个工作日审核结果。审核通过后,CA机构颁发证书并通过邮件或邮寄方式发送。收到证书后按指南安装并使用签名工具对程序进行数字签名,确保软件完整性和可信度。注意证书有效期、管理和兼容性问题。
|
6月前
|
自然语言处理 安全 测试技术
HCL AppScan Standard 10.8.0 (Windows) - Web 应用程序安全测试
HCL AppScan Standard 10.8.0 (Windows) - Web 应用程序安全测试
325 0
HCL AppScan Standard 10.8.0 (Windows) - Web 应用程序安全测试
|
6月前
|
算法 关系型数据库 测试技术
WHQL微软驱动签名方案,让驱动程序在Windows系统流畅运行
WHQL认证(Windows徽标认证)是微软设立的严格测试标准,旨在确保驱动程序的兼容性、稳定性和互通性。本文介绍了三种WHQL微软驱动签名方案:单系统签名、多系统签名和硬件兼容性测试方案,分别满足不同开发商的需求。通过WHQL认证,不仅能消除Windows安装警告,提升用户体验,还能获得“Designed for Windows”徽标授权,入列全球Windows Catalog及HCL产品表,提升品牌权威性和采购优先权。此外,访问微软OCA可获取错误反馈,助力产品质量改进。选择合适的签名方案,让驱动在Windows系统中流畅运行!
|
11月前
|
监控 Ubuntu Linux
视频监控笔记(五):Ubuntu和windows时区同步问题-your clock is behind
这篇文章介绍了如何在Ubuntu和Windows系统中通过设置相同的时区并使用ntp服务来解决时间同步问题。
230 4
视频监控笔记(五):Ubuntu和windows时区同步问题-your clock is behind
|
12月前
|
Windows Python
python获取windows机子上运行的程序名称
python获取windows机子上运行的程序名称