一个月前开始学习Windows驱动开发,断断续续将《Windows驱动开发技术详解》这本书概读了两遍,将其中的一些体会分享给大家,这次介绍一下最简单的同步调用和异步调用的执行顺序问题。 我们知道驱动基本上由DriverEntry例程(这里引用书上的术语,就不称作函数了)和若干派遣例程组成,这里只讲最简单的,其余的以后再说。 DriverEntry例程是由System进程在驱动被加载的时机被调用的。其中主要注册派遣例程,以方便后续的用户进程能够访问它们。 同步这个概念就不说了,我们来看看下面基本的4种调用。 1)同步调用(驱动支持同步模式) 这种情况下,同步等待是由Windows来负责等待操作的完成,而驱动仅仅在派遣例程中的调用IOCompleteRequest来完成该操作。 2)异步调用(驱动支持同步模式) 这种情况下,同步等待是Windows就不负责了,等待必须由用户负责,而驱动仅仅在派遣例程中的调用IOCompleteRequest来完成该操作。 从这里可以看到,如果驱动层只支持同步模式的话,应用程序使用同步和异步没有多大驱动,因为当前调用返回值,底层动作已经完毕。反而多了等待事件的操作,还不如用同步方式。 这里需要强调一下,在派遣例程返回之前,用户的IO调用是不会返回的。好像好多人对此不是很理解。 3)同步调用(驱动支持异步模式) 和1)一样,同步等待是由Windows来负责等待操作的完成,不过在派遣例程返回时,该IO操作还没有完成。等到后续的DPC例程执行完时,本次IO操作才算完毕,用户的的调用这时候才真正返回。 这里需要注意的有两点: 一:DPC的执行线程上下文已经不一定是该调用的用户线程,因此有些需要访问用户内存空间的代码就会出问题了。 二:DPC的执行级别是在DISPATCH_LEVEL上的,执行期间不会发生线程切换。(书上这么说的,不过如果在其中有等待操作的话,还是会发生线程切换的,因为是当前线程主动放弃CPU时间的。 【当前线程<线程甲>:我是无辜的,为什么让我暂停!!! <----没名字的家伙,停的就是你-_-】) 4)异步调用(驱动支持异步模式) 从图上可以看到,只有在这种方式下,用户才能在底层执行的同时来做点兼职工作。 介绍完基本的调用方式,我们把取消例程加上后再分析一下。由于仅支持同步模式的驱动以及用户同步调用时,IO操作已经完成,因此这里只考虑异步调用(驱动支持异步模式)方式。 其中可以分为取消成功和取消失败两种情况: 1)取消成功 我们将上面的兼职工作修改成取消IO操作,从图上可以看到,驱动执行Cancel例程时,会将DPC例程取消,同时将本次IO操作以Cancelled返回。然后用户继续等待事件,当然也已经将信号设置上了。 注意,虽然已经调用了CancelIO,但是,等待事件不可跳过,因为还有取消失败的情况发生。 还有Cancel例程的运行级别是在APC_LEVEL上的。 2)取消失败 我们发现,Windows打算执行Cancel例程时,本次IO操作已经完毕。该请求已经不存在了,当然以失败而告终。 注意DPC中应该将该IO操作的Cancel例程指针设定为NULL。 到此为止,上述的执行顺序还算比较简单,原因是用户调用还处于单线程模式下。 下面稍微加大一点难度,我们把CancelIO的调用移动到另外一个线程中去,这样CancelIO的调用时机点和本次IO操作的重叠契机又多了两种情况。我们先来看看和单线程相同的两种情况。 除了Cancel例程在另一个线程上下文中执行之外,和单线程方式没啥区别。 1)取消成功 2)取消失败 多出来的两种情况是,IO操作执行过程中(IO调用还未返回),由另一个线程取消本次IO。这里也会存在两种情况,成功或者失败。 1)取消失败 IO操作还未到达派遣例程时,当然调用无效啦,就像平白无辜调用CancelIO一样。见图。 2)取消成功 这次的情况是两个例程赶到一块去了,大家注意做好同步控制。具体还得看派遣例程内部如何实现。实现得好的话,这种情况和上面的某一种属于同一类。 到此为止,有几个概念需要明确一下。 1)例程 有时可以把它当作用户态的线程在内核上的一种映射。当然不一定是一对一的。 2)运行级别(IRQL) 书上也有介绍,大家记住处于DISPATCH_LEVEL的运行级别下是不会发生线程切换的就行。 不过有一点书上也没有提到,就是处理APC_LEVEL的运行级别下会不会发生线程切换呢?好像网上也没有找到答案。 由此看来,只要掌握同步和异步的概念,并且对系统已线程为单位进行执行的基础知识没有问题的话,也不是很难理解的。 关于多IO操作的StartIO例程的执行顺序,下回学好了再写,今天就到此为止,希望大家共同进步。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课