|
作者:Tesla.Angela
这篇文章,来源于一个一年多前的噩梦(具体原因参见主教程的WFP章节)。
选择在这个时候发这篇文章,目的是终结这个噩梦,顺带问候一下YaoHui这个SB。
题外话:如果对我的这个故事感兴趣,还可以看一下我在2013-8-5至2013-8-9之间发的微博。
言归正传,当你在一个高IRQL的线程A里,需要执行一个只能在低IRQL才能执行的操作,怎么办?
比如说,在IRQL=2的时候,需要用ZwReadFile。我曾经想过两个办法:
1.在调用ZwReadFile之前,调用KeLowerIrql降低IRQL,然后再调用ZwReadFile,最后使用KeRaiseIrql来恢复原来的IRQL。
2.发消息给另外一个线程B,让线程B执行,等线程B执行完,再让线程A继续执行。
结果如下:
1.蓝屏。
2.偶尔会正常,但没过多久就会死锁。如果在单CPU的系统里,一定死锁。
原因如下:
1.彻底破坏了系统环境。
2.在高IRQL的情况下,线程是不能切换的。当线程A、B都被同一个CPU执行时,会陷入“你等我我又等你”的状态。
这两个办法都行不通之后,我郁闷了很久。当时我直骂NT系统的设计人员,说他们是“用嘴来拉屎,用屁眼来吃饭”。
后来,我终于找到了这个问题的解决方案:使用“作业队列”。
我个人感觉“作业队列”其实是代码片段。等于发通知给系统,让系统的线程(IRQL=0)来执行你的代码片段。
“作业队列”,有人翻译为“劳务线程”、“作业线程”甚至“工人线程”。
不管怎么说,这玩意其实非常简单,只有两个相关函数:ExInitializeWorkItem、ExQueueWorkItem。
ExInitializeWorkItem用来初始化一个“作业队列”,而ExQueueWorkItem则用于执行。
这两个函数的原型如下:
VOID ExInitializeWorkItem
(
_In_ PWORK_QUEUE_ITEM Item, //一片NonPagedPool,大小为sizeof(WORK_QUEUE_ITEM)
_In_ PWORKER_THREAD_ROUTINE Routine,//“代码片段”函数
_In_ PVOID Context //传递给“代码片段”函数的参数
);
VOID ExQueueWorkItem
(
_Inout_ PWORK_QUEUE_ITEM WorkItem, //你之前申请的那片NonPagedPool
_In_ WORK_QUEUE_TYPE QueueType //作业队列的类型,个人感觉没啥区别,详情见MSDN
);
下面的例子是在WFP回调里调用ZwWriteFile。
但有一点我必须要说明:使用“作业队列”来执行代码是滞后的。时机是IRQL下降到0的时候。
换句话说,系统会“尽快”执行你的代码,“尽快”的意思是,一旦IRQL下降到0,你的代码就能马上被执行。
|
评分
-
查看全部评分
|